SuperAPI AI Directory

create-end-card-ffmpeg

Design & creation

View repository

Updated 10-15 21:54

About this skill

create-end-card-ffmpeg

Purpose

Render a clean end card with ffmpeg primitives only. No browser, no Node, no AI. Takes a backdrop image + up to N text lines + an optional logo and produces an mp4 with sequential fade-in animation.

The opinionated alternatives — create-end-card-html, create-end-card-hyperframes, create-end-card-pil — remain available for end cards that need richer motion graphics (kinetic typography, spring physics, particle effects, image transitions). Use this atom when you want a deterministic, fast, zero-dependency build for a static-with-fades end card.

Inputs

Field Required Notes
backdrop yes Path to a backdrop image (jpg/png). Will be scaled to width×height. Often the last frame of the prior scene.
text_lines no List of { text, fade_in_start_s, fade_in_duration_s, font_size, y_offset_from_bottom } entries. Sequenced for stagger.
logo_path no Path to a logo image (PNG, ideally pre-recolored white/black/etc.). If SVG, the atom converts via cairosvg first.
logo_options no { width, fade_in_start_s, fade_in_duration_s, position }
duration_s yes Total output duration (seconds)
width no, default 720 Output width
height no, default 1280 Output height (9:16 = 720×1280, 1:1 = 1080×1080, etc.)
fps no, default 24 Output frame rate
crf no, default 18 x264 quality (lower = better, 18 is visually lossless-ish)
backdrop_dim no, default 0 Brightness offset to apply to backdrop (-0.15 darkens 15%; useful when text needs contrast)
output_path yes Where to write the mp4

Workflow

  1. Prepare the logo. If logo_path is .svg, run cairosvg to convert to PNG at logo_options.width (default 240px wide). If the logo needs a color shift (e.g. brand mark must be white), pre-process the alpha channel via PIL to swap RGB to the target color while preserving alpha.
  2. Build the ffmpeg filter graph. Single command, all overlays in one pass:
    • Scale backdrop to target dimensions + apply optional eq=brightness=<dim>
    • For each text_lines entry, add a drawtext filter with an alpha expression that fades in from fade_in_start_s over fade_in_duration_s
    • For the logo, add an overlay with the logo stream piped through a fade=t=in:st=<start>:d=<dur>:alpha=1 filter
  3. Run ffmpeg with -loop 1 -t <duration_s> on each input.
  4. Validate the output is the correct duration + has video stream.

Alpha-fade expression cheat-sheet

The drawtext filter doesn't support fade= directly — it uses an alpha expression that you build manually:

alpha='if(lt(t, <start>), 0, if(lt(t, <start> + <dur>), (t - <start>) / <dur>, 1))'

This: holds invisible until start, ramps 0→1 over dur seconds, holds at 1 afterward.

For a fade-out at the end:

alpha='if(lt(t, <fade_out_start>), 1, if(lt(t, <fade_out_start> + <fade_out_dur>), 1 - (t - <fade_out_start>) / <fade_out_dur>, 0))'

For a fade-in + hold + fade-out:

alpha='if(lt(t, <start>), 0, if(lt(t, <start> + <in_dur>), (t - <start>) / <in_dur>, if(lt(t, <out_start>), 1, if(lt(t, <out_start> + <out_dur>), 1 - (t - <out_start>) / <out_dur>, 0))))'

Output

  • <output_path> — the mp4 end card
  • <output_path>.manifest.json — recording inputs + computed filter + duration

Quality Checks

  • Output duration matches duration_s ± 0.05s
  • All text lines fade in at the correct times (verify with ffmpeg -i out.mp4 -ss <line_start> -frames:v 1 and check alpha)
  • Logo position is within the safe area (don't let it crop off screen)
  • No transparency artifacts at fade boundaries

Failure Modes

  • SVG conversion fails. cairosvg requires Cairo + libffi installed. On macOS: brew install cairo libffi. On Linux: apt install libcairo2-dev libffi-dev. If install fails, fall back to manual SVG → PNG conversion via ImageMagick (convert -background none -density 300 logo.svg -resize 240x logo.png).
  • drawtext font not found. drawtext=fontfile=<path> requires an absolute path to a TTF/OTF file. On macOS use /System/Library/Fonts/HelveticaNeue.ttc. On Linux use one of /usr/share/fonts/truetype/dejavu/*.ttf. If the font has spaces or special chars, escape with backslashes.
  • Alpha expression syntax error. ffmpeg's filtergraph is unforgiving with nested if(...) — quote the entire alpha= value and escape internal commas/colons if needed. Easier: build the expression as a Python f-string and pass via -filter_complex_script if it gets long.
  • Logo PNG has wrong color. SVG → PNG via cairosvg preserves the SVG's fill color. If you need a white-recolored logo, pre-process with PIL: load PNG → convert to RGBA → replace all non-transparent pixels with white preserving alpha.

Example invocation

from scripts.create_end_card import create_end_card

create_end_card(
    backdrop='generated/callback-frames/end-card-bg.png',
    text_lines=[
        {'text': 'THE WORLD IS', 'fade_in_start_s': 0.0, 'fade_in_duration_s': 0.75,
         'font_size': 72, 'y_offset_from_bottom': 360},
        {'text': 'YOUR COURT.', 'fade_in_start_s': 0.9, 'fade_in_duration_s': 0.7,
         'font_size': 72, 'y_offset_from_bottom': 270},
    ],
    logo_path='nike/brand-assets/logos/jumpman.svg',
    logo_options={'width': 240, 'fade_in_start_s': 2.0, 'fade_in_duration_s': 0.3,
                  'position': 'center', 'recolor': '#ffffff'},
    duration_s=4.0,
    width=720, height=1280,
    backdrop_dim=-0.15,
    output_path='clips/end-card.mp4',
)

When NOT to use this

  • The end card has motion graphics (animated logos, kinetic type, particle effects). Use create-end-card-hyperframes.
  • The end card has interactive elements or web-style layout (links, CTAs, complex grids). Use create-end-card-html.
  • The end card needs gradient sampling from a reference image. Use create-end-card-pil.

This atom is for the case where you want a clean fade-in end card produced deterministically in ~5 seconds with no AI or browser dependencies.