fountain-fm/fountain-skills

fountain-clip-producer

Render a clip post into a finished, platform-ready video with framing, captions, and overlays.

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Overview

This skill turns the clip that a post carries into a video file. It decides nothing about the moment or the span, because the caller settles those before it runs. Module media cuts the landscape master, and every module after it works from that one file. The rest shape the picture, put the words and the layers on it, and gate the delivery on one report.

Input

  • The SocialPostMediaSource of a SocialPost, which names the file and the span.

The file can carry no video, which most of a podcast catalogue does not. Empty ids and a YouTube video id both name sources that Fountain does not hold as episodes.

  • Or an external source for a raw local video, which carries the media path and span for this session.

Its post carries no source, because a raw local path is not a valid media URL.

  • The word timings of that span, which this skill makes from the clip's own audio.

Module shots wants the speaker of each word too, which nothing supplies.

  • A delivery tier, which the words of the request imply.
  • Or a queue run: module queue reads the drafts that wait for media, and derives the inputs above.

Optional:

  • A target shape, which is vertical, square, or landscape.
  • The name of a caption preset, or the text of an overlay.

Output

  • The finished work: a landscape master, and one export for each shape that the request asks for.

The master is finished work and not a working, because the user keeps it and cuts from it again.

  • A SocialPostUpload on content.uploads of the post, unless the user asked you not to attach it.
  • Workings: a clip manifest, a crop plan, a caption plan, an overlay plan, a QA report, and a removal

report when module trims cut the clip.

Housekeeping

You MUST read HOUSEKEEPING.md if you haven't already.

Requirements

  • Fountain API.
  • Python 3.11 or later.
  • OpenCV 4.8 or later, importable from that same Python, for the face detection of module framing.

Its model, and the fonts that the presets name, ship in assets, so no machine installs either.

  • ffmpeg and ffprobe, built with libass, drawtext, fontconfig and whisper, or no caption can be burned

and no word can be timed. A stock build often carries none of them, and on macOS the Homebrew ffmpeg-full formula is the one that does.

  • A whisper.cpp model file, which the whisper filter takes the path of and does nothing without.

ggml-base.en.bin in ~/.cache/whisper is the one this skill looks for first, and it is 141 MB, so the machine installs it one time and the skill does not ship it.

  • ImageMagick, to measure the width of caption text.
  • yt-dlp, for a source that ffmpeg cannot seek directly.

Keep it current: YouTube changes what a client must send, and a build a few weeks old answers 403 on every download while the captions still come through.

  • A web search tool, and a way to read a page, for the reference sources of module brand.
  • Skill fountain-onboarding, which installs a tool that module preflight finds missing.

Process

  1. Read the delivery tier from the request.

Do the least work that the tier asks for.

  1. Run module preflight to check the machine before the first render.

One report serves every clip of a run, because the machine does not change between them.

  1. Run module media to cut the landscape master from media, between ts_start and ts_end.

Cut all the clips of the run together, then transcribe them all together. No clip waits for another one. Then transcribe the master with whisper to get the word timings of the clip, and rebase them so the first word starts at zero. Use the binary and the model that module preflight names, with max_len=1, and put model= last in the filter string or the option after it is swallowed. That asks for one token for each segment, and each token is usually a whole word. A token is a continuation of the word before it only when it carries no leading space and the word before it does not end a sentence: join those two, and treat every other token as its own word. Do not join on timing, because whisper butts one word's start against the last one's end. A run of punctuation is its own token: attach it to the word before it rather than drop it, because module captions reads it to find a sentence end. These are measured from the audio being cut, so they are the only timings that describe this file.

  1. Run module trims to survey the pauses and the filler, and report what it found.

Cut only when the user asks, because the cut moves every time after it.

  1. Run module framing to crop the master to each shape that the request asks for.

Run module shots with it when one shot holds two people and the crop must follow who speaks. Skip both for a source with no video, which has no picture to crop and no face to follow.

  1. Run module brand to load the look of the show, for a clean final or a publish final.
  2. Send the user to the clip styling page when the request names no caption style and module

