mcpField
F
framefields

framefields

github.com/gatewai-devクリエイティブ・メディア497110 回インストール今日更新
リポジトリ

framefields について

framefieldsは、Node.jsとTypeScriptで使うコードベースの動画SDK向け導入スキルです。環境構築から最初のレンダリング確認までを案内し、ブラウザーを使わずWebGPUでネイティブに描画します。

主な機能

  1. 01動画プロジェクトのひな型作成
  2. 02SDKとレンダースクリプトの設定
  3. 03レンダリング画像と動きの確認
  4. 04ライブプレビューの起動
  5. 05動画の検証と書き出し

活用シーン

  1. 01TypeScript動画プロジェクトの新規構築
  2. 02全編を書き出す前のフレーム確認
  3. 03プレビューで映像と音声を確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gatewai-dev/framefields --skill framefields

インストール後、Claude Code で/framefieldsを入力して読み込まれたか確認します。

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

framefields

概要

framefields はコードファーストの動画 SDK です。動画は、キーフレームアニメーションを持つレイヤーのツリーである TypeScript の Composition で、ブラウザー、Puppeteer、Chromium を使わずに、Node.js 上で WebGPU(Dawn)をネイティブに使用してレンダリングされます。エンジンは framefields npm パッケージです。以下のすべては、ユーザー自身のプロジェクトで実行されます。

このスキルでは、何もない状態から、最初のレンダリングを検証するところまで進めます。それ以降の作業には、用途別のスキルを読み込んでください。

タスクスキル
シーン、レイアウト、タイポグラフィ、アニメーション、ビートグリッド、映像構成framefields-compose
ポストプロセス、カラーグレーディング、VFX、デバイス上のビジョン(トラッキング、切り抜き、ポーズ)framefields-effects
フレームの検査、フレームグリッド、ピクセルテスト、動画のエクスポートと QAframefields-render

1. 環境を確認する

  • Node.js 22 以降。 node --version を実行してください。古いバージョンではインポートに失敗します。
  • GPU、または CPU レンダラー。 macOS では Metal を使用します(すべての Mac と macOS VM には Metal GPU が搭載されています)。Linux では Vulkan、Windows では Direct3D 12 または Vulkan を使用します。GPU がない場合、framefields は CPU でレンダリングします。Linux(サンドボックス、CI ランナー、コンテナー)では Mesa lavapipe を使用し、apt-get install -y libvulkan1 mesa-vulkan-drivers でインストールします。Windows では、Windows に組み込まれている WARP、または lavapipe を使用します。すべての機能(テキスト、メディア、Lottie、エフェクト、3D、ビジョン)を同じようにレンダリングできますが、速度は遅くなります。Linux でのテストでは、M1 Pro の GPU より約 12 倍遅くなりました。describeGpu() は software: true を返します。フレーム、グリッド、短い動画のエクスポートには十分です。長編動画には、GPU マシンまたはレンダーワーカー(framefields-render)を使用してください。
  • ES モジュールプロジェクト。 プロジェクトの package.json に "type": "module" が必要です。

2. インストール

プロジェクトで既に使用されているパッケージマネージャーを使ってください(pnpm-lock.yaml、yarn.lock、bun.lock、package-lock.json を確認してください)。

インストール前に package.json を作成してください。 npm 11、pnpm 10、bun は、プロジェクトで許可されていない限り依存関係のインストールスクリプトをブロックしますが、framefields が必要とするのは 1 つだけです。skia-canvas はインストールスクリプトでネイティブバイナリをダウンロードします。これがないと、import "framefields" は Cannot find module '.../skia.node' で失敗します。インストールスクリプトを含むその他のパッケージは、スクリプトを実行しなくても動作するため、インストールを静かに保ち、framefields が使用しないダウンロードを省くために、それらは拒否します。

新しいプロジェクトでは、3 つすべてのブロックを含む package.json を作成してください。既存のプロジェクトでは、既存のエントリを維持しながら、各ブロックを package.json に統合してください。

