Communitygithub.com

shaharsha/deck-to-video

将幻灯片图像(或PDF)与每页旁白音频合成为自动播放的MP4视频,支持进度条、倒计时、幻灯片计数以及元素高亮同步。

deck-to-video 是什么?

该技能将一系列幻灯片图像(或PDF)与对应的旁白音频片段合并成单个MP4视频。每张幻灯片的持续时长精确等于其旁白长度加上一个短暂的停顿,然后硬切到下一张。它内置了使用PIL(而非ffmpeg)渲染的叠加层,包括填充式进度条、剩余秒数倒计时(右下角)以及“第N/M页”幻灯片计数器(右上角)。关键功能包括:基于强行对齐(如ElevenLabs提供)突出显示与旁白同步的幻灯片元素,从而实现不依赖口头指示的视觉强调;以及从对齐的单词时间戳生成SRT字幕。该技能支持多个平台(Claude Code、Cursor等),并包含脚本以处理输入准备、高亮JSON构建和字幕创建。其文档详细记录了常见的编码陷阱(如ffmpeg的drawbox限制)和调试工作流,强调验证叠加层是否正确渲染。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/shaharsha/claude-skills/tree/HEAD/skills/deck-to-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

幻灯片转视频(slides + narration → 自动播放mp4)

概述

将N张幻灯片图像(或一个PDF) + N个每页旁白音频片段合成为一个mp4文件:每张幻灯片正好保持其旁白时长加上一个短暂的停顿,然后硬切到下一张。叠加层:一个填充进度条 + 剩余秒数倒计时(右下角)和一个“N / M”幻灯片计数器(右上角)。

核心命令(脚本位于本技能的 scripts/ 目录下——请使用其完整路径调用):

python3 <this-skill-dir>/scripts/build_video.py SLIDES AUDIO_DIR out.mp4
# SLIDES = slides.pdf(以200dpi分辨率栅格化)或一个每页幻灯片图像的目录
#          (数字感知排序:s2在s10之前;仍建议使用零填充名称)
# AUDIO_DIR = slide01.mp3, slide02.mp3, … — 数量必须等于幻灯片数量
# 选项:--size 1920x1080 · --pad 0.7 · --no-bar · --no-counter · --bar-color HEX · --crf 20
#          --highlights hl.json · --anim-fps 30   (参见下方的“高亮元素”说明;
#          scripts/make_highlights.py 用于构建 hl.json)

成功时,它会为每张幻灯片打印一行——该每页时长已包含停顿时间——并以 duration matches segment sum 结束(否则退出代码非零)。

确保输入正确

  1. 幻灯片:如果从pptx开始,请使用真实的PowerPoint进行栅格化,而不是LibreOffice。 通过实际的PowerPoint将pptx导出为PDF(office-render 技能在macOS上执行此操作),然后将PDF交给脚本——LibreOffice会替换字体并重排布局,因此视频将与幻灯片作者看到的界面不匹配。200dpi可确保1080p下密集的幻灯片文本保持清晰。(已经有每页幻灯片图像?直接使用它们——跳过此步骤。)
  2. 音频:每张幻灯片一个剪辑,命名为 slideNN.mp3。 如果剪辑来自 elevenlabs-tts 的 generate_tts.py(narrating-pptx 使用它),它们已经符合此确切格式——重复使用它,以便pptx和视频听起来完全相同。
  3. 如果调整了旁白速度(例如 ffmpeg -filter:a atempo=1.1),请使用调整后的文件构建视频。

为什么叠加层使用PIL烘焙,而不是由ffmpeg绘制(血泪教训)

  • 使用基于t的宽度表达式的drawbox在某些ffmpeg构建中从第0帧就渲染一个完整的条——该表达式只在初始化时求值一次,而不是每帧求值。故障是静默的:视频正常编码,而进度条从未移动。在同一构建中,drawtext表达式确实会每帧更新,这使得损坏的进度条在一个正常工作的倒计时旁边很容易被忽略。
  • Homebrew的ffmpeg瓶装包(2026年+)没有libfreetype — No such filter: 'drawtext' — 而keg版本(ffmpeg@7、ffmpeg@8)也是如此。不要花费安装周期来发现这一点;如果因为其他原因需要完整构建,uv tool install static-ffmpeg 提供了一个(脚本会自动回退到它)。

因此,脚本使用PIL烘焙帧(进度条填充、倒计时、计数器),并将图像序列以 -r 30 编码后提供给ffmpeg。确定性、字体可控,并且比过滤30fps流更便宜编码。默认是每秒一帧,并且在幻灯片上没有任何内容移动时是正确的——秒级更新对于倒计时来说阅读自然。携带--highlights的幻灯片则以30fps烘焙,原因在下一节中说明。

脚本已制定的编码选择(不要撤消它们)

