A video editor that isn’t there

Phantom is the command line I built to take my YouTube videos from raw recording to uploaded. Trims, audio, titles, B-roll and posts included.

Every video I’m making, and its next step
$ phantom projects status

==============================================================================
 YOUTUBE CONTENT PIPELINE SUMMARY
 Directory: ~/Videos/YT Projects
==============================================================================
1. Why I script before I record
   Stage 2/9: [Scripted]
   Pipeline: ✓1 ➔ [2:Script] ➔ 3:Thumb ➔ 4:Rec ➔ 5:Proc ➔ 6:Rev ➔ 7:Ready ➔ 8:Sched ➔ 9:Upload
   ➜ Next Step: Design the thumbnail (e.g. thumbnail.png)
   Files: [✓ outline.md] | [No raw files] | [✗ to-review.mp4] | [✗ final.mp4] | [✗ metadata.json] | [✗ thumbnail]
------------------------------------------------------------------------------
 2/9 [Scripted]           Why I script before I record
      ➜ Design the thumbnail (e.g. thumbnail.png)
2. My new app changes YouTube forever
   Stage 5/9: [Processed]
   Pipeline: ✓1 ➔ ✓2 ➔ ✓3 ➔ ✓4 ➔ [5:Proc] ➔ 6:Rev ➔ 7:Ready ➔ 8:Sched ➔ 9:Upload
   ➜ Next Step: Review video and export final.mp4
   Files: [✓ outline.md] | [1 raw file(s)] | [✓ to-review.mp4] | [✗ final.mp4] | [✗ metadata.json] | [✓ thumbnail (thumbnail.png)]
------------------------------------------------------------------------------
 5/9 [Processed]          My new app changes YouTube forever
      ➜ Review video and export final.mp4
3. Terminals are easy, actually
   Stage 7/9: [Ready to Schedule]
   Pipeline: ✓1 ➔ ✓2 ➔ ✓3 ➔ ✓4 ➔ ✓5 ➔ ✓6 ➔ [7:Ready] ➔ 8:Sched ➔ 9:Upload
   ➜ Next Step: Upload video to YouTube (`phantom publish yt`) or set publishDate in metadata.json
   Files: [✓ outline.md] | [2 raw file(s)] | [✓ to-review.mp4] | [✓ final.mp4] | [✓ metadata.json] | [✓ thumbnail (thumbnail.png)]
------------------------------------------------------------------------------
 7/9 [Ready to Schedule]  Terminals are easy, actually
      ➜ Upload video to YouTube (`phantom publish yt`) or set publishDate in metadata.json
4. Six weeks of content in one sitting
   Stage 9/9: [Uploaded]
   Pipeline: ✓1 ➔ ✓2 ➔ ✓3 ➔ ✓4 ➔ ✓5 ➔ ✓6 ➔ ✓7 ➔ ✓8 ➔ [9:Upload]
   ➜ Next Step: Pipeline complete!
   Files: [✓ outline.md] | [1 raw file(s)] | [✓ to-review.mp4] | [✓ final.mp4] | [✓ metadata.json (Uploaded: 2026-10-20)] | [✓ thumbnail (thumbnail.png)]
------------------------------------------------------------------------------
 9/9 [Uploaded]           Six weeks of content in one sitting
      ➜ Pipeline complete!

Fifth, run
one command

Seven steps: transcribe, trim the silences, clean the audio, encode once, verify, review, write the metadata.

~/Videos/YT Projects/
My new app changes YouTube forever
Stage 5/9: [Processed]

   outline.md
   thumbnail.png
   raw.mp4
   to-review.mp4
   final.mp4
   metadata.json
   publishDate
   YouTube URL

➜ Next step: Review video and export final.mp4
$ phantom edit inspect-video to-review.mp4

What one run leaves behind

cuts.txt
# the coughing fit
00:27-00:35
# said the wrong model name
06:25-06:35
02:05-02:17.5

Bad takes, cut from a text file