{
  "name": "my-film",
  "private": true,
  "type": "module",
  "allowScripts": {
    "skia-canvas": true,
    "sharp": false,
    "webgpu": false,
    "onnxruntime-node": false,
    "node-av": false,
    "esbuild": false
  },
  "pnpm": {
    "onlyBuiltDependencies": ["skia-canvas"],
    "ignoredBuiltDependencies": ["sharp", "webgpu", "onnxruntime-node", "node-av", "esbuild"]
  },
  "trustedDependencies": ["skia-canvas"]
}

allowScripts は npm、pnpm は pnpm、trustedDependencies は bun が読み取ります。それぞれのツールは、他のツール向けのフィールドを無視します。Yarn はデフォルトでインストールスクリプトを実行するため、これらは必要ありません。また、インストールに使用する正確なマネージャーとバージョンを "packageManager" に追加してください(例:"[email protected]")。インストールで生成されたロックファイル(pnpm、npm、または yarn)をコミットしてください(bun のロックファイルはワーカーでレンダリングできません)。それからインストールします。

npm install framefields
npm install --save-dev tsx typescript @types/node

インストールは、install-scripts の警告なしで完了する必要があります。許可を設定する前に framefields をインストールした場合は、設定を追加し、スキップされたスクリプトを npm rebuild skia-canvas(pnpm rebuild skia-canvas)で再実行してください。すべてのスクリプトを一括で許可しないでください(npm install-scripts approve --all)。

最小限の tsconfig.json:

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022",
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true,
    "types": ["node"]
  },
  "include": ["src/**/*"]
}

3. プロジェクトをひな形から作成する

コンポジションは、レンダリングするスクリプトと分けてください。これにより、テスト、プレビュー、レンダリングスクリプト、レンダーワーカーが、すべて同じ動画をビルドします。film.ts はビルドのみを行う必要があります。レンダリング、process.exit()、インポート時の書き込みは行いません。

src/
  theme.ts    # canvas size, fps, colors, fonts: the only place for magic numbers
  film.ts     # buildFilm(): assembles the Composition
  render.ts   # renders frames for review, a live preview, or the full video
assets/
  fonts/      # .ttf / .otf files the film uses
  footage/    # source videos (.mp4, .mov) and audio (.mp3, .wav) tracked in Git LFS
output/       # renders (add to .gitignore)

テキストには実際のフォントファイルが必要です。使用するフォントをユーザーに尋ねてください。

// src/theme.ts
import { FontManager } from "framefields";

export const W = 1920;
export const H = 1080;
export const FPS = 30;
export const INK = "#0E0D0C";
export const PAPER = "#EEE9E0";
export const SANS = "Inter";

export async function registerFonts(): Promise<void> {
  await FontManager.register({ family: SANS, source: "assets/fonts/Inter.ttf" });
}
// src/film.ts
import { Composition, Layer, LayerAnimation } from "framefields";
import { FPS, H, INK, PAPER, registerFonts, SANS, W } from "./theme.js";

const DURATION = 3 * FPS; // frames

export async function buildFilm(): Promise<Composition> {
  // Fonts must be registered before any text layer is built.
  await registerFonts();

  const film = new Composition({
    width: W,
    height: H,
    fps: FPS,
    durationMs: (DURATION / FPS) * 1000,
    backgroundColor: INK,
  });

  film.add(
    Layer.text("Hello, framefields", {
      id: "title",
      position: "absolute",
      x: 0,
      y: H / 2 - 60,
      width: W,
      height: 120,
      fontFamily: SANS,
      fontSize: 96,
      fontWeight: 700,
      fill: PAPER,
      align: "center",
    }).animate(
      LayerAnimation.create()
        .fadeIn(0, 20, "power2.out")
        .fromTo("y", H / 2 - 20, H / 2 - 60, { start: 0, end: 30, ease: "expo.out" }),
    ),
  );

  return film;
}
// src/render.ts
import fs from "node:fs/promises";
import path from "node:path";
import { HeadlessMediaRenderer, startPreview } from "framefields";
import { buildFilm } from "./film.js";

const OUT = path.resolve(import.meta.dirname, "../output");
const [mode = "video", ...args] = process.argv.slice(2);

if (mode === "preview") {
  // The page runs film.ts itself and renders it with WebGPU in the browser.
  const session = await startPreview({ entry: new URL("./film.ts", import.meta.url), export: "buildFilm" });
  console.log(`Preview at ${session.url}`);
  await session.closed; // until its tab closes or the next preview replaces it
  process.exit(0);
}

