Update to Fountain
Snapshot Oct 28, 2026 · 18:05 UTC · version 1.29.0
Collection source: downloaded plugin package.
Instructions updated for fountain-clip-producer
Full description — Now says: Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there.
Observed in instructions or declared skills. Runtime behavior has not been tested.
Up to 10 plugins free · no card required.
All fields and original values.
Full description
Oct 10, 2026 · 12:04 UTC
Render a clip post into a finished, platform-ready video with framing, captions, and overlays.
Oct 28, 2026 · 18:05 UTC
Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there.
Skill instructions
Oct 10, 2026 · 12:04 UTC
post into a finished, platform-ready video with framing, captions, and overlays. This skill turns the clip that a post carries into a video file. It does not choose the moment or the span. The caller chooses both before this skill runs. ...
Oct 28, 2026 · 18:05 UTC
on this machine, only when the user asks. To change a clip on Fountain, render it again there. This skill renders the video of a clip post on this machine, when the user asks for a local render. Fountain renders each clip automatically, ...
Supporting files
Oct 10, 2026 · 12:04 UTC
[{"relative_path":"HOUSEKEEPING.md","size_in_bytes":9383},{"relative_path":"assets/fonts/anton-regular.ttf","size_in_bytes":170812},{"relative_path":"assets/fonts/archivo-black-regular.ttf","size_in_bytes":90988},{"relative_path":"assets...
Oct 28, 2026 · 18:05 UTC
[{"relative_path":"HOUSEKEEPING.md","size_in_bytes":9268},{"relative_path":"modules/brand/MODULE.md","size_in_bytes":6947},{"relative_path":"modules/brand/scripts/derive-palette.py","size_in_bytes":8736},{"relative_path":"modules/caption...
Compare saved observations
Download comparison JSONSKILL.md text changes
− removed · + added
--- before +++ after @@ -1,15 +1,13 @@ --- name: fountain-clip-producer -description: Render a clip post into a finished, platform-ready video with framing, captions, and overlays. +description: Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there. --- ## Overview -This skill turns the clip that a post carries into a video file. -It does not choose the moment or the span. -The caller chooses both before this skill runs. -Module **media** cuts the landscape master. -Every module after it works from that one file. +This skill renders the video of a clip post on this machine, when the user asks for a local render. +Fountain renders each clip automatically, and that render is the first choice, even when it misses part of the request. +Module **media** cuts the landscape master, and every module after it works from that file. The other modules crop the picture and put the captions and the layers on it. The skill delivers nothing until one report passes. @@ -25,8 +23,6 @@ Module **shots** also needs the speaker of each word. No input supplies it. - 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 from them. Optional: @@ -39,8 +35,8 @@ The master is finished work, and not one of the workings. 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. +- Workings: a clip manifest, a crop plan, a caption plan, an overlay plan, a QA report, a removal + report when module **trims** cut the clip, and a pace plan when module **pace** sped it up. ## Housekeeping @@ -51,22 +47,20 @@ - 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. + Module **preflight** downloads its model, the fonts that the presets name, and the overlay placeholders. - ffmpeg and ffprobe, built with libass, drawtext, fontconfig and whisper. Without them, the skill cannot burn a caption or time a word. A stock build often has none of them. On macOS, the Homebrew `ffmpeg-full` formula has them. - A whisper.cpp model file. The whisper filter takes the path of this file, and does nothing without it. - This skill looks first for `ggml-base.en.bin` in `~/.cache/whisper`. - That file is 141 MB, so the skill does not ship it. - The machine installs it one time. + That file is 141 MB, so the machine installs it one time. + Module **preflight** finds the file, and prints the line that installs it. - ImageMagick, to measure the width of caption text. - yt-dlp, for a source that ffmpeg cannot seek directly. Keep it current, because YouTube changes what a client must send. A build a few weeks old gets a 403 error on every download, while the captions still download. - 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 @@ -96,33 +90,34 @@ file. 4. Run module **trims** to survey the pauses and the filler, and report what it found. Cut only when the user asks, because a cut moves every time stamp after it. -5. Run module **framing** to crop the master to each shape that the request asks for. +5. Run module **pace** when the request asks for a faster clip, or the Brand section records a pace. +6. 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. Such a source has no picture to crop and no face to follow. -6. Run module **brand** to load the look of the show, for a clean final or a publish final. -7. Send the user to the clip styling page when the request names no caption style and module +7. Run module **brand** to load the look of the show, for a clean final or a publish final. +8. Send the user to the clip rendering page when the request names no caption style and module **brand** holds none. The choice that the page records comes back as a brand kit. Do not stop the run to wait for an answer. Produce the clip with the default style, and tell the user which style that was. -8. Run module **captions** on every portrait export, and on another shape when the request asks for it. +9. Run module **captions** on every portrait export, and on another shape when the request asks for it. Run module **fonts** with it. -9. Run module **overlays** when the request asks for a layer, and always for a source with no video. - For a source with no video, the overlay is not polish. - There, an audiogram package is the whole picture. - 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. - Every audiogram package needs the artwork. -10. Run module **qa** as the blocking gate. +10. Run module **overlays** when the request asks for a layer, and always for a source with no video. + For a source with no video, the overlay is not polish. + There, an audiogram package is the whole picture. + 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. + Every audiogram package needs the artwork. +11. Run module **qa** as the blocking gate. Deliver nothing until it reports a pass. -11. Use the render, and never the transcript, to confirm two things. +12. Use the render, and never the transcript, to confirm two things. The quote that the copy uses is in the clip. The person that the copy credits is the person who says the quote. The caller wrote the quote and the credit without seeing the clip. The transcript holds sentences and names no speaker. Find the speaker from the camera, and from a cutaway that shows a closed mouth. - Repair what is wrong with the Social API, and tell the user what you changed. + Repair what is wrong, and tell the user what you changed. Move `ts_start` or `ts_end` when the clip opens or closes inside a word, or when the quote is outside the span. Then write the new words into `transcript`, so that the two agree. @@ -132,9 +127,10 @@ Only the gate can say that the new cut is finished. Move an edge only to repair a fault that you can prove, or to make a change that the user asked for. Never move an edge to improve the clip, because choosing the moment is the caller's job. -12. Attach the video with the Uploads API and the Social API, unless the user asked you not to. -13. 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. +13. Upload the video with the Uploads API, unless the user asked you not to attach it. + Send the repairs of step 12 and the `upload_id` in one Social API update. + A new `source` and its `upload_id` MUST go in the same update. +14. Give the user each finished clip, with its `post_url`, the render result, and where the video is attached. ## Additional notes @@ -188,12 +184,11 @@ 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 the clips of one run are independent of each other. -Module **queue** therefore shares the clips of a long queue between at most three workers. -Each worker has its own output folder. -A run is finished when the last clip is finished. -Three workers do not finish three times faster, because ffmpeg already uses every core of the machine. -A short queue uses one worker, because each new worker reads the skill again before it renders anything. +Keep the files of a run in `fountain/outputs` of the working folder, with one folder for each show. +In it, give each clip a folder named `{episode_number ?? YYYY-MM-DD}-{slugified-topic}`. +Keep only the finished work in the clip folder. +Put the rest in its `workings` folder, or in the `workings` folder of the show when clips share it. +These files are for this session only, so no later session reads them. 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.
Full technical diff · 3 changed fields
changed /description
"Render a clip post into a finished, platform-ready video with framing, captions, and overlays."
"Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there."
changed /included_files
[
{
"relative_path": "HOUSEKEEPING.md",
"size_in_bytes": 9383
},
{
"relative_path": "assets/fonts/anton-regular.ttf",
"size_in_bytes": 170812
},
{
"relative_path": "assets/fonts/archivo-black-regular.ttf",
"size_in_bytes": 90988
},
{
"relative_path": "assets/fonts/bebas-neue-regular.ttf",
"size_in_bytes": 61400
},
{
"relative_path": "assets/fonts/courier-prime-regular.ttf",
"size_in_bytes": 71188
},
{
"relative_path": "assets/fonts/dm-serif-display-regular.ttf",
"size_in_bytes": 76580
},
{
"relative_path": "assets/fonts/montserrat-black.ttf",
"size_in_bytes": 462844
},
{
"relative_path": "assets/fonts/montserrat-bold.ttf",
"size_in_bytes": 454864
},
{
"relative_path": "assets/fonts/montserrat-regular.ttf",
"size_in_bytes": 445928
},
{
"relative_path": "assets/fonts/permanent-marker-regular.ttf",
"size_in_bytes": 74632
},
{
"relative_path": "assets/fonts/poppins-bold.ttf",
"size_in_bytes": 155996
},
{
"relative_path": "assets/licences/apache-permanent-marker.txt",
"size_in_bytes": 11358
},
{
"relative_path": "assets/licences/mit-yunet.txt",
"size_in_bytes": 1085
},
{
"relative_path": "assets/licences/ofl-anton.txt",
"size_in_bytes": 4484
},
{
"relative_path": "assets/licences/ofl-archivo-black.txt",
"size_in_bytes": 4492
},
{
"relative_path": "assets/licences/ofl-bebas-neue.txt",
"size_in_bytes": 4337
},
{
"relative_path": "assets/licences/ofl-courier-prime.txt",
"size_in_bytes": 4403
},
{
"relative_path": "assets/licences/ofl-dm-serif-display.txt",
"size_in_bytes": 4605
},
{
"relative_path": "assets/licences/ofl-montserrat.txt",
"size_in_bytes": 4400
},
{
"relative_path": "assets/licences/ofl-poppins.txt",
"size_in_bytes": 4385
},
{
"relative_path": "assets/models/face-detection-yunet-2023mar.onnx",
"size_in_bytes": 232589
},
{
"relative_path": "modules/brand/MODULE.md",
"size_in_bytes": 6722
},
{
"relative_path": "modules/brand/scripts/derive-palette.py",
"size_in_bytes": 8629
},
{
"relative_path": "modules/captions/MODULE.md",
"size_in_bytes": 9858
},
{
"relative_path": "modules/captions/assets/bold-social.json",
"size_in_bytes": 624
},
{
"relative_path": "modules/captions/assets/bounce-in.json",
"size_in_bytes": 589
},
{
"relative_path": "modules/captions/assets/broadsheet.json",
"size_in_bytes": 579
},
{
"relative_path": "modules/captions/assets/current-word.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/captions/assets/glow-bounce.json",
"size_in_bytes": 756
},
{
"relative_path": "modules/captions/assets/glow-word.json",
"size_in_bytes": 662
},
{
"relative_path": "modules/captions/assets/hormozi.json",
"size_in_bytes": 825
},
{
"relative_path": "modules/captions/assets/impact-loud.json",
"size_in_bytes": 655
},
{
"relative_path": "modules/captions/assets/karaoke-fill.json",
"size_in_bytes": 603
},
{
"relative_path": "modules/captions/assets/marker.json",
"size_in_bytes": 593
},
{
"relative_path": "modules/captions/assets/minimal-light.json",
"size_in_bytes": 697
},
{
"relative_path": "modules/captions/assets/pill-karaoke.json",
"size_in_bytes": 705
},
{
"relative_path": "modules/captions/assets/stadium.json",
"size_in_bytes": 559
},
{
"relative_path": "modules/captions/assets/typewriter.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/captions/assets/wide-block.json",
"size_in_bytes": 574
},
{
"relative_path": "modules/captions/assets/word-pop.json",
"size_in_bytes": 669
},
{
"relative_path": "modules/captions/scripts/build-captions.py",
"size_in_bytes": 58040
},
{
"relative_path": "modules/fonts/MODULE.md",
"size_in_bytes": 3809
},
{
"relative_path": "modules/framing/MODULE.md",
"size_in_bytes": 5285
},
{
"relative_path": "modules/framing/scripts/extract-face-framing.py",
"size_in_bytes": 12813
},
{
"relative_path": "modules/framing/scripts/visual-person-qa.py",
"size_in_bytes": 5622
},
{
"relative_path": "modules/media/MODULE.md",
"size_in_bytes": 9538
},
{
"relative_path": "modules/media/scripts/build-time-map.py",
"size_in_bytes": 19853
},
{
"relative_path": "modules/media/scripts/verify-content-alignment.py",
"size_in_bytes": 6504
},
{
"relative_path": "modules/overlays/MODULE.md",
"size_in_bytes": 7227
},
{
"relative_path": "modules/overlays/assets/audiogram-brand.json",
"size_in_bytes": 1137
},
{
"relative_path": "modules/overlays/assets/audiogram-cover.json",
"size_in_bytes": 1088
},
{
"relative_path": "modules/overlays/assets/audiogram-headline.json",
"size_in_bytes": 1191
},
{
"relative_path": "modules/overlays/assets/audiogram-minimal.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/overlays/assets/guest-intro.json",
"size_in_bytes": 454
},
{
"relative_path": "modules/overlays/assets/handle-watermark.json",
"size_in_bytes": 486
},
{
"relative_path": "modules/overlays/assets/headline-bar.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/overlays/assets/hook-card.json",
"size_in_bytes": 735
},
{
"relative_path": "modules/overlays/assets/hook-title.json",
"size_in_bytes": 469
},
{
"relative_path": "modules/overlays/assets/launch-pack.json",
"size_in_bytes": 851
},
{
"relative_path": "modules/overlays/assets/pip-card.json",
"size_in_bytes": 375
},
{
"relative_path": "modules/overlays/assets/placeholder-logo.png",
"size_in_bytes": 28352
},
{
"relative_path": "modules/overlays/assets/placeholder-sponsor.png",
"size_in_bytes": 23782
},
{
"relative_path": "modules/overlays/assets/show-logo.json",
"size_in_bytes": 480
},
{
"relative_path": "modules/overlays/assets/sponsor-logo.json",
"size_in_bytes": 536
},
{
"relative_path": "modules/overlays/scripts/build-overlays.py",
"size_in_bytes": 35210
},
{
"relative_path": "modules/preflight/MODULE.md",
"size_in_bytes": 4360
},
{
"relative_path": "modules/preflight/scripts/render-preflight.py",
"size_in_bytes": 14647
},
{
"relative_path": "modules/qa/MODULE.md",
"size_in_bytes": 4252
},
{
"relative_path": "modules/qa/scripts/validate-clip.py",
"size_in_bytes": 12237
},
{
"relative_path": "modules/queue/MODULE.md",
"size_in_bytes": 5845
},
{
"relative_path": "modules/shots/MODULE.md",
"size_in_bytes": 4793
},
{
"relative_path": "modules/shots/scripts/plan-shots.py",
"size_in_bytes": 11253
},
{
"relative_path": "modules/trims/MODULE.md",
"size_in_bytes": 4053
},
{
"relative_path": "modules/trims/scripts/plan-trims.py",
"size_in_bytes": 11623
},
{
"relative_path": "scripts/align-word-timings.py",
"size_in_bytes": 6904
}
][
{
"relative_path": "HOUSEKEEPING.md",
"size_in_bytes": 9268
},
{
"relative_path": "modules/brand/MODULE.md",
"size_in_bytes": 6947
},
{
"relative_path": "modules/brand/scripts/derive-palette.py",
"size_in_bytes": 8736
},
{
"relative_path": "modules/captions/MODULE.md",
"size_in_bytes": 10057
},
{
"relative_path": "modules/captions/assets/bold-social.json",
"size_in_bytes": 624
},
{
"relative_path": "modules/captions/assets/bounce-in.json",
"size_in_bytes": 589
},
{
"relative_path": "modules/captions/assets/broadsheet.json",
"size_in_bytes": 579
},
{
"relative_path": "modules/captions/assets/current-word.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/captions/assets/glow-bounce.json",
"size_in_bytes": 756
},
{
"relative_path": "modules/captions/assets/glow-word.json",
"size_in_bytes": 662
},
{
"relative_path": "modules/captions/assets/hormozi.json",
"size_in_bytes": 825
},
{
"relative_path": "modules/captions/assets/impact-loud.json",
"size_in_bytes": 655
},
{
"relative_path": "modules/captions/assets/karaoke-fill.json",
"size_in_bytes": 603
},
{
"relative_path": "modules/captions/assets/marker.json",
"size_in_bytes": 593
},
{
"relative_path": "modules/captions/assets/minimal-light.json",
"size_in_bytes": 697
},
{
"relative_path": "modules/captions/assets/pill-karaoke.json",
"size_in_bytes": 661
},
{
"relative_path": "modules/captions/assets/stadium.json",
"size_in_bytes": 559
},
{
"relative_path": "modules/captions/assets/typewriter.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/captions/assets/wide-block.json",
"size_in_bytes": 574
},
{
"relative_path": "modules/captions/assets/word-pop.json",
"size_in_bytes": 669
},
{
"relative_path": "modules/captions/scripts/build-captions.py",
"size_in_bytes": 59047
},
{
"relative_path": "modules/fonts/MODULE.md",
"size_in_bytes": 3970
},
{
"relative_path": "modules/framing/MODULE.md",
"size_in_bytes": 5283
},
{
"relative_path": "modules/framing/scripts/extract-face-framing.py",
"size_in_bytes": 13082
},
{
"relative_path": "modules/framing/scripts/visual-person-qa.py",
"size_in_bytes": 5946
},
{
"relative_path": "modules/media/MODULE.md",
"size_in_bytes": 9538
},
{
"relative_path": "modules/media/scripts/build-time-map.py",
"size_in_bytes": 19853
},
{
"relative_path": "modules/media/scripts/verify-content-alignment.py",
"size_in_bytes": 6504
},
{
"relative_path": "modules/overlays/MODULE.md",
"size_in_bytes": 7277
},
{
"relative_path": "modules/overlays/assets/audiogram-brand.json",
"size_in_bytes": 1137
},
{
"relative_path": "modules/overlays/assets/audiogram-cover.json",
"size_in_bytes": 1088
},
{
"relative_path": "modules/overlays/assets/audiogram-headline.json",
"size_in_bytes": 1191
},
{
"relative_path": "modules/overlays/assets/audiogram-minimal.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/overlays/assets/guest-intro.json",
"size_in_bytes": 454
},
{
"relative_path": "modules/overlays/assets/handle-watermark.json",
"size_in_bytes": 486
},
{
"relative_path": "modules/overlays/assets/headline-bar.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/overlays/assets/hook-card.json",
"size_in_bytes": 735
},
{
"relative_path": "modules/overlays/assets/hook-title.json",
"size_in_bytes": 469
},
{
"relative_path": "modules/overlays/assets/launch-pack.json",
"size_in_bytes": 851
},
{
"relative_path": "modules/overlays/assets/pip-card.json",
"size_in_bytes": 375
},
{
"relative_path": "modules/overlays/assets/show-logo.json",
"size_in_bytes": 512
},
{
"relative_path": "modules/overlays/assets/sponsor-logo.json",
"size_in_bytes": 568
},
{
"relative_path": "modules/overlays/scripts/build-overlays.py",
"size_in_bytes": 35978
},
{
"relative_path": "modules/pace/MODULE.md",
"size_in_bytes": 3622
},
{
"relative_path": "modules/pace/scripts/plan-pace.py",
"size_in_bytes": 3437
},
{
"relative_path": "modules/preflight/MODULE.md",
"size_in_bytes": 4792
},
{
"relative_path": "modules/preflight/scripts/render-preflight.py",
"size_in_bytes": 19688
},
{
"relative_path": "modules/qa/MODULE.md",
"size_in_bytes": 4353
},
{
"relative_path": "modules/qa/scripts/validate-clip.py",
"size_in_bytes": 12237
},
{
"relative_path": "modules/shots/MODULE.md",
"size_in_bytes": 4793
},
{
"relative_path": "modules/shots/scripts/plan-shots.py",
"size_in_bytes": 11253
},
{
"relative_path": "modules/trims/MODULE.md",
"size_in_bytes": 4053
},
{
"relative_path": "modules/trims/scripts/plan-trims.py",
"size_in_bytes": 11623
},
{
"relative_path": "scripts/align-word-timings.py",
"size_in_bytes": 6904
}
]changed /skill_md_contents
"---\nname: fountain-clip-producer\ndescription: Render a clip post into a finished, platform-ready video with framing, captions, and overlays.\n---\n\n## Overview\n\nThis skill turns the clip that a post carries into a video file.\nIt does not choose the moment or the span.\nThe caller chooses both before this skill runs.\nModule **media** cuts the landscape master.\nEvery module after it works from that one file.\nThe other modules crop the picture and put the captions and the layers on it.\nThe skill delivers nothing until one report passes.\n\n## Input\n\n- The `SocialPostMediaSource` of a `SocialPost`, which names the file and the span.\n The file can have no video.\n Most files of a podcast catalogue have no video.\n Empty `ids` and a YouTube video id both name sources that Fountain does not hold as episodes.\n- Or an external source for a raw local video, which carries the media path and span for this session.\n Its post carries no `source`, because a raw local path is not a valid `media` URL.\n- The word timings of that span, which this skill makes from the clip's own audio.\n Module **shots** also needs the speaker of each word.\n No input supplies it.\n- A delivery tier, which the words of the request imply.\n- Or a queue run.\n Module **queue** reads the drafts that wait for media, and derives the inputs above from them.\n\nOptional:\n\n- A target shape, which is vertical, square, or landscape.\n- The name of a caption preset, or the text of an overlay.\n\n## Output\n\n- The finished work: a landscape master, and one export for each shape that the request asks for.\n The master is finished work, and not one of the workings.\n The user keeps it and cuts from it again.\n- A `SocialPostUpload` on `content.uploads` of the post, unless the user asked you not to attach it.\n- Workings: a clip manifest, a crop plan, a caption plan, an overlay plan, a QA report, and a removal\n report when module **trims** cut the clip.\n\n## Housekeeping\n\nYou MUST read HOUSEKEEPING.md if you haven't already.\n\n## Requirements\n\n- Fountain API.\n- Python 3.11 or later.\n- OpenCV 4.8 or later, importable from that same Python, for the face detection of module **framing**.\n Its model, and the fonts that the presets name, ship in `assets`, so no machine installs either.\n- ffmpeg and ffprobe, built with libass, drawtext, fontconfig and whisper.\n Without them, the skill cannot burn a caption or time a word.\n A stock build often has none of them.\n On macOS, the Homebrew `ffmpeg-full` formula has them.\n- A whisper.cpp model file.\n The whisper filter takes the path of this file, and does nothing without it.\n This skill looks first for `ggml-base.en.bin` in `~/.cache/whisper`.\n That file is 141 MB, so the skill does not ship it.\n The machine installs it one time.\n- ImageMagick, to measure the width of caption text.\n- yt-dlp, for a source that ffmpeg cannot seek directly.\n Keep it current, because YouTube changes what a client must send.\n A build a few weeks old gets a 403 error on every download, while the captions still download.\n- A web search tool, and a way to read a page, for the reference sources of module **brand**.\n- Skill **fountain-onboarding**, which installs a tool that module **preflight** finds missing.\n\n## Process\n\n1. Read the delivery tier from the request.\n Do the least work that the tier asks for.\n2. Run module **preflight** to check the machine before the first render.\n One report serves every clip of a run, because the machine does not change between clips.\n3. Run module **media** to cut the landscape master from `media`, between `ts_start` and `ts_end`.\n Cut all the clips of the run together, then transcribe them all together.\n No clip waits for another one.\n Then transcribe the master with whisper to get the word timings of the clip.\n Rebase the timings so that the first word starts at zero.\n Use the binary and the model that module **preflight** names, with `max_len=1`.\n Put `model=` last in the filter string, or the filter loses the option after it.\n Write the output as SRT, and give it to `scripts/align-word-timings.py`.\n Give the script the `transcript` of the source as the reference, and the clip's start in that SRT as\n the offset.\n The script keeps the words of the transcript and the timings of whisper.\n So a name or a number that whisper misheard is spelled correctly.\n The script writes the words JSON that module **captions** reads.\n Before you caption, read the words that the script lists as unheard.\n A number in digits, or a word that differs from the audio, is where a caption goes wrong.\n The words that the script lists as past the end are not in this clip, so check the offset and the duration.\n When the script matches less than 80% of the transcript, the transcript does not describe this clip.\n Then check the span and the offset before you caption.\n These timings are measured from the audio of the cut, so they are the only timings that describe this\n file.\n4. Run module **trims** to survey the pauses and the filler, and report what it found.\n Cut only when the user asks, because a cut moves every time stamp after it.\n5. Run module **framing** to crop the master to each shape that the request asks for.\n Run module **shots** with it when one shot holds two people and the crop must follow who speaks.\n Skip both for a source with no video.\n Such a source has no picture to crop and no face to follow.\n6. Run module **brand** to load the look of the show, for a clean final or a publish final.\n7. Send the user to the clip styling page when the request names no caption style and module\n **brand** holds none.\n The choice that the page records comes back as a brand kit.\n Do not stop the run to wait for an answer.\n Produce the clip with the default style, and tell the user which style that was.\n8. Run module **captions** on every portrait export, and on another shape when the request asks for it.\n Run module **fonts** with it.\n9. Run module **overlays** when the request asks for a layer, and always for a source with no video.\n For a source with no video, the overlay is not polish.\n There, an audiogram package is the whole picture.\n Without one, the clip is captions on an empty frame.\n Load the artwork of the show from `info.image`, and give it to the package.\n Every audiogram package needs the artwork.\n10. Run module **qa** as the blocking gate.\n Deliver nothing until it reports a pass.\n11. Use the render, and never the transcript, to confirm two things.\n The quote that the copy uses is in the clip.\n The person that the copy credits is the person who says the quote.\n The caller wrote the quote and the credit without seeing the clip.\n The transcript holds sentences and names no speaker.\n Find the speaker from the camera, and from a cutaway that shows a closed mouth.\n Repair what is wrong with the Social API, and tell the user what you changed.\n Move `ts_start` or `ts_end` when the clip opens or closes inside a word, or when the quote is outside\n the span.\n Then write the new words into `transcript`, so that the two agree.\n Correct the label, the title, the text and the context when the credit is wrong.\n A moved edge makes the render out of date, so go back to step 3 with the new span.\n The words, the captions and the gate all describe the old cut.\n Only the gate can say that the new cut is finished.\n Move an edge only to repair a fault that you can prove, or to make a change that the user asked for.\n Never move an edge to improve the clip, because choosing the moment is the caller's job.\n12. Attach the video with the Uploads API and the Social API, unless the user asked you not to.\n13. Present each finished clip on the clip card of skill **fountain-clip-finder**, with one added\n line saying the render result and where the video is attached.\n\n## Additional notes\n\nA run with more than one clip does the same work on each clip.\nMove all the clips through one stage, then move them all through the next stage.\nMost of the time of a render goes between the actions, and not inside them.\nA render of one clip spends a third of its time in the tools.\n\nThere are three delivery tiers, and each tier adds to the one before it.\nThe request implies which tier to use.\nThe user names the work that they want, and not the tier, so read the tier from their words:\n\n- A rough cut is the landscape master alone, with no crop, no captions and no gate.\n Read it from words about checking a span rather than making a clip.\n- A clean final is publishable.\n Its portrait export has captions, because people watch it with the sound off.\n Read it from \"produce this clip\", when the request names neither captions nor packaging.\n A clean final of a source with no video also has its audiogram package.\n The reason is the same as for the captions on a portrait export.\n Without the package, there is nothing to watch.\n- A publish final adds the overlays and the packaging, and the request names one of them.\n\nAsk the user when the words fit none of the three tiers.\nYou MUST NOT raise the tier on your own, because polish is requested work.\nCaptions on a portrait export do not raise the tier.\nMake a square or a landscape export only when the request asks for it.\n\nThe word timings come from the clip, and never from the episode transcript.\nThat transcript holds sentences and no words.\nIt is the caller's evidence for the span, and not this skill's evidence for a caption.\nThe text of the words follows a different rule.\nWhisper mishears a name or a number that the transcript has right.\nSo the words come from the transcript, and the timings come from whisper.\nNever write your own alignment code in the run, because code written during a run fails in a new way each time.\n\nAlways cut from the tallest rendition.\nA 9:16 crop keeps the whole height and about a third of the width.\nThus that height is the real resolution of the clip, and module **qa** fails a big upscale.\nWhen the tallest rendition cannot reach the target, deliver the size that it can hold, and tell the user.\nE.g., deliver 720x1280 from a 720p master.\nA true smaller resolution passes the gate, and an upscale does not.\n\nWhen a span seems wrong but you cannot prove it wrong, report it and do not repair it.\nA letterbox is also requested work.\n\nTo change a clip that this skill already made, first read the manifest and the QA report.\nReuse what is still correct, and write each new output under a new name.\nChange only the output of the module that changes.\nA caption change MUST NOT force a new crop.\n\nA post does not have to be approved before this skill runs, and rendering one approves nothing.\nNever put an API key, a token, or a cookie into a command, a manifest, or a report.\n\nThese steps make one clip, and the clips of one run are independent of each other.\nModule **queue** therefore shares the clips of a long queue between at most three workers.\nEach worker has its own output folder.\nA run is finished when the last clip is finished.\nThree workers do not finish three times faster, because ffmpeg already uses every core of the machine.\nA short queue uses one worker, because each new worker reads the skill again before it renders anything.\n\nThe purpose of this skill is a good clip, and not a full set of completed steps.\nReadability, framing, and sync matter more than procedure.\n"
"---\nname: fountain-clip-producer\ndescription: Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there.\n---\n\n## Overview\n\nThis skill renders the video of a clip post on this machine, when the user asks for a local render.\nFountain renders each clip automatically, and that render is the first choice, even when it misses part of the request.\nModule **media** cuts the landscape master, and every module after it works from that file.\nThe other modules crop the picture and put the captions and the layers on it.\nThe skill delivers nothing until one report passes.\n\n## Input\n\n- The `SocialPostMediaSource` of a `SocialPost`, which names the file and the span.\n The file can have no video.\n Most files of a podcast catalogue have no video.\n Empty `ids` and a YouTube video id both name sources that Fountain does not hold as episodes.\n- Or an external source for a raw local video, which carries the media path and span for this session.\n Its post carries no `source`, because a raw local path is not a valid `media` URL.\n- The word timings of that span, which this skill makes from the clip's own audio.\n Module **shots** also needs the speaker of each word.\n No input supplies it.\n- A delivery tier, which the words of the request imply.\n\nOptional:\n\n- A target shape, which is vertical, square, or landscape.\n- The name of a caption preset, or the text of an overlay.\n\n## Output\n\n- The finished work: a landscape master, and one export for each shape that the request asks for.\n The master is finished work, and not one of the workings.\n The user keeps it and cuts from it again.\n- A `SocialPostUpload` on `content.uploads` of the post, unless the user asked you not to attach it.\n- Workings: a clip manifest, a crop plan, a caption plan, an overlay plan, a QA report, a removal\n report when module **trims** cut the clip, and a pace plan when module **pace** sped it up.\n\n## Housekeeping\n\nYou MUST read HOUSEKEEPING.md if you haven't already.\n\n## Requirements\n\n- Fountain API.\n- Python 3.11 or later.\n- OpenCV 4.8 or later, importable from that same Python, for the face detection of module **framing**.\n Module **preflight** downloads its model, the fonts that the presets name, and the overlay placeholders.\n- ffmpeg and ffprobe, built with libass, drawtext, fontconfig and whisper.\n Without them, the skill cannot burn a caption or time a word.\n A stock build often has none of them.\n On macOS, the Homebrew `ffmpeg-full` formula has them.\n- A whisper.cpp model file.\n The whisper filter takes the path of this file, and does nothing without it.\n That file is 141 MB, so the machine installs it one time.\n Module **preflight** finds the file, and prints the line that installs it.\n- ImageMagick, to measure the width of caption text.\n- yt-dlp, for a source that ffmpeg cannot seek directly.\n Keep it current, because YouTube changes what a client must send.\n A build a few weeks old gets a 403 error on every download, while the captions still download.\n- A web search tool, and a way to read a page, for the reference sources of module **brand**.\n\n## Process\n\n1. Read the delivery tier from the request.\n Do the least work that the tier asks for.\n2. Run module **preflight** to check the machine before the first render.\n One report serves every clip of a run, because the machine does not change between clips.\n3. Run module **media** to cut the landscape master from `media`, between `ts_start` and `ts_end`.\n Cut all the clips of the run together, then transcribe them all together.\n No clip waits for another one.\n Then transcribe the master with whisper to get the word timings of the clip.\n Rebase the timings so that the first word starts at zero.\n Use the binary and the model that module **preflight** names, with `max_len=1`.\n Put `model=` last in the filter string, or the filter loses the option after it.\n Write the output as SRT, and give it to `scripts/align-word-timings.py`.\n Give the script the `transcript` of the source as the reference, and the clip's start in that SRT as\n the offset.\n The script keeps the words of the transcript and the timings of whisper.\n So a name or a number that whisper misheard is spelled correctly.\n The script writes the words JSON that module **captions** reads.\n Before you caption, read the words that the script lists as unheard.\n A number in digits, or a word that differs from the audio, is where a caption goes wrong.\n The words that the script lists as past the end are not in this clip, so check the offset and the duration.\n When the script matches less than 80% of the transcript, the transcript does not describe this clip.\n Then check the span and the offset before you caption.\n These timings are measured from the audio of the cut, so they are the only timings that describe this\n file.\n4. Run module **trims** to survey the pauses and the filler, and report what it found.\n Cut only when the user asks, because a cut moves every time stamp after it.\n5. Run module **pace** when the request asks for a faster clip, or the Brand section records a pace.\n6. Run module **framing** to crop the master to each shape that the request asks for.\n Run module **shots** with it when one shot holds two people and the crop must follow who speaks.\n Skip both for a source with no video.\n Such a source has no picture to crop and no face to follow.\n7. Run module **brand** to load the look of the show, for a clean final or a publish final.\n8. Send the user to the clip rendering page when the request names no caption style and module\n **brand** holds none.\n The choice that the page records comes back as a brand kit.\n Do not stop the run to wait for an answer.\n Produce the clip with the default style, and tell the user which style that was.\n9. Run module **captions** on every portrait export, and on another shape when the request asks for it.\n Run module **fonts** with it.\n10. Run module **overlays** when the request asks for a layer, and always for a source with no video.\n For a source with no video, the overlay is not polish.\n There, an audiogram package is the whole picture.\n Without one, the clip is captions on an empty frame.\n Load the artwork of the show from `info.image`, and give it to the package.\n Every audiogram package needs the artwork.\n11. Run module **qa** as the blocking gate.\n Deliver nothing until it reports a pass.\n12. Use the render, and never the transcript, to confirm two things.\n The quote that the copy uses is in the clip.\n The person that the copy credits is the person who says the quote.\n The caller wrote the quote and the credit without seeing the clip.\n The transcript holds sentences and names no speaker.\n Find the speaker from the camera, and from a cutaway that shows a closed mouth.\n Repair what is wrong, and tell the user what you changed.\n Move `ts_start` or `ts_end` when the clip opens or closes inside a word, or when the quote is outside\n the span.\n Then write the new words into `transcript`, so that the two agree.\n Correct the label, the title, the text and the context when the credit is wrong.\n A moved edge makes the render out of date, so go back to step 3 with the new span.\n The words, the captions and the gate all describe the old cut.\n Only the gate can say that the new cut is finished.\n Move an edge only to repair a fault that you can prove, or to make a change that the user asked for.\n Never move an edge to improve the clip, because choosing the moment is the caller's job.\n13. Upload the video with the Uploads API, unless the user asked you not to attach it.\n Send the repairs of step 12 and the `upload_id` in one Social API update.\n A new `source` and its `upload_id` MUST go in the same update.\n14. Give the user each finished clip, with its `post_url`, the render result, and where the video is attached.\n\n## Additional notes\n\nA run with more than one clip does the same work on each clip.\nMove all the clips through one stage, then move them all through the next stage.\nMost of the time of a render goes between the actions, and not inside them.\nA render of one clip spends a third of its time in the tools.\n\nThere are three delivery tiers, and each tier adds to the one before it.\nThe request implies which tier to use.\nThe user names the work that they want, and not the tier, so read the tier from their words:\n\n- A rough cut is the landscape master alone, with no crop, no captions and no gate.\n Read it from words about checking a span rather than making a clip.\n- A clean final is publishable.\n Its portrait export has captions, because people watch it with the sound off.\n Read it from \"produce this clip\", when the request names neither captions nor packaging.\n A clean final of a source with no video also has its audiogram package.\n The reason is the same as for the captions on a portrait export.\n Without the package, there is nothing to watch.\n- A publish final adds the overlays and the packaging, and the request names one of them.\n\nAsk the user when the words fit none of the three tiers.\nYou MUST NOT raise the tier on your own, because polish is requested work.\nCaptions on a portrait export do not raise the tier.\nMake a square or a landscape export only when the request asks for it.\n\nThe word timings come from the clip, and never from the episode transcript.\nThat transcript holds sentences and no words.\nIt is the caller's evidence for the span, and not this skill's evidence for a caption.\nThe text of the words follows a different rule.\nWhisper mishears a name or a number that the transcript has right.\nSo the words come from the transcript, and the timings come from whisper.\nNever write your own alignment code in the run, because code written during a run fails in a new way each time.\n\nAlways cut from the tallest rendition.\nA 9:16 crop keeps the whole height and about a third of the width.\nThus that height is the real resolution of the clip, and module **qa** fails a big upscale.\nWhen the tallest rendition cannot reach the target, deliver the size that it can hold, and tell the user.\nE.g., deliver 720x1280 from a 720p master.\nA true smaller resolution passes the gate, and an upscale does not.\n\nWhen a span seems wrong but you cannot prove it wrong, report it and do not repair it.\nA letterbox is also requested work.\n\nTo change a clip that this skill already made, first read the manifest and the QA report.\nReuse what is still correct, and write each new output under a new name.\nChange only the output of the module that changes.\nA caption change MUST NOT force a new crop.\n\nA post does not have to be approved before this skill runs, and rendering one approves nothing.\nNever put an API key, a token, or a cookie into a command, a manifest, or a report.\n\nKeep the files of a run in `fountain/outputs` of the working folder, with one folder for each show.\nIn it, give each clip a folder named `{episode_number ?? YYYY-MM-DD}-{slugified-topic}`.\nKeep only the finished work in the clip folder.\nPut the rest in its `workings` folder, or in the `workings` folder of the show when clips share it.\nThese files are for this session only, so no later session reads them.\n\nThe purpose of this skill is a good clip, and not a full set of completed steps.\nReadability, framing, and sync matter more than procedure.\n"Full snapshot data
{
"description": "Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there.",
"included_files": [
{
"relative_path": "HOUSEKEEPING.md",
"size_in_bytes": 9268
},
{
"relative_path": "modules/brand/MODULE.md",
"size_in_bytes": 6947
},
{
"relative_path": "modules/brand/scripts/derive-palette.py",
"size_in_bytes": 8736
},
{
"relative_path": "modules/captions/MODULE.md",
"size_in_bytes": 10057
},
{
"relative_path": "modules/captions/assets/bold-social.json",
"size_in_bytes": 624
},
{
"relative_path": "modules/captions/assets/bounce-in.json",
"size_in_bytes": 589
},
{
"relative_path": "modules/captions/assets/broadsheet.json",
"size_in_bytes": 579
},
{
"relative_path": "modules/captions/assets/current-word.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/captions/assets/glow-bounce.json",
"size_in_bytes": 756
},
{
"relative_path": "modules/captions/assets/glow-word.json",
"size_in_bytes": 662
},
{
"relative_path": "modules/captions/assets/hormozi.json",
"size_in_bytes": 825
},
{
"relative_path": "modules/captions/assets/impact-loud.json",
"size_in_bytes": 655
},
{
"relative_path": "modules/captions/assets/karaoke-fill.json",
"size_in_bytes": 603
},
{
"relative_path": "modules/captions/assets/marker.json",
"size_in_bytes": 593
},
{
"relative_path": "modules/captions/assets/minimal-light.json",
"size_in_bytes": 697
},
{
"relative_path": "modules/captions/assets/pill-karaoke.json",
"size_in_bytes": 661
},
{
"relative_path": "modules/captions/assets/stadium.json",
"size_in_bytes": 559
},
{
"relative_path": "modules/captions/assets/typewriter.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/captions/assets/wide-block.json",
"size_in_bytes": 574
},
{
"relative_path": "modules/captions/assets/word-pop.json",
"size_in_bytes": 669
},
{
"relative_path": "modules/captions/scripts/build-captions.py",
"size_in_bytes": 59047
},
{
"relative_path": "modules/fonts/MODULE.md",
"size_in_bytes": 3970
},
{
"relative_path": "modules/framing/MODULE.md",
"size_in_bytes": 5283
},
{
"relative_path": "modules/framing/scripts/extract-face-framing.py",
"size_in_bytes": 13082
},
{
"relative_path": "modules/framing/scripts/visual-person-qa.py",
"size_in_bytes": 5946
},
{
"relative_path": "modules/media/MODULE.md",
"size_in_bytes": 9538
},
{
"relative_path": "modules/media/scripts/build-time-map.py",
"size_in_bytes": 19853
},
{
"relative_path": "modules/media/scripts/verify-content-alignment.py",
"size_in_bytes": 6504
},
{
"relative_path": "modules/overlays/MODULE.md",
"size_in_bytes": 7277
},
{
"relative_path": "modules/overlays/assets/audiogram-brand.json",
"size_in_bytes": 1137
},
{
"relative_path": "modules/overlays/assets/audiogram-cover.json",
"size_in_bytes": 1088
},
{
"relative_path": "modules/overlays/assets/audiogram-headline.json",
"size_in_bytes": 1191
},
{
"relative_path": "modules/overlays/assets/audiogram-minimal.json",
"size_in_bytes": 749
},
{
"relative_path": "modules/overlays/assets/guest-intro.json",
"size_in_bytes": 454
},
{
"relative_path": "modules/overlays/assets/handle-watermark.json",
"size_in_bytes": 486
},
{
"relative_path": "modules/overlays/assets/headline-bar.json",
"size_in_bytes": 638
},
{
"relative_path": "modules/overlays/assets/hook-card.json",
"size_in_bytes": 735
},
{
"relative_path": "modules/overlays/assets/hook-title.json",
"size_in_bytes": 469
},
{
"relative_path": "modules/overlays/assets/launch-pack.json",
"size_in_bytes": 851
},
{
"relative_path": "modules/overlays/assets/pip-card.json",
"size_in_bytes": 375
},
{
"relative_path": "modules/overlays/assets/show-logo.json",
"size_in_bytes": 512
},
{
"relative_path": "modules/overlays/assets/sponsor-logo.json",
"size_in_bytes": 568
},
{
"relative_path": "modules/overlays/scripts/build-overlays.py",
"size_in_bytes": 35978
},
{
"relative_path": "modules/pace/MODULE.md",
"size_in_bytes": 3622
},
{
"relative_path": "modules/pace/scripts/plan-pace.py",
"size_in_bytes": 3437
},
{
"relative_path": "modules/preflight/MODULE.md",
"size_in_bytes": 4792
},
{
"relative_path": "modules/preflight/scripts/render-preflight.py",
"size_in_bytes": 19688
},
{
"relative_path": "modules/qa/MODULE.md",
"size_in_bytes": 4353
},
{
"relative_path": "modules/qa/scripts/validate-clip.py",
"size_in_bytes": 12237
},
{
"relative_path": "modules/shots/MODULE.md",
"size_in_bytes": 4793
},
{
"relative_path": "modules/shots/scripts/plan-shots.py",
"size_in_bytes": 11253
},
{
"relative_path": "modules/trims/MODULE.md",
"size_in_bytes": 4053
},
{
"relative_path": "modules/trims/scripts/plan-trims.py",
"size_in_bytes": 11623
},
{
"relative_path": "scripts/align-word-timings.py",
"size_in_bytes": 6904
}
],
"name": "fountain-clip-producer",
"skill_md_contents": "---\nname: fountain-clip-producer\ndescription: Render a clip on this machine, only when the user asks. To change a clip on Fountain, render it again there.\n---\n\n## Overview\n\nThis skill renders the video of a clip post on this machine, when the user asks for a local render.\nFountain renders each clip automatically, and that render is the first choice, even when it misses part of the request.\nModule **media** cuts the landscape master, and every module after it works from that file.\nThe other modules crop the picture and put the captions and the layers on it.\nThe skill delivers nothing until one report passes.\n\n## Input\n\n- The `SocialPostMediaSource` of a `SocialPost`, which names the file and the span.\n The file can have no video.\n Most files of a podcast catalogue have no video.\n Empty `ids` and a YouTube video id both name sources that Fountain does not hold as episodes.\n- Or an external source for a raw local video, which carries the media path and span for this session.\n Its post carries no `source`, because a raw local path is not a valid `media` URL.\n- The word timings of that span, which this skill makes from the clip's own audio.\n Module **shots** also needs the speaker of each word.\n No input supplies it.\n- A delivery tier, which the words of the request imply.\n\nOptional:\n\n- A target shape, which is vertical, square, or landscape.\n- The name of a caption preset, or the text of an overlay.\n\n## Output\n\n- The finished work: a landscape master, and one export for each shape that the request asks for.\n The master is finished work, and not one of the workings.\n The user keeps it and cuts from it again.\n- A `SocialPostUpload` on `content.uploads` of the post, unless the user asked you not to attach it.\n- Workings: a clip manifest, a crop plan, a caption plan, an overlay plan, a QA report, a removal\n report when module **trims** cut the clip, and a pace plan when module **pace** sped it up.\n\n## Housekeeping\n\nYou MUST read HOUSEKEEPING.md if you haven't already.\n\n## Requirements\n\n- Fountain API.\n- Python 3.11 or later.\n- OpenCV 4.8 or later, importable from that same Python, for the face detection of module **framing**.\n Module **preflight** downloads its model, the fonts that the presets name, and the overlay placeholders.\n- ffmpeg and ffprobe, built with libass, drawtext, fontconfig and whisper.\n Without them, the skill cannot burn a caption or time a word.\n A stock build often has none of them.\n On macOS, the Homebrew `ffmpeg-full` formula has them.\n- A whisper.cpp model file.\n The whisper filter takes the path of this file, and does nothing without it.\n That file is 141 MB, so the machine installs it one time.\n Module **preflight** finds the file, and prints the line that installs it.\n- ImageMagick, to measure the width of caption text.\n- yt-dlp, for a source that ffmpeg cannot seek directly.\n Keep it current, because YouTube changes what a client must send.\n A build a few weeks old gets a 403 error on every download, while the captions still download.\n- A web search tool, and a way to read a page, for the reference sources of module **brand**.\n\n## Process\n\n1. Read the delivery tier from the request.\n Do the least work that the tier asks for.\n2. Run module **preflight** to check the machine before the first render.\n One report serves every clip of a run, because the machine does not change between clips.\n3. Run module **media** to cut the landscape master from `media`, between `ts_start` and `ts_end`.\n Cut all the clips of the run together, then transcribe them all together.\n No clip waits for another one.\n Then transcribe the master with whisper to get the word timings of the clip.\n Rebase the timings so that the first word starts at zero.\n Use the binary and the model that module **preflight** names, with `max_len=1`.\n Put `model=` last in the filter string, or the filter loses the option after it.\n Write the output as SRT, and give it to `scripts/align-word-timings.py`.\n Give the script the `transcript` of the source as the reference, and the clip's start in that SRT as\n the offset.\n The script keeps the words of the transcript and the timings of whisper.\n So a name or a number that whisper misheard is spelled correctly.\n The script writes the words JSON that module **captions** reads.\n Before you caption, read the words that the script lists as unheard.\n A number in digits, or a word that differs from the audio, is where a caption goes wrong.\n The words that the script lists as past the end are not in this clip, so check the offset and the duration.\n When the script matches less than 80% of the transcript, the transcript does not describe this clip.\n Then check the span and the offset before you caption.\n These timings are measured from the audio of the cut, so they are the only timings that describe this\n file.\n4. Run module **trims** to survey the pauses and the filler, and report what it found.\n Cut only when the user asks, because a cut moves every time stamp after it.\n5. Run module **pace** when the request asks for a faster clip, or the Brand section records a pace.\n6. Run module **framing** to crop the master to each shape that the request asks for.\n Run module **shots** with it when one shot holds two people and the crop must follow who speaks.\n Skip both for a source with no video.\n Such a source has no picture to crop and no face to follow.\n7. Run module **brand** to load the look of the show, for a clean final or a publish final.\n8. Send the user to the clip rendering page when the request names no caption style and module\n **brand** holds none.\n The choice that the page records comes back as a brand kit.\n Do not stop the run to wait for an answer.\n Produce the clip with the default style, and tell the user which style that was.\n9. Run module **captions** on every portrait export, and on another shape when the request asks for it.\n Run module **fonts** with it.\n10. Run module **overlays** when the request asks for a layer, and always for a source with no video.\n For a source with no video, the overlay is not polish.\n There, an audiogram package is the whole picture.\n Without one, the clip is captions on an empty frame.\n Load the artwork of the show from `info.image`, and give it to the package.\n Every audiogram package needs the artwork.\n11. Run module **qa** as the blocking gate.\n Deliver nothing until it reports a pass.\n12. Use the render, and never the transcript, to confirm two things.\n The quote that the copy uses is in the clip.\n The person that the copy credits is the person who says the quote.\n The caller wrote the quote and the credit without seeing the clip.\n The transcript holds sentences and names no speaker.\n Find the speaker from the camera, and from a cutaway that shows a closed mouth.\n Repair what is wrong, and tell the user what you changed.\n Move `ts_start` or `ts_end` when the clip opens or closes inside a word, or when the quote is outside\n the span.\n Then write the new words into `transcript`, so that the two agree.\n Correct the label, the title, the text and the context when the credit is wrong.\n A moved edge makes the render out of date, so go back to step 3 with the new span.\n The words, the captions and the gate all describe the old cut.\n Only the gate can say that the new cut is finished.\n Move an edge only to repair a fault that you can prove, or to make a change that the user asked for.\n Never move an edge to improve the clip, because choosing the moment is the caller's job.\n13. Upload the video with the Uploads API, unless the user asked you not to attach it.\n Send the repairs of step 12 and the `upload_id` in one Social API update.\n A new `source` and its `upload_id` MUST go in the same update.\n14. Give the user each finished clip, with its `post_url`, the render result, and where the video is attached.\n\n## Additional notes\n\nA run with more than one clip does the same work on each clip.\nMove all the clips through one stage, then move them all through the next stage.\nMost of the time of a render goes between the actions, and not inside them.\nA render of one clip spends a third of its time in the tools.\n\nThere are three delivery tiers, and each tier adds to the one before it.\nThe request implies which tier to use.\nThe user names the work that they want, and not the tier, so read the tier from their words:\n\n- A rough cut is the landscape master alone, with no crop, no captions and no gate.\n Read it from words about checking a span rather than making a clip.\n- A clean final is publishable.\n Its portrait export has captions, because people watch it with the sound off.\n Read it from \"produce this clip\", when the request names neither captions nor packaging.\n A clean final of a source with no video also has its audiogram package.\n The reason is the same as for the captions on a portrait export.\n Without the package, there is nothing to watch.\n- A publish final adds the overlays and the packaging, and the request names one of them.\n\nAsk the user when the words fit none of the three tiers.\nYou MUST NOT raise the tier on your own, because polish is requested work.\nCaptions on a portrait export do not raise the tier.\nMake a square or a landscape export only when the request asks for it.\n\nThe word timings come from the clip, and never from the episode transcript.\nThat transcript holds sentences and no words.\nIt is the caller's evidence for the span, and not this skill's evidence for a caption.\nThe text of the words follows a different rule.\nWhisper mishears a name or a number that the transcript has right.\nSo the words come from the transcript, and the timings come from whisper.\nNever write your own alignment code in the run, because code written during a run fails in a new way each time.\n\nAlways cut from the tallest rendition.\nA 9:16 crop keeps the whole height and about a third of the width.\nThus that height is the real resolution of the clip, and module **qa** fails a big upscale.\nWhen the tallest rendition cannot reach the target, deliver the size that it can hold, and tell the user.\nE.g., deliver 720x1280 from a 720p master.\nA true smaller resolution passes the gate, and an upscale does not.\n\nWhen a span seems wrong but you cannot prove it wrong, report it and do not repair it.\nA letterbox is also requested work.\n\nTo change a clip that this skill already made, first read the manifest and the QA report.\nReuse what is still correct, and write each new output under a new name.\nChange only the output of the module that changes.\nA caption change MUST NOT force a new crop.\n\nA post does not have to be approved before this skill runs, and rendering one approves nothing.\nNever put an API key, a token, or a cookie into a command, a manifest, or a report.\n\nKeep the files of a run in `fountain/outputs` of the working folder, with one folder for each show.\nIn it, give each clip a folder named `{episode_number ?? YYYY-MM-DD}-{slugified-topic}`.\nKeep only the finished work in the clip folder.\nPut the rest in its `workings` folder, or in the `workings` folder of the show when clips share it.\nThese files are for this session only, so no later session reads them.\n\nThe purpose of this skill is a good clip, and not a full set of completed steps.\nReadability, framing, and sync matter more than procedure.\n"
}SHA-256 of public snapshot: eda6b6ce128080c84d62b989a5e7c0db1402818f5d540fe54914c9c4a98feae1