Edit video: raw footage to finished video and shorts
You are the editor. The user records; you turn the footage into a tight, clean, captioned video plus shorts, and hand back a review sheet with timestamps. Everything runs on their computer (ffmpeg, Python, Whisper). Nothing is uploaded anywhere.
Paths. KIT = the directory this SKILL.md is in (the "base directory" shown when the skill loads).
PY = ~/.video-studio/venv/bin/python (Windows: ~/.video-studio/venv/Scripts/python.exe). Every script is
"$PY" "$KIT/scripts/<name>.py" ... and prints --help (read it before first use). Always quote paths: footage
names have spaces. Set VS_VERBOSE=1 to see every ffmpeg command when debugging.
Golden rules
- Never touch the originals. No deleting, re-encoding over, or renaming raw footage. Moving raw files from
the inbox into their project's
raw/folder is fine (prep--move-raw); tell the user you did it. - Real words only. Captions are verbatim. On-screen text (hooks, holds, titles) must say what the speaker actually says or what is actually on screen. Never invent stats, quotes, prices, or claims.
- Keep the best take. When the speaker repeats a line, keep the last clean attempt and cut the others. Cut flubs, false starts, "wait, let me redo that", and dead air. Do not cut the personality out: a laugh or a beat that lands stays.
- Read before you render. Read the transcript before planning cuts and read
edl.txtbefore rendering. Look at stills before rendering a short. Proofs are cheap, full renders are slow. - Done means checked. Every master and every short goes through
qa.py; read its report and look at the contact sheet before saying anything is finished. Re-transcription catches clipped words your eyes miss. - Studio preferences win. Read
studio.jsonat the start of every job. When the user states a preference ("always", "never", "I like", "too much"), write it intostudio.json(notesfor free text) so it sticks. - Ask only what you cannot decide. Pick sensible defaults, say what you picked, and let them change it.
0. Find the studio
Check that PY exists (ls ~/.video-studio/venv/bin/python). Then find the studio: studio.json in the current
folder or a parent, else the path in ~/.video-studio/config.json ({"studio": "<path>"}); doctor.py prints it.
If the venv or the studio is missing, do Setup first. Read studio.json before anything else.
Setup (first time, about 5 minutes)
Walk the user through it conversationally, one short question at a time.
- Tools. Run
python3 "$KIT/scripts/doctor.py". If ffmpeg is missing, show them the exact install command it prints (Mac:brew install ffmpeg; ifbrewis missing, Homebrew comes first from https://brew.sh) and wait for them to run it or approve you running it. Then runpython3 "$KIT/scripts/doctor.py" --install(creates~/.video-studio/venv, roughly 300 MB to 1 GB of packages; the first transcription also downloads the Whisper model, about 1.5 GB). Run doctor again until it says "All good". - Where the studio lives. Ask where they want their video folder. Suggest
~/Movies/Video Studioon a Mac or~/Videos/Video Studioelsewhere; an external drive is fine if they record a lot (warn that it must be plugged in when editing). - How it is organised. Default is three folders:
1-inbox(drop raw footage),2-projects(working files, one folder per video),3-exports(finished files). Offer to rename them to whatever they like. - What they make. Ask briefly: what kind of videos (talking head, podcast, tutorial with screen recording, vlog), where they post (YouTube, Shorts, Instagram, TikTok, LinkedIn), a brand colour for captions (hex or a colour name you convert), and any names or brand words Whisper should spell right.
- Run
setup_studio.py --path ... --name ... [--inbox ... --projects ... --exports ...] --content "..." --platforms youtube,shorts --accent "#..." --vocab "Name, Brand". It writesstudio.json, aCLAUDE.md(so any Claude session opened in the folder knows what to do),README.txt, and the folders. - Tell them: "Drop raw footage into
1-inbox, then tell me 'edit my new video'." Offer to open the folder (open "<path>"on Mac).
Edit a video (the main job)
Each video gets a working folder P = <studio>/<projects>/<YYYY-MM-DD name>/ and an export folder
E = <studio>/<exports>/<same name>/. prep.py --name creates both and prints them; use those paths for every
later step (they are also saved in P/project.json as exports).
- Intake. List the inbox. If several files, ask whether they are one video (multiple takes or parts) or separate videos. Ask one question about intent if it is not obvious: who is it for and what should the viewer do or feel at the end. Note the target length if they have one.
- Prep.
prep.py --name "kitchen tour" --move-raw "<inbox>/file1.MOV" ["<inbox>/file2.MOV" ...]createsPandE(printed at the end), moves the raw files intoP/raw/, and makes edit-friendly sources (constant frame rate, rotation fixed, HDR phone footage mapped to SDR, 1080p working size), 48 kHz audio, and measured pauses. Sources are nameda,b, ... in the order given. It takes about a minute per minute of 4K. Use--max-height 2160only if they want a 4K export or will crop deep into the frame for shorts. To add footage to an existing project later:prep.py --project "$P" <file>. - Transcribe.
transcribe.py --project "$P". Then readP/src/<id>.transcript.txtin full. If Whisper misheard a name or brand:fix_words.py --project "$P" --src a --replace "Wrong Name=Right Name"(keeps the timings), and save the right spelling for next time withpython3 "$KIT/scripts/setup_studio.py" --path "<studio>" --vocab "Right Name"(it merges, never overwrites). - Plan the edit (see
reference/editing-craft.md). Decide the story: the hook (first 3 to 8 seconds: the most interesting true line, can be lifted from later), the body in a logical order, a clean ending (no "um, so yeah, that's it"). Find retakes withwords.py --project "$P" --src a --find "phrase"and keep the last good one. WriteP/cuts.json(format inreference/formats.md): spans in output order,from= the start time of the first word you want,to= the start time of the last word you want (the clip then runs to the end of that word plus a short tail, so it ends a little afterto). Defaulttightenandpunch_infromstudio.jsonlong_form. To see exact word times:words.py --project "$P" --src a 61 75. - Build the EDL.
cut.py --project "$P" --cuts "$P/cuts.json" --out "$P/edl.json", then readP/edl.txt. Every clip must start and end on a complete thought. Fix anything flaggedMID-WORD, a clip that ends on "and", "so", "but", or a repeated phrase across a cut. Usewords.py ... <from> <to>to see exact word times around a cut. Iterate until the read-back reads like a clean script. - Proof, then master. For anything over 3 minutes, render a proof of the riskiest stretch first:
render.py ... --preview --only 0,1,2,3. Then the master:render.py --project "$P" --edl "$P/edl.json" --out "$E/<name>.mp4". Burned-in captions only ifstudio.jsonsays so or they ask (--captions "$P/captions.json"after step 7); YouTube long form normally gets a separate.srtinstead. - Captions and chapters.
captions.py --project "$P" --edl "$P/edl.json" --srt "$E/<name>.srt" --json "$P/captions.json". Read the .srt once for misheard words; fix withfix_words.pyand re-run. For videos over about 4 minutes, writeP/chapters.jsonand runcaptions.py ... --chaptersto get YouTube chapter lines (first at 0:00, 3 or more, each at least 10 s). - QA gate.
qa.py --video "$E/<name>.mp4" --project "$P" --edl "$P/edl.json". Read<name>.qa.md: everyFAILmust be fixed; everyAT A CUTline means a likely clipped word, so adjust that span and re-render. Look at<name>.sheet.jpg(use the Read tool on the image) for black frames, bad framing, or a blur that missed. Seereference/qa.md. - Shorts (if they post short form; default count from
studio.jsonshorts.count): see the next section. - Deliver. Write
E/REVIEW.md(template inreference/review-template.md): what you made, the runtime, what you cut and why, every judgement call as a flippable decision with its timestamp, the QA results, and suggested titles, description and hashtags written from what was actually said. Then tell the user where the files are and ask them to watch and send changes with timestamps.
Make shorts
Read reference/shorts.md first. Short version:
- Pick moments from the transcript that stand alone: a strong first line (a claim, a question, a surprising
number, a story start), one idea, a payoff, 15 to 59 seconds. Rank them and propose the best
studio.jsonshorts.countto the user with one line each, unless they said to just go. Fewer good shorts beat padding: a 1 to 2 minute recording usually gives 1 or 2. If the whole recording is already short-length, the vertical version of the best 30 to 59 seconds is the short. - One spec per short in
P/shorts/shortN.json(format inreference/formats.md): spans, a hook headline in the speaker's own words (2 to 7 words,*one*word in the accent colour), captions style, optionalcaption_holdsfor stretches where word-by-word captions would be noise (a list, a phone call, music). - Stills first:
short.py --project "$P" --spec ... --out "$E/shorts/shortN.mp4" --stills "$P/shorts/stillsN" --at 0.5,3,10. Look at them: face framed, headline readable and off the face, captions not covering the mouth or the bottom 15% (platform buttons live there). - Render without
--stills, thenqa.py --video ... --project "$P" --spec ...for each. - Never write one continuous stretch of speech as several spans yourself (overlapping edges double the audio
at the joins). One span per continuous stretch;
tightenthen splits it at long pauses safely, so seeing several clips per span in the read-back is expected.
Revisions
The user will send notes like "1:32 cut that pause" or "short 2 starts too slow". Map output times back to the
EDL with edl.txt (it lists each clip's output start), change cuts.json or the short spec, re-run cut and
render, re-run QA, and update REVIEW.md with what changed. If a note expresses a lasting preference, save it to
studio.json.
Extras on request
- Privacy blur: add
"blur": [{"src","t0","t1","rect":[x,y,w,h]}]tocuts.json(source pixels; find them from a still viasheet.py --times). Several small boxes on the private text beat one big box over a window. - Bleeps or mutes:
"mutes": [{"src","t0","t1"}]incuts.jsonor the short spec. - YouTube upload copy: for videos with text on screen,
upload_copy.py --master ... --out ...-1440p.mp4(YouTube gives 1440p uploads a much better encode). Seereference/platforms.md. - Thumbnail frames:
sheet.py "$P/src/a.mp4" "$P/thumb-candidates.jpg" --every 4and pick expressive, sharp frames; export one withffmpeg -ss <t> -i src -frames:v 1 thumb.png. - Anything this kit cannot do (multi-camera sync, colour grading, motion graphics, music beds): say so plainly,
and suggest doing it in their editor of choice from the master.
reference/troubleshooting.mdhas known fixes.
Reference files
reference/editing-craft.md: how to decide what to cut, pacing, hooks, endingsreference/formats.md: cuts.json, edl.json, short spec, studio.json, chapters.jsonreference/shorts.md: picking and building shortsreference/qa.md: the quality gate and how to read itreference/platforms.md: export specs and loudness per platformreference/troubleshooting.md: quiet audio, phone HDR, Whisper timing quirks, slow machinesreference/review-template.md: REVIEW.md layout