const film = await buildFilm();
await fs.mkdir(OUT, { recursive: true });

if (mode === "frames") {
  const renderer = new HeadlessMediaRenderer();
  for (const frame of args.map(Number)) {
    const png = await film.renderFrame({ frame, renderer });
    await fs.writeFile(path.join(OUT, `f${String(frame).padStart(4, "0")}.png`), png);
  }
} else if (mode === "grid") {
  const png = await film.renderFrameGrid({
    frames: args.length ? args.map(Number) : [0, 10, 20, 30, 45, 60, 75, 89],
    columns: 4,
    cellWidth: 480,
    showLabels: true,
  });
  await fs.writeFile(path.join(OUT, "grid.png"), png);
} else {
  const result = await film.renderVideo({
    outputPath: path.join(OUT, "output.mp4"),
    quality: "high",
  });
  await result.cleanup?.();
  console.log(`Exported ${result.filePath}`);
}

// Native GPU and encoder threads keep Node alive; always exit explicitly.
process.exit(0);

package.json にスクリプトを追加します。

"scripts": {
  "typecheck": "tsc --noEmit -p .",
  "frames": "tsx src/render.ts frames",
  "grid": "tsx src/render.ts grid",
  "preview": "tsx src/render.ts preview",
  "render": "tsx src/render.ts"
}
動画アセットと Git LFS

動画入力(Layer.video("assets/footage/clip.mp4"))、Lottie アニメーション(Layer.lottie(...))、オーディオトラック(Layer.audio(...))、LUT、カスタムフォントは Git LFS に含め、output/ 内のレンダリング出力は .gitignore に含めます。

  1. Git LFS を初期化します:

    git lfs install
    
  2. プロジェクトルートに .gitattributes を設定します:

    assets/**/*.mp4 filter=lfs diff=lfs merge=lfs -text
    assets/**/*.mov filter=lfs diff=lfs merge=lfs -text
    assets/**/*.webm filter=lfs diff=lfs merge=lfs -text
    assets/**/*.mp3 filter=lfs diff=lfs merge=lfs -text
    assets/**/*.wav filter=lfs diff=lfs merge=lfs -text
    assets/**/*.cube filter=lfs diff=lfs merge=lfs -text
    assets/**/*.lottie filter=lfs diff=lfs merge=lfs -text
    assets/fonts/**/*.ttf filter=lfs diff=lfs merge=lfs -text
    assets/fonts/**/*.otf filter=lfs diff=lfs merge=lfs -text
    
  3. レンダリング前にポインターを実体化します: 新しくクローンした環境やコンテナー化されたエージェント環境では、LFS メディアは約 130 バイトのテキストポインターファイルとして存在します。Framefields のネイティブ WebGPU および WebCodecs デマルチプレクサーはポインターをデコードできないため、失敗するか、空白のフレームを出力します。必ずポインターが実体化されていることを確認してください:

    git lfs pull
    
  4. .gitignore の対象を絞ります: LFS でソース映像を追跡する場合、*.mp4 を .gitignore に追加しないでください。代わりに出力ディレクトリ(output/、scratch/)を無視します。

4. 検証は、最も手軽な手順から

いきなり動画全体をレンダリングしないでください。GPU のないマシンでは、すべてのレンダリングが CPU(lavapipe または WARP)で実行されるため、手軽な手順がより重要になります。各手順では、動画レンダリングで問題が判明するまでにかかる数分に対し、数秒で問題を検出できます。

  1. npm run typecheck
  2. npm run frames -- 0 30 60 を実行し、output/ 内の PNG を確認します。黒いフレームは通常、フォントの欠落、レイヤーがキャンバスの外にあること、または不透明度が 0 のままになっていることを意味します。
  3. npm run grid を実行し、コンタクトシート全体で動きが滑らかに見えるか確認します。
  4. 誰かが見ている場合は、npm run preview を実行します。エクスポート前に、音声付きで動画を再生します(詳細は後述)。
  5. フレームと動きに問題がないことを確認してから、npm run render を実行します。

