audiogram
This skill should be used when the user asks to "make an audiogram", "turn a podcast into a video", "create a waveform video", "make a podcast clip video", "convert audio to video", "animate a waveform/audio bars", "add a moving waveform to my voiceover", or "make a square/vertical clip for a podcast quote". Covers amplitude-driven waveforms, frequency bars, synced captions, cover-art layout, a progress bar, and 1:1 / 9:16 social framing.
1How It Works & Mechanism
The single golden rule to avoid pitfalls: Bar/waveform heights must be derived from the current frame; never use a real-time AnalyserNode + rAF loop.
**Drive every bar height from the current frame, never from a real-time analyser loop.**
Provide a mechanistic explanation: Real-time analysis reads "what is playing at this very moment," whereas a renderer draws frames out-of-order or at varying speeds, causing the waveform to desync or freeze.
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.
Two implementation paths bound to specific APIs: Use visualizeAudio for frequency bars, and visualizeAudioWaveform for smooth oscilloscopes; numberOfSamples must be a power of 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, mirror the first half to both sides so that low frequencies sit in the middle.
For a centered equalizer, take the first N bars and **mirror** them around the middle so the bass sits in the center.
Do not load long audio entirely: Trim to ≤90s first, or use useWindowedAudioData instead (fetching only near the current frame via HTTP range requests).
For anything long, trim first or use `useWindowedAudioData()`, which fetches only the audio around the current frame via HTTP range requests.
The layout is fixed into five layers (background / cover + title / waveform / captions / progress bar), where the progress bar is a pure function of frame/durationInFrames.
const progress = useCurrentFrame() / durationInFrames; // 0 → 1
The caption mechanism is not implemented in this skill and is explicitly delegated to caption-animation (a skill in another repository); this skill only places the caption track into the layout and shares the same audio clock.
The mechanics of word-by-word reveal, active-word highlighting, and SRT/JSON timing belong to the **caption-animation** skill
Multi-aspect ratio support from a single draft: Register multiple Compositions for the same component to reframe content instead of adding pillarboxes/letterboxes.
Design once inside the center safe zone, then reframe — don't letterbox.
The core acceptance criterion for the HEAVY tier is "offline baking": No audio analysis during rendering; samples must either be baked into props or pre-decoded using useAudioData.
never analyse audio at render time; headless render has no realtime audio clock.
The verification loop targets two aspects: "fidelity + anomalies": Still frame waveform heights must match the audio amplitude at that moment and sync with captions, while checking for missing covers, out-of-bounds captions, or overflowing bars.
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.
2Core Capabilities
3External Dependencies
| 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) |
4Recommended Governance & Evidence
- scripts/: Local script or command execution: (Verification loop explicitly listed in SKILL.md) `npx remotion still Audiogram out/f-{start,mid,end}.png -`
- 网络端点: External network access declared: `npx remotion …` and `@remotion/media-utils`: Initial package retrieval via official npm channels (registry.npmjs.or)
- 本地工作目录: File I/O and artifact output: Remotion project constructed by the agent according to this skill and out/audiogram.mp4 (out/clip-square.mp4 for 1:1)
5Second-Pass Verification
- [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 …` 亦给出。
6Verdict & Scenarios
77694b043f16eba0…