One start and end per line, on the raw recording’s timeline. The ranges come out in the same single encode, so they cost nothing extra.

summary.md
# phantom pipeline
Input: raw.mp4 (1920x1080, 30fps) -> final.mp4 (07:31)
Cuts (cuts.txt): 00:27-00:35, 06:25-06:35

## Review flags
none

## Timing
Transcription              8.1s
Speech Extraction          5.4s
Audio Processing          1m03s
Final Encode & Multiplex  4m00s
Verify Final               0.2s
Audio & Video Review      39.6s
Auto Create Metadata      2m21s
total                     7m29s

One command, seven steps

The run report for a real 7 minute video. The video is encoded once, at the end, straight from the source. A finished step is skipped on the next run.

process_audio.sh
# 1. extract   2. denoise   3. normalise   4. remux
deep-filter raw-audio.wav
ffmpeg -i filtered.wav \
  -af "loudnorm=I=-16:TP=-1.5:LRA=11" \
  -ar 48000 processed-audio.wav

Every video at the same loudness

DeepFilterNet takes the room noise out, then loudnorm brings the voice to −16 LUFS with a true peak of −1.5 dB. The video stream is copied, never re-encoded.

phantom edit inspect-video
====================================================
VIDEO QUALITY CONTROL INSPECTION REPORT
====================================================
Resolution:      1920x1080 (1080p Full HD)
Aspect Ratio:    16:9 [Ratio: 1.78]
Duration:        00:07:31.000
Frame Rate:      30.00 fps
----------------------------------------------------
  • Black Detect:  min_duration=0.1s
  • Freeze Detect: min_duration=0.5s
----------------------------------------------------
SUCCESS: No video glitches or bad frames detected

A check before anyone sees it

ffmpeg’s blackdetect and freezedetect scan the final file and print the exact timestamp of anything wrong, ready to find in an editor.

phantom meta approve-title
My new app changes YouTube forever
   Current title (not approved)
   1. [14/16] I Built the App YouTube Should Have Shipped  (44 chars)
   2. [13/16] This App Replaced My Video Editor  (33 chars)
   3. [13/16] Why I Stopped Editing My Own Videos  (35 chars)
   4. [12/16] One Command Now Edits My Whole Video  (36 chars)
   5. [12/16] My Editing Workflow Is a Terminal Now  (37 chars)

Titles, scored out of sixteen

Claude drafts the titles and scores each on eight counts, from clarity to honesty. The top one is a placeholder until a person approves it.

metadata.json
{
  "title": "How Semantic Caching Works and How to Use It",
  "titleReviewed": true,
  "timestamps": [
    { "timestamp": "00:00", "topic": "The Repeat Question Problem" },
    { "timestamp": "00:40", "topic": "Who Should Use Semantic Caching" },
    { "timestamp": "01:35", "topic": "Semantic Caching Flow Example" }
  ],
  "tweetTemplate": "... {url}",
  "resources": [ ... ],
  "recommendations": [ ... ]
}

Everything YouTube asks for

Description, chapters, tweet, next-video picks and resource links, all written from the trimmed captions so a cut take never reaches them.

phantom projects status --repackage
Six weeks of content in one sitting
   Stage 10: [Needs Repackaging]
   ➜ Next Step: Rework the title & thumbnail:
     CTR 2.1% in its first 2 days
     < channel's 28-day 3.4%
   Files: [✓ final.mp4] | [✓ metadata.json]

A second look after 48 hours

Two days after publishing, the video’s click-through rate is read from YouTube Studio and set against the channel’s 28-day figure. A weak one is flagged for a new title and thumbnail.

step-card, rendered by phantom

B-roll, timed to the words

Each template is built once. Per video I write a small cue file and run one command, and every clip lands on the exact word that starts it. It costs no LLM tokens.

These five clips were rendered by Phantom for this page, in this page’s colours.

A numbered point, on cue