选择原因
apad=pad_dur=PAD + -t seg_dur精确的尾部填充;简单的-shortest会剪切语音后的停顿
yuv420p + 偶数尺寸否则无法在QuickTime/WhatsApp/浏览器中播放
-tune stillimage, CRF 20静态幻灯片压缩良好,不会出现文本闪烁
concat demuxer + -c copy无损连接相同编码的片段
-movflags +faststart视频在完成下载前即可开始流式传输

验证——在帧证明叠加层正确之前,假定它已损坏

脚本断言最终时长 ≈ 各个片段时长之和。叠加层需要肉眼检查:

ffmpeg -y -ss T1 -i out.mp4 -frames:v 1 a.jpg   # 某张幻灯片的早期
ffmpeg -y -ss T2 -i out.mp4 -frames:v 1 b.jpg   # 同一张幻灯片的后期

从脚本打印的每页时长中选取T1/T2(对它们进行累计求和以获得幻灯片边界;记住它们已包含停顿时间)。读取两帧图像。在b.jpg中进度条必须明显更满,倒计时更低——并且应该在每张新幻灯片时重置。在早期时间戳出现完整的进度条 = 静态进度条错误——正是这个检查最初发现了它。你无法无头验证音频——请说明这一点;永远不要声称你听到了。

突出显示与旁白同步的元素

说出“左边……中间……右边……”的旁白听起来像音频描述轨道(参见自动演示幻灯片)。解决方法是去掉口头指示,而是突出显示元素——但前提是时间来自音频。

音频是主导;动画跟随它。 永远不要为了击中某个标记而编写旁白,也永远不要估计短语何时出现。正常生成剪辑,然后使用ElevenLabs强行对齐(POST /v1/forced-alignment,音频 + 转录文本 → 每个字符的start/end;使用 elevenlabs-tts 的 scripts/align_narration.py 运行它)来读回每个事物实际被说出的时间,并从那里推导出时间窗口。稍后重新录制一个剪辑并重新对齐会自动修正。

  • 锚定脚本中的位置,而不是关键词。 一个孤立的关键词在出现两次时就会失效(“标签”在一张幻灯片上出现了四次)。标记旁白转向每个元素的位置,使用从脚本中复制的一个短短语,按说话顺序,并且仅在之前匹配之后搜索每个短语——这正是 make_highlights.py 所做的;脚本中较早出现的重复单词无法捕获它。(保留到连接脚本的每元素片段偏移量也同样适用。)
  • 在对齐之前去除音频标签——所有标签。 [confident] 是表演出来的,而非说出来的;将其发送给对齐器,它会搜索音频中的单词并拖拽所有后续偏移量。Eleven v4标签是自由形式的([Pause, dry amusement]),因此去除方括号中的所有内容,而不仅仅是字母和空格(elevenlabs-tts 的对齐器会这样做)。根据相同的去除字符串计算偏移量。
  • 检查返回的字符数是否等于文本长度,并观察loss(在健康的17个剪辑英语演示中为0.04-0.10,在健康的26个剪辑希伯来语演示中为0.59-0.77;演示中的明显异常值是信号,而非绝对阈值)。不匹配意味着该幻灯片上的每个窗口都是错误的。

使用 scripts/make_highlights.py 构建 --highlights JSON,基于三个输入:标记(每张幻灯片,每个区域包含旁白转向该元素时的短语)、框(每张幻灯片,区域 → [x, y, w, h])和 alignment/ 文件:

python3 <this-skill-dir>/scripts/make_highlights.py markers.json boxes.json narration/alignment/ hl.json \
  --size 1920x1080 [--inches 13.333]      # --inches: 框以幻灯片英寸为单位,页面宽度如此
# markers.json: {

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

shaharsha/generating-video-clips

Use when turning a still image, illustration, comic panel or character art into a short animated clip (image-to-video), choosing a video model or provider (Gemini Omni, Veo, Seedance, Kling, ElevenLabs Flows) or hitting their API gates, lip-syncing a character's mouth to existing dialogue audio (Sync, fal.ai, active speaker), or fixing a generated clip with artifacts - duplicated props, a door or a second figure appearing, hands through bodies, glowing eyes, a gesture repeated at the start,...

shaharsha/image-generation

Generate logos, icons, UI mockups, hero images, product shots, and other design assets using OpenAI GPT Image 2.5 (Sunburst, Flare) or Google Gemini (Nano Banana 2, Nano Banana Pro, Nano Banana 2 Lite). Use whenever the user asks to create, design, generate, mock up, render, or illustrate a visual asset, needs a transparent PNG, or asks for image prompt engineering. Picks the right model and quality for the job, writes a model-specific prompt following the official prompting rules of each...

shaharsha/producing-animated-episodes

Use when making a narrated or voiced animated video with recurring characters - an anime, cartoon or comic episode, a short for a group chat, a story video about friends, family, a class or a team - from photos, a chat export, a story idea or a script, in any visual style, Hebrew/RTL or English. Also use when such a video has subtitles or cards too fast to read, music stopping early, the ending cut off, lines buried under music, mouths out of sync, a gesture before its line, or one face...

相关技能