@effing/annie

中文文档
0.48.0发布于 2026/11/12MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 2227

Annie animation frame generator utilities

原始使用文档
文档目录14 节
  1. Installation
  2. Concepts
  3. Annie Format
  4. Generation Patterns
  5. Quick Start
  6. API Overview
  7. Functions
  8. Examples
  9. With Express/Node.js
  10. With React Router / Remix
  11. Saving to File
  12. Reading an Annie
  13. FFmpeg Integration
  14. Related Packages

@effing/annie

Generate and read TAR archives of PNG, JPEG or QOI frames for animated layers.

Part of the Effing family — programmatic video creation with TypeScript.

Annie is a simple animation format: a TAR archive containing sequentially-named PNG, JPEG or QOI frames. Generate frames server-side, stream them to the browser or FFmpeg, and read them back with the same package.

Installation

npm install @effing/annie

Concepts

Annie Format

An Annie is a TAR archive where each entry is a PNG, JPEG or QOI frame:

frame_00000
frame_00001
frame_00002
...

Frames may vary in pixel format, channels (with or without alpha) and size, but all frames of an annie must be one format: all PNG, all JPEG or all QOI. FFS and FFmpeg decode an annie with the decoder for its first frame.

This format is:

  • Streamable — frames can be written as they're generated
  • Universal — TAR is supported everywhere
  • FFmpeg-compatible — can be piped directly to FFmpeg as image input

Generation Patterns

Annie supports two generation patterns:

Buffer — Collect all frames, return complete archive:

const archive = await annieBuffer(frames);

Stream — Yield chunks as frames are generated:

const stream = annieStream(frames);

Quick Start

import { annieStream, annieBuffer } from "@effing/annie";
import { createCanvas, renderReactElement } from "@effing/canvas";
import { tween, easeOutQuad } from "@effing/tween";

// Define a frame generator
async function* generateFrames() {
  yield* tween(90, async ({ lower: progress }) => {
    const scale = 1 + 0.3 * easeOutQuad(progress);
    const canvas = createCanvas(1080, 1920);
    const ctx = canvas.getContext("2d");
    await renderReactElement(
      ctx,
      <div style={{
        width: 1080,
        height: 1920,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        fontSize: 72,
        transform: `scale(${scale})`
      }}>
        Hello World!
      </div>,
      { fonts: myFonts }
    );
    return canvas.encode("png");
  });
}

// Stream response
const stream = annieStream(generateFrames(), { signal: request.signal });
return new Response(stream, {
  headers: { "Content-Type": "application/x-tar" }
});

API Overview

Functions

annieStream(frames, options?)

Create a ReadableStream that produces TAR chunks as frames are generated.

function annieStream(
  frames: AsyncIterable<Buffer>,
  options?: AnnieStreamOptions,
): ReadableStream<Buffer>;

Options:

  • signal — AbortSignal for cancellation

annieBuffer(frames)

Collect all frames and return a complete TAR buffer.

function annieBuffer(frames: AsyncIterable<Buffer>): Promise<Buffer>;

annieFrames(source)

Async-iterate the frames of an existing Annie, in ascending index order.

function annieFrames(source: AnnieSource): AsyncGenerator<AnnieFrame>;

The source can be a Uint8Array/ArrayBuffer, an (async) iterable of byte chunks (such as a Node.js Readable), or a Web ReadableStream. Each frame carries its index, entry name, raw data bytes, and a contentType sniffed from the frame's magic bytes (the archive itself does not record the image format): "image/png", "image/jpeg", "image/qoi", or "application/octet-stream" for anything else.

sniffImageContentType(bytes)

Detect a PNG, JPEG or QOI image from its leading bytes, the same way annieFrames does: the full 8-byte PNG signature, FF D8 FF for JPEG, or qoif for QOI (with 3 or 4 channels and colorspace 0 or 1 when the 14-byte header is there). Returns "image/png", "image/jpeg", "image/qoi" or undefined.

function sniffImageContentType(bytes: Uint8Array): ImageContentType | undefined;

Examples

With Express/Node.js

import { Readable } from "stream";
import { annieStream } from "@effing/annie";

app.get("/animation.tar", async (req, res) => {
  const stream = annieStream(generateFrames());
  res.setHeader("Content-Type", "application/x-tar");
  Readable.fromWeb(stream).pipe(res);
});

With React Router / Remix

import { annieStream } from "@effing/annie";

export async function loader({ request }: LoaderFunctionArgs) {
  const stream = annieStream(generateFrames(), { signal: request.signal });
  return new Response(stream, {
    headers: { "Content-Type": "application/x-tar" },
  });
}

Saving to File

import { writeFile } from "fs/promises";
import { annieBuffer } from "@effing/annie";

const buffer = await annieBuffer(generateFrames());
await writeFile("animation.tar", buffer);

Reading an Annie

import { readFile } from "fs/promises";
import { annieFrames } from "@effing/annie";

for await (const frame of annieFrames(await readFile("animation.tar"))) {
  console.log(frame.name, frame.contentType, frame.data.length);
}

FFmpeg Integration

Note that Annie TAR archives can be piped directly to FFmpeg. Extract the frames with tar -xO and pipe to FFmpeg's image2pipe input. This works when every frame has the same format; QOI frames need FFmpeg 5.1 or newer.

# Create animated PNG (loops forever)
tar -xO < animation.tar | ffmpeg -f image2pipe -framerate 30 -i - -plays 0 -c:v apng -f apng output.png

# Create MP4 video
tar -xO < animation.tar | ffmpeg -f image2pipe -framerate 30 -i - -c:v libx264 -pix_fmt yuv420p output.mp4

# Create GIF
tar -xO < animation.tar | ffmpeg -f image2pipe -framerate 30 -i - output.gif

版本与发布记录

@effing/annie 共发布 119 个版本,首次发布于 2026/01/21;过去 12 个月发布了 119 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.48.0latest2026/11/121 天前
0.47.32026/11/112 天前
0.47.22026/11/104 天前
0.47.12026/10/2915 天前
0.47.02026/10/2717 天前
0.46.22026/10/2222 天前

关于 @effing/annie 的常见问题

@effing/annie 是什么?

Annie animation frame generator utilities

如何安装 @effing/annie?

在项目目录运行 npm install @effing/annie,或使用 pnpm add / yarn add / bun add。

@effing/annie 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@effing/annie 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@effing/annie 可以商用吗?

@effing/annie 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@effing/annie 的下载量有多少?

根据 npm 官方下载统计,@effing/annie 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,227 次,日均约 74 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@effing/annie 还在维护吗?

最新版本 0.48.0 发布于 2026/11/12(1 天前);至今共发布 119 个版本,其中过去 12 个月发布了 119 个;首次发布于 2026/01/21。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。