Step 2 of 7, red flag number 3, tip number 7. The label is a pattern and the title fits itself to the frame, from 112 pixels down to 56.

phantom broll clip step-card -o step-2.mp4 \
  --cue "so number two" --until "every single pause" \
  --var number=2 --var total=7 \
  --var "title=Trim every silence"

One number, counted up

Write the value the way it should read. The digits roll up like an odometer, and the words around them sit small on the same baseline.

phantom broll clip big-stat -o stat.mp4 \
  --cue "forty eight hours" \
  --var "value=48 hours" \
  --var "label=before a weak title gets flagged"

A command, typed out

Typed with a human rhythm that is the same on every render. Output lines can print on the beat of the words that mention them.

phantom broll clip terminal -o run.mp4 \
  --cue "you run one command" \
  --var "command=phantom projects process raw.mp4 --bgm lofi-chill" \
  --var "output=[dim]Trimming silences...\n[green]final.mp4 written"

Boxes that light in turn

Two to six steps joined by arrows: the agent loop, a request on its way to a database, or this whole pipeline in four words.

phantom broll clip flow -o flow.mp4 \
  --cue "the whole thing is four steps" \
  --var "steps=Record|Fix mistakes|Process|Publish"

A list that ticks itself

Items get a tick or a cross as each one is said. Good for what to review, what never to automate, and what counts as done.

phantom broll clip checklist -o ready.mp4 \
  --cue "ready to schedule when" \
  --var "title=Ready to schedule when" \
  --var "items=final.mp4 exists|metadata.json is complete|a thumbnail is there"

All 54 commands,
in eight groups

Read the source

phantom projects The pipeline

  • status See every video and its next stepphantom projects status [--pending] [--finished] [--less] [--json] Google Sheets
  • process Turn a raw recording into final.mp4phantom projects process <raw_video> [--bgm <track>] [--force] ffmpeg, Silero VAD
  • newvideo Create a project folderphantom projects newvideo <project_name>
  • cleanup Archive everything but the mediaphantom projects cleanup [<project_path>] [--full]

phantom edit Audio and video

  • trim-silences Cut the pauses out of a recordingphantom edit trim-silences <video_path> Silero VAD
  • process-audio Denoise and normalise to −16 LUFSphantom edit process-audio [-R|--recursive] <path> DeepFilterNet, loudnorm
  • add-bgm Loop a music track under a videophantom edit add-bgm <video> <bgm_track> [--volume <percentage>] ffmpeg
  • caption Transcribe and burn in captionsphantom edit caption <video_path> [--preset shorts|longs] Whisper on Groq
  • transcribe-cloud Write an .srt from the audiophantom edit transcribe-cloud <video_path> Whisper on Groq
  • inspect-video Scan for black and frozen framesphantom edit inspect-video <video_path> [--black-duration <s>] ffprobe
  • review-audio Flag audio problems before publishingphantom edit review-audio <video_or_audio_path>
  • downscale Bring a video down to 1080pphantom edit downscale <video_path> [-o <output>] ffmpeg

phantom meta Titles and metadata

  • create Write the whole metadata.jsonphantom meta create [<project_path>] [--skip-ai] Claude
  • titles Generate and score title ideasphantom meta titles [--traffic search|browse|both] [--channel-size small|mid|large] Claude
  • approve-title List the titles and approve onephantom meta approve-title <project> [<rank_or_title>]
  • desc Write a two or three sentence descriptionphantom meta desc [<project_path>] Claude
  • tweet Draft the launch tweet, under 240 charactersphantom meta tweet [<project_path>] Claude
  • timestamps Turn captions into chaptersphantom meta timestamps [<captions_srt_path>] Claude
  • resources List the links the video promisedphantom meta resources [<project_path>]
  • recommend Pick the videos to send viewers to nextphantom meta recommend [<project_path>]
  • prepare-recommendations Index the channel for recommendationsphantom meta prepare-recommendations [<channel>] [--limit <int>] [--fresh] YouTube Data API
  • read Print a project’s metadataphantom meta read [<project_or_video_path>] [--json]
  • manual Fill in the metadata by handphantom meta manual <video_path>