brand holds none. The choice it records comes back as a brand kit. Do not hold the run for an answer: produce with the default and say what it was.

  1. Run module captions on every portrait export, and on another shape when the request asks for it.

Run module fonts with it.

  1. Run module overlays when the request asks for a layer, and always for a source with no video.

There the overlay is not polish: an audiogram package is the whole picture, and without one the clip is captions on an empty frame. Load the artwork of the show from info.image and give it to the package, which every one of them needs.

  1. Run module qa as the blocking gate, and deliver nothing until it reports a pass.
  2. Confirm on the render, and never on the transcript, that the quote the copy uses is in the clip and

that the person it credits is the one who says it. The caller wrote both unseen: the transcript carries sentences and names no speaker. Take the speaker from the camera and from a cutaway that shows a closed mouth. Repair what is wrong with the Social API, and say what you changed: move ts_start or ts_end when the clip opens or closes inside a word, or when the quote sits outside the span, then write the new words into transcript so the two agree; correct the label, the title, the text and the context when the credit is wrong. A moved edge makes the render stale, so go back to step 3 with the new span: the words, the captions and the gate all describe the old cut, and only the gate can say the new one is finished. Move an edge only to repair what you can prove, or to make a change the user asked for, and never to improve the clip - choosing the moment is the caller's job.

  1. Attach the video with the Uploads API and the Social API, unless the user asked you not to.
  2. Present each finished clip on the clip card of skill fountain-clip-finder, with one added

line saying the render result and where the video is attached.

Additional notes

A run with more than one clip does the same work on each clip. Move all the clips through one stage, then move them all through the next stage. Most of the time of a render goes between the actions, and not inside them. A render of one clip spends a third of its time in the tools.

There are three delivery tiers, each adding to the one before, and the request implies which one. The user names the work they want, not the tier, so read it from their words:

  • A rough cut is the landscape master alone, with no crop, no captions and no gate.

Read it from words about checking a span rather than making a clip.

  • A clean final is publishable, and a portrait export carries captions, because it is watched muted.

Read it from "produce this clip", when the request names neither captions nor packaging. A clean final of a source with no video carries its audiogram package too, for the same reason that a portrait export carries captions: without it there is nothing to watch.

  • A publish final adds the overlays and the packaging, and the request names one of them.

Ask when the words fit none of the three, and you MUST NOT raise the tier on your own: polish is requested work. Captions on a portrait export are not a raise, and a square or a landscape export still waits to be asked.

The word timings come from the clip, and never from the episode transcript. That transcript carries sentences and no words, and it is the caller's evidence for the span rather than this skill's evidence for a caption. The words themselves are a different matter: whisper mishears a name or a number that the transcript has right, so correct the wording against the transcript and keep the timings whisper made.

Always cut from the tallest rendition: a 9:16 crop keeps the whole height and about a third of the width, so that height is the real resolution of the clip, and module qa fails a big upscale. Deliver the shape the tallest rendition can hold when it cannot reach the target, e.g. 720x1280 from a 720p master, and say so - a true smaller resolution passes the gate and an upscale does not.

A span that reads wrong but that you cannot prove wrong is a report and not a repair, and a letterbox is requested work too.

To change a clip this skill already made, read the manifest and the QA report first, reuse what is still correct, and write each new output under a new name. Touch only the output of the module that changes, and a caption change MUST NOT force a new crop.

A post does not have to be approved before this skill runs, and rendering one approves nothing. Never put an API key, a token, or a cookie into a command, a manifest, or a report.

These steps make one clip, and several clips of one run are independent. Module queue therefore gives each clip its own worker and runs the workers at the same time, each in its own output folder, and a run is finished when the last clip is. They do not finish three times faster, because ffmpeg already uses every core of the machine.

The purpose of this skill is a good clip, and not a full set of completed steps. Readability, framing, and sync matter more than procedure.

from this repository

More skills

All skills