Communitygithub.com

LiliLIN0324/imgseq-cover-video-skill

Build an mp4 cover/teaser video from a folder of numerically named stills (01.jpg, 02.jpg, ... up to 200 and beyond). Use when the user asks to turn a folder of numbered images into a video, make an image-flip reel or slideshow, add soft crossfade transitions between images, or export a "cover" video from jpgs in a folder — ignoring non-numbered images such as logo.jpg, discord.jpg or cover.mp4.

What is imgseq-cover-video-skill?

imgseq-cover-video-skill is a Claude Code agent skill that build an mp4 cover/teaser video from a folder of numerically named stills (01.jpg, 02.jpg, ... up to 200 and beyond). Use when the user asks to turn a folder of numbered images into a video, make an image-flip reel or slideshow, add soft crossfade transitions between images, or export a "cover" video from jpgs in a folder — ignoring non-numbered images such as logo.jpg, discord.jpg or cover.mp4.

Works with~Claude Code~Codex CLI~Cursor
npx skills add LiliLIN0324/imgseq-cover-video-skill

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

叠帧 · Numbered Stills → Cover Video

Overview

Scan a folder, keep only files whose stem is purely a number (01.jpg, 200.jpg), play them in numeric order, and encode an H.264 mp4. Between images you either hard cut (--transition none) or crossfade (--transition fade, the default). Non-numbered images (logo, discord, …) and existing videos are ignored.

Procedure

  1. Inspect the folder first. List files and probe one image's dimensions with ffprobe. Note how many numbered stills exist and whether aspects are mixed.
  2. Back up any existing output. If <folder>/cover.mp4 already exists, rename it to cover.prev-YYYYMMDD.mp4 before rendering. Never silently overwrite the user's earlier render.
  3. Render with the bundled script:
python "<skill-dir>/scripts/imgseq2mp4.py" --dir "<folder>" --out cover.mp4

Defaults: 0.8 s per image with a 0.2 s fade crossfade, 30 fps, 1920x1080, letterbox pad, CRF 18, no audio, +faststart.

  1. Verify, don't assume. Probe duration/frames, confirm no black frames, and extract the last frame to compare against the last source image:
ffprobe -v error -show_entries format=duration -show_entries stream=nb_frames -of default=nw=1 cover.mp4
ffmpeg -i cover.mp4 -vf blackdetect=d=0.05:pix_th=0.10 -an -f null -
ffmpeg -y -sseof -0.05 -i cover.mp4 -frames:v 1 frame_last.png

When transitions are on, also grab a frame inside the first transition window and check it is a blend (closer to image 2 than a hard cut would be, but still measurably different from both sources).

  1. Report image count, ignored images, total duration, output path and size.

Pace

Total length = n × seconds − (n−1) × transition_seconds. Each image's on-screen step is seconds − transition_seconds; that step, not --seconds, is what the viewer feels.

Step per imageCommand20 imgs44 imgs200 imgs
0.35 s--seconds 0.5 --transition-seconds 0.15~8 s~18 s~75 s
0.45 s--seconds 0.65 --transition-seconds 0.2~9 s~22 s~95 s
0.60 s (default)--seconds 0.8 --transition-seconds 0.2~12 s~27 s~120 s
0.80 s--seconds 1.0 --transition-seconds 0.2~16 s~34 s~150 s

Hard cuts use --transition none --seconds <step>.

Move one row at a time when the user asks for faster or slower. Keep --transition-seconds at 0.15–0.2; longer starts to read as a slideshow rather than a crossfade.

Rules

  • Crossfade only. Do not use slide, wipe, or other push transitions — they were rejected as a direction.
  • Select with regex ^\d+\.(jpe?g|png|webp)$ — filename must be only digits. This is what excludes logo.jpg, discord.jpg, cover.jpg.
  • Sort by integer value, not lexicographically (2.jpg before 10.jpg, 99.jpg before 100.jpg). Gaps in numbering are fine; missing numbers are not errors.
  • Use --fit pad (default) so the whole frame stays visible. Only use --fit fill when the user explicitly wants edge-to-edge cropping.
  • Output is silent. Do not add music unless the user supplies a track.
  • Keep colors untouched. JPEGs are full-range; if you encode them as untagged full-range H.264, most players decode as limited and the result looks brighter and washed out (+10 levels). The script converts to limited-range bt709 and tags it — do not "simplify" that chain away.
  • The script refuses to overwrite --out without --force; resolve by backing up, not by forcing blindly.

Machine notes

  • ffmpeg is not on PATH. The script prefers a real ffmpeg.exe (~/.dsh/tools/ffmpeg/**/ffmpeg.exe, then copies under C:/ or D:/ffmpeg) before any .cmd shim, because the shim routes through cmd.exe and caps the command line at ~8k chars. Override with FFMPEG=/path/to/ffmpeg.
  • This build lacks -filter_complex_script; pass the xfade graph inline via -filter_complex (the script already does).
  • Transitions are rendered in groups of --chunk (default 6) and then merged, so at most 6 decoders are open at once. A single 44-input xfade pass exhausted RAM on a 16 GB machine (~2.4 GB free) and failed with Cannot allocate memory; do not raise --chunk above ~8 here. Grouping recurses, so 200 images merge in three stages without OOM.
  • Intermediate stage files are encoded CRF 14 / veryfast and discarded; only the final pass uses --crf / medium.
  • Intermediate frames need a writable directory. tempfile.mkdtemp() results can be inaccessible under restricted sandboxes (observed on Windows + DSH: ffmpeg got Permission denied and os.listdir raised WinError 5). The script therefore creates a plain directory, proves it writable, and falls back through $IMGSEQ_WORKDIR → system temp → <out-dir>/.imgseq-work.

Resources

  • scripts/imgseq2mp4.py — selection, numeric sort, hard-cut or crossfade render, verification-friendly summary.

Related Skills