phantom publish Posting

  • yt Upload with thumbnail, playlist and schedulephantom publish yt <video_path> [--playlist <name>] [--title <rank_or_title>] YouTube Data API
  • x Post a tweet, with an image if you likephantom publish x [--image] [--substack] <tweet_content>
  • substack Post a Substack notephantom publish substack [<text>] [-i <images>...] [-v <video>] Playwright
  • linkedin Share a LinkedIn postphantom publish linkedin [<text>] [-f <file>] [--url <url>]
  • devto Publish, schedule or draft from Markdownphantom publish devto <markdown_file> [--tags <tags>...] [--schedule "YYYY-MM-DD HH:MM"]
  • email Send the broadcast to subscribersphantom publish email --title <title> --description <desc> --url <url> Resend
  • yap Turn a spoken ramble into a postphantom publish yap --platform <linkedin|substack>

phantom research Reddit and Skool

  • reddit post Scrape a post and its commentsphantom research reddit post <url> [--max-comments <int>] [--json] Playwright
  • reddit analyze Read the mood of a threadphantom research reddit analyze <url> [--json] Groq
  • reddit sub Scrape a subreddit feedphantom research reddit sub <subreddit> [--best|--hot] [--limit <int>] Playwright
  • reddit search Scrape search resultsphantom research reddit search <query> [--sort <sort>] [--time <time>] Playwright
  • reddit profile Scrape a user’s posts and commentsphantom research reddit profile <username_or_url> [--posts <int>] Playwright
  • reddit serve Answer scrape requests as JSON linesphantom research reddit serve [--port <int>] [--headless]
  • skool post Scrape a Skool postphantom research skool post <url> [--max-comments <int>] [--json] Playwright
  • skool community Scrape a community’s feedphantom research skool community <url_or_slug> [--limit <int>] Playwright

phantom ideas What to make next

  • from-comments Turn a video’s comments into ideasphantom ideas from-comments <video> [--limit <int>] [--order relevance|time] Claude
  • from-outlier Turn an outlier video into ideasphantom ideas from-outlier <video> [--fresh-summary] [--json]
  • export Send ideas to the Google Sheetphantom ideas export [--file <path> | --idea <text>] Google Sheets
  • archive Archive the ideas already reviewedphantom ideas archive [--dry-run] Google Sheets

phantom yt The channel

  • info Read a video’s numbers from Studiophantom yt info <video> [--fresh] [--days <int>] YouTube Studio
  • channel-stats Read the channel’s 28-day figuresphantom yt channel-stats [--fresh] [--history] YouTube Studio
  • endscreen Add an end screen from a templatephantom yt endscreen <video>... [--template <name>]
  • add-to-playlist Put a video in a playlistphantom yt add-to-playlist <video> <playlist_id_or_name> YouTube Data API
  • titles Print a channel’s titlesphantom yt titles [<channel>] [--limit <int>] [--numbered] YouTube Data API
  • count-uploads Count uploads over 7, 14 and 30 daysphantom yt count-uploads [<channel>] [--fresh] YouTube Data API
  • summarize Summarise any videophantom yt summarize <video> [--model <model>]
  • comments Scrape a video’s commentsphantom yt comments <video> [--limit <int>]

phantom broll B-roll clips

  • list Show the templates and their variablesphantom broll list [<template>]
  • find Check where a phrase is saidphantom broll find <phrase> [--srt <word_srt>] [--after <timestamp>]
  • render Render every clip in a cue filephantom broll render <cue_file.json> [--only <file>...] [--dry-run] HyperFrames
  • clip Render one clip from flagsphantom broll clip <template> -o <output.mp4> [--cue <phrase>] [--var key=value...] HyperFrames

All of it is on GitHub. Read it, fork it, or lift the one script you need.