全体をレンダリングする前に、フレーム PNG またはグリッドをユーザーに見せてください。framefields-render では、ピクセルプローブテスト、レンダリング QA(renderVideo({ qa }))、空白フレームの診断について説明しています。エンジン自体に問題や不具合があると思われる場合は、https://github.com/gatewai-dev/framefields にイシューを登録してください。

ライブプレビュー

npm run preview はローカルホストのプレーヤー(映像、音声、タイムライン)を起動し、その URL を出力します。ページは film.ts 自体を実行し、WebGPU でリアルタイムにレンダリングするため、ユーザーは実際の動画を確認できます。低速なデバイスでは、音声に追いつくためにフレームがスキップされます。

  • バックグラウンドコマンドとして実行し、出力を確認します。ページが開いたタイミングや、ページのエラー(ビルドの失敗、フレームでの例外、フォントの欠落)が表示され、停止した理由も出力されます。最後のタブが閉じてから 5 秒後に停止します。もう一度実行してください。
  • 自動で何かが開くわけではありません。アプリに組み込みブラウザー(Claude Code、Codex)がある場合は、そこで URL を開きます。それ以外の場合は、ユーザーにリンクを渡してください。startPreview(..., { open: true }) は既定のブラウザーを開きます。
  • アセットはファイルの URL を基準に参照します。 ページは film.ts 自体を実行するため、アセットパスはブラウザー内でも解決できる必要があります。process.cwd() や単純な相対パスではなく、path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets/fonts") のようにモジュール自体の URL を基準に作成してください。プレビューではプロジェクト内(package.json があるフォルダー)のファイルをすべて配信します。フォントやメディアを別の場所に保存している場合は、startPreview に root(1 つ以上のパス)を渡します。
  • 動画を変更した後は、もう一度実行してください。同じ URL が使われ、開いているタブは同じタイミングで自動的に再読み込みされます。
  • #t=12.5(秒)を使うと、特定の位置にリンクできます。スクリプトを実行できるブラウザーでは、window.framefieldsPreview に seek(seconds)、play()、pause()、state() があります。seek はフレームの描画後に解決されるため、その後にスクリーンショットを撮ってください。
  • ユーザーは映像にメモをピン留めできます。 一時停止してクリックする(または範囲をドラッグする)と、その位置と再生時刻にメモが追加されます。各メモはプレビューの出力に表示され、.framefields/preview-notes/ に保存されます。保存されるのは notes.json と、該当箇所をマークしたフレーム画像 note-<id>.jpg です。ユーザーからメモを残したと言われたら、編集前に notes.json を読み、未解決の各メモの画像を確認してください。その後、ユーザーが変更を確認してメモを完了にできるよう、プレビューをもう一度実行します。詳細は framefields-render を参照してください。

5. サーバーでレンダリングする

framefields のレンダーワーカー(Docker、RunPod の Pod、またはサーバーレス)がプッシュされたコミットをレンダリングします。LFS アセットを取得し、ロックファイルからインストールを行い、src/film.ts をインポートして buildFilm を呼び出し、結果をアップロードします。上記の手順でビルドしたプロジェクトは、次の条件を満たせば変更せずに動作します。

  • framefields が 2.2 以降で、ロックファイルがコミットされて package.json と一致し、packageManager が固定されていること。
  • すべての入力が assets/ 配下にあり、Git LFS で追跡されてプッシュされていること(git lfs ls-files に一覧表示されること)。
  • アセットのパスの取得に process.cwd() ではなく import.meta.url を使うこと。
  • レンダリング中に node_modules へ書き込む処理がないこと(ファイルは読み取り専用で共有されます)。

ジョブが失敗すると { code, hint, retryable } が返されます。retryable が true の場合に限り、変更せずに再送信してください。ジョブの形式とエラーコードについては、https://framefields.dev/docs/guides/render-service/ を参照してください。

6. フィルムを発展させる

最初のレンダリングが成功したら、専門スキルに移行します。framefields-compose はフィルムをビートグリッド上のシーンモジュールに分割し、framefields-effects はカラーグレーディング、グレイン、ビジョン駆動型エフェクトを扱い、framefields-render はテストと納品チェックを行います。プロジェクトの成長に合わせて、サイズ、色、フォント、タイミングの唯一の情報源として theme.ts を維持してください。

コメント