1仕組みと動作原理
落とし穴を防ぐ唯一の鉄則:バー/波形の高さは現在のフレームから導出する必要があり、リアルタイムの AnalyserNode + rAF ループは絶対に使用しないこと。
**Drive every bar height from the current frame, never from a real-time analyser loop.**
仕組みの解説:リアルタイム分析は「今この瞬間に再生されている音」を読み取りますが、レンダラーはフレームを順不同または可変速度で描画するため、波形が音ズレしたり停止したりします。
Live `AnalyserNode` + `requestAnimationFrame` reads "what is playing right now" — but a video renderer paints frames out of order and faster/slower than real time, so the wave desyncs or freezes.
2つの実装ルートを個別の API にバインド:スペクトルバーには visualizeAudio、滑らかなオシロスコープ波形には visualizeAudioWaveform を使用。numberOfSamples は必ず 2 の累乗である必要があります。
`numberOfSamples` must be a power of two (16/32/64); use 16–32 for a chunky branded look, 64+ for a detailed spectrum.
センター配置のイコライザーは、低周波数が中央に配置されるよう、前半部分を両端にミラーリングする必要があります。
For a centered equalizer, take the first N bars and **mirror** them around the middle so the bass sits in the center.
長尺音声を丸ごとロードしないこと:事前に 90 秒以下にトリミングするか、useWindowedAudioData(HTTP range リクエストにより現在のフレーム付近のみを取得)に切り替えます。
For anything long, trim first or use `useWindowedAudioData()`, which fetches only the audio around the current frame via HTTP range requests.
レイアウトは5つのレイヤー(背景 / カバー+タイトル / 波形 / 字幕スロット / プログレスバー)に固定され、プログレスバーは frame/durationInFrames の純粋関数として動作します。
const progress = useCurrentFrame() / durationInFrames; // 0 → 1
字幕の仕組みは本スキル内では実装せず、明確に caption-animation(別リポジトリのスキル)へ委譲します。本スキルはレイアウト内に字幕トラックを配置し、同一の音声クロックを共有する役割のみを担います。
The mechanics of word-by-word reveal, active-word highlighting, and SRT/JSON timing belong to the **caption-animation** skill
1つの原稿からマルチ画角を展開:レターボックス(黒帯)を追加するのではなく、同一コンポーネントに対して複数の Composition を登録して再フレーミングします。
Design once inside the center safe zone, then reframe — don't letterbox.
HEAVY ティアの検収における中核は「オフラインベイク」:レンダリング時には音声分析を行わず、サンプルを props にベイクするか useAudioData で事前デコードします。
never analyse audio at render time; headless render has no realtime audio clock.
検証ループは「再現性+不具合防止」の2点に焦点を当てる:静止画の波形の高さがその瞬間の音声振幅と一致し字幕と同期していることを確認し、同時にカバー画像の欠落、字幕の見切れ、バーのはみ出しをチェックします。
This is HEAVY tier: the deliverable is a real **MP4** (audio muxed in), and the waveform must be *data-driven* — its shape at any frame must match the audio at that time.
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| package | @remotion/media-utils(useAudioData / useWindowedAudioData / visualizeAudio / visualizeAudioWaveform) |
| cli | npx remotion still / render / compositions |
| cli | scripts/contact-sheet.sh + scripts/probe-mp4.sh(仓库根共享验证工具箱,SKILL.md 明列) |
| network | (条件式)useWindowedAudioData 对远程音频走 HTTP range 请求;本地 staticFile 时无网络 |
| cli | 跨包委派:caption-animation(位于另一仓库 iart-ai/tiktok-video-skills) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: (SKILL.md に明記された検証ループ)`npx remotion still Audiogram out/f-{start,mid,end}.png -`
- 网络端点: 外部ネットワークアクセスの宣言: `npx remotion …` および `@remotion/media-utils`: 初回実行時に npm 公式ルート経由でパッケージを取得(registry.npmjs.or)
- 本地工作目录: ファイルの読み書きとアーティファクト出力: 本スキルに基づきエージェントが構築した Remotion プロジェクトおよび out/audiogram.mp4(1:1 の場合は out/clip-square.mp4)
5二重目の独立検証
- [ok] 「禁止实时 analyser」是否在 reference 的两条实现路线里都成立(防第一遍只信开头那句) — Remotion 路线:Bars 组件用 `useAudioData(staticFile(src))` + `visualizeAudio({ audioData, frame, fps, numberOfSamples, smoothing: true })`,值是 frame 的纯函数。canvas 路线:reference 明写 `No Remotion. Decode the file once to a per-frame amplitude array (RMS per frame), then a deterministic loop draws each frame`,并在末尾注明实时 AnalyserNode 只能用于实时预览——`// but never use it to drive a deterministic/offline render.`。两条路线都守住了铁律,且都点名了不得使用实时分析。
- [discrepancy] windowInSeconds 参数在两处的语义是否一致(第一遍漏检) — SKILL.md 在「长文件」小节把 windowInSeconds 用在 `useWindowedAudioData(staticFile("full-episode.mp3"), fps, /* windowInSeconds */ 10)`——语义是「HTTP range 抓取窗口」;而 reference 的参数速查表写 `| windowInSeconds | visualizeAudioWaveform | small (≈1/fps) tracks per-frame amplitude |`——语义是「逐帧幅度分析窗口」,且 §参数示例里 `windowInSeconds: 1 / fps` 出现在 visualizeAudioWaveform 调用中。两个 API 确实各有同名参数,但文档未区分说明,读者极易把「抓取窗口」与「分析窗口」混为一谈(设成 1/fps 去抓长音频,或设成 10 去分析都会出问题)。属对 pin commit 有效的表述出入,非能力虚假。
- [unlocatable] 「80% 静音观看」「加字幕显著提升留存」是否有据 — SKILL.md 写 "the captions carry the message for the 80% who watch on mute" 与 "subtitled video holds attention far longer",仓库内无引用来源与数据文件;只读侦查不验证,仅记为文档声明。
- [discrepancy] 跨包字幕委派会不会造成「只装本包即能力缺失」 — SKILL.md 把词级字幕机制交给 `caption-animation`,但该 skill 位于**另一个仓库** iart-ai/tiktok-video-skills(本包目录树内不存在)。youtube 包的 .claude-plugin/plugin.json 只声明本包两个 skill,README 也未提示这一跨包依赖。结论:只安装 youtube 包时,本 skill 的「captions carry the message」这一层需要用户自行补装另一包,SKILL.md 未说明——属真实的安装面缺口(不影响档位)。
- [ok] HEAVY tier 的「离线烘焙」要求是否与 reference 的实现一致 — SKILL.md 要求「Bake the amplitude/waveform samples into props (or decode once via useAudioData) offline — never analyse audio at render time」,reference 的两条路线恰好各对应一半:Remotion 路线在一次解码后按帧取值,canvas 路线预先把整轨解码为每帧 RMS 数组。要求与实现一致,不是空头约束。
- [ok] 一稿多幅与真实音频长度对齐是否落实 — reference §Full audiogram composition 末尾给出 1x1 与 9x16 两个 `<Composition>` 注册示例,并写 `Set durationInFrames from the actual clip length: Math.round(audioData.durationInSeconds * fps), or read it ahead of time with getAudioDurationInSeconds() and feed it via calculateMetadata.`;批量命令 `npx remotion render Audiogram-1x1 …` / `Audiogram-9x16 …` 亦给出。
6結論
77694b043f16eba0…