agentsclimarketplace

Deck to video

Skill shaharsha/claude-skills/skills/deck-to-video

Use when turning a slide deck plus per-slide narration audio into an mp4 video — a self-playing/self-presenting deck video, "slides to video", "presentation to mp4", a shareable WhatsApp/Slack/Drive video of a narrated deck, or when a slide video needs a per-slide progress bar / countdown / slide counter. Also use when an ffmpeg-drawn progress bar renders full/static from the first frame, or ffmpeg errors "No such filter: 'drawtext'".From its SKILL.md

Install
npx -y skills add shaharsha/claude-skills --skill deck-to-video

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

6.4 KB, as published. Nobody here has run it

Deck to Video (slides + narration → self-playing mp4)

Overview

Turn N slide images (or a PDF) + N per-slide audio clips into one mp4: each slide holds for exactly its narration's length plus a short pause, then hard-cuts to the next. Overlays: a filling progress bar + seconds-remaining countdown (bottom-right) and an "N / M" slide counter (top-right).

The one command (the script ships in this skill's own scripts/ directory — call it by its full path there):

python3 <this-skill-dir>/scripts/build_video.py SLIDES AUDIO_DIR out.mp4
# SLIDES = slides.pdf (rasterized at 200dpi) OR a directory of per-slide images
#          (numeric-aware ordering: s2 before s10; zero-padded names still recommended)
# AUDIO_DIR = slide01.mp3, slide02.mp3, … — count MUST equal slide count
# options: --size 1920x1080 · --pad 0.7 · --no-bar · --no-counter · --bar-color HEX · --crf 20

On success it prints one line per slide — that per-slide length already includes the pad — and ends with duration matches segment sum (it exits non-zero otherwise).

Getting the inputs right

  1. Slides: if starting from a pptx, rasterize from real PowerPoint, not LibreOffice. Export the pptx to PDF via actual PowerPoint (the office-render skill does this on macOS), then hand the PDF to the script — LibreOffice substitutes fonts and re-flows layouts, so the video would not match what the deck's author sees. 200 dpi keeps dense slide text crisp at 1080p. (Already have per-slide images? Use them directly — skip this step.)
  2. Audio: one clip per slide, named slideNN.mp3. If the deck was narrated with the narrating-pptx skill, its narration/ directory is already in this exact shape — reuse it so the pptx and the video sound identical.
  3. If narration pace was adjusted (e.g. ffmpeg -filter:a atempo=1.1), build the video from the adjusted files.

Why the overlays are baked with PIL, not drawn by ffmpeg (paid for in blood)

  • drawbox with a t-based width expression renders a FULL bar from frame 0 in several ffmpeg builds — the expression is evaluated once at init, not per frame. The failure is silent: the video encodes fine and the bar simply never moves. drawtext expressions do update per frame in the same builds, which makes the broken bar easy to miss next to a working countdown.
  • Homebrew's ffmpeg bottles (2026+) are built without libfreetype — No such filter: 'drawtext' — and the keg versions (ffmpeg@7, ffmpeg@8) are the same. Don't spend an install cycle discovering this; if a full build is needed for anything else, uv tool install static-ffmpeg ships one (the script auto-falls-back to it).

So the script bakes one frame per second with PIL (bar fill, countdown, counter) and feeds ffmpeg a 1 fps image sequence encoded at -r 30. Deterministic, font-controlled, and cheaper to encode than filtering a 30 fps stream — the slide is static anyway. Second-granularity updates read naturally for a countdown.

Encoding choices the script already makes (don't undo them)

ChoiceWhy
apad=pad_dur=PAD + -t seg_durexact tail padding; naive -shortest cuts the pause after speech
yuv420p + even dimensionsanything else fails to play in QuickTime/WhatsApp/browsers
-tune stillimage, CRF 20static slides compress well without text shimmer
concat demuxer + -c copylossless join of identically-encoded segments
-movflags +faststartvideo streams before it finishes downloading

Validate — assume the overlay is broken until frames prove otherwise

The script asserts final duration ≈ sum of segments. The overlay needs eyes:

ffmpeg -y -ss T1 -i out.mp4 -frames:v 1 a.jpg   # early in some slide
ffmpeg -y -ss T2 -i out.mp4 -frames:v 1 b.jpg   # late in the same slide

Pick T1/T2 from the script's printed per-slide lengths (cumulative-sum them for slide boundaries; remember they include the pad). Read both images. The bar must be visibly fuller in b.jpg and the countdown lower — and it should reset at each new slide. A full-looking bar at an early timestamp = the static-bar bug — this exact check is what caught it originally. You cannot verify audio headlessly — say so; never claim you heard it.

Common mistakes

MistakeConsequenceFix
ffmpeg drawbox animated by tbar full from frame 0, silentlyPIL-baked frames (the script)
Assuming drawtext existsNo such filter on Homebrew buildsscript falls back to static-ffmpeg / PIL text
LibreOffice rasterizationfonts/layout differ from the real deckreal PowerPoint PDF via office-render
-shortest for segment lengtheats the pause after narrationapad + explicit -t
Audio count ≠ slide countdesynced or missing slidesscript hard-fails; fix inputs, don't skip slides
Judging output by "it encoded fine"static bar / wrong slide slips throughextract early/late frames and look
Deck prints its own slide numbersnumber appears twice (deck footer + counter)render the video from a numberless deck variant, or pass --no-counter
ffmpeg hangs at 0% CPU after writing a segmentbuild stalls forever; a killed attempt leaves an INVALID partial filethe script now times out each segment (300s) and retries once, deleting the partial first
Piping the build through | tail inside an && chainpipeline exit = tail's 0 — a failed build "succeeds" and stale artifacts get deliveredrun the script unpiped (or set -o pipefail); keep delivery/sync in a separate step gated on verified output

Caveats

  • Hard cuts between slides by design — crossfades break the lossless -c copy concat and add little.
  • ~13–17 min of 1080p mostly-static slides ≈ 40–50 MB.
  • The video is a third artifact beside the clean pptx and the narrated pptx — it forces the narration's pace on the viewer, so keep the pptx variants for people who prefer reading.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.