Skip to content

feat(shorts): reproducible Short builder - #5

Open
jeevesh2515 wants to merge 2 commits into
mainfrom
feat/short-builder
Open

jeevesh2515 wants to merge 2 commits into
mainfrom
feat/short-builder

Conversation

@jeevesh2515

@jeevesh2515 jeevesh2515 commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Why

Everything downstream of clip generation was being done by hand — beat-aligned cutting, caption timing, music ducking, watermarking, loudness mastering. All of it is deterministic, so it belongs in the repo rather than in a chat transcript.

Generation is the commodity. This is the part worth owning, and it runs free on any machine with ffmpeg.

Usage

export SPEACHES_API_URL=https://speaches-production-293a.up.railway.app
npx tsx scripts/build-short.ts scripts/shorts/example.json

Takes a JSON config plus a folder of clips — Higgsfield, Veo, stock, the source is irrelevant — and emits an upload-ready MP4 with a QC contact sheet.

What it does

  1. Narration via Speaches /v1/audio/speech/timed, returning per-word timings. Fails loudly when timings are absent rather than silently degrading — edge-tts ≥7 returns none unless boundary="WordBoundary" is passed, which produces perfect audio and an empty timing list.
  2. Cuts land on narrative beats. Each scene declares the last spoken word of its beat; cut points come from real word timings, not a fixed grid.
  3. Stretches only when a beat outruns its clip, and warns above 1.35×. This matters: the glass-rain shot initially needed 1.55×, which made "five thousand miles an hour" look slow — the picture contradicting the narration. The fix is moving a sentence to a neighbouring scene, not accepting the stretch.
  4. reverse + useTail turns a reused opening clip into a closing pull-back whose final frame is the opening frame — a free closing shot and a seamless loop.
  5. ASS karaoke captions via libass, optional documentary locator card, music sidechain-ducked under speech, watermark burned top-left (the only Shorts corner not covered by platform UI), mastered to −14 LUFS.
  6. Verifies rather than assumes — emits a contact sheet and a measured loop-seam delta.

Verification

Run against the HD 189733b Short previously assembled by hand:

Hand-built Builder
Runtime 25.77s 25.77s
Size 14.8MB 14.8MB
Loop seam 3.0/255 3.0/255
Scene timings 7.90 / 7.90 / 6.93 / 3.06 identical

tsc clean on both the server project and the script.

Also

scripts/shorts/README.md covers Higgsfield CLI setup and credit budgeting — notably: generate the still first and animate second, since images cost a fraction of a clip and re-rolling video prompts blind is how a credit balance disappears.

Clips, music, renders and brand assets are gitignored; only the code and config ship.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a deterministic workflow for creating polished YouTube Shorts with narration, timed scenes, captions, locator text, watermarking, background music, and audio balancing.
    • Added support for clip adjustments, looping, speed changes, final MP4 rendering, and quality-control previews.
    • Included a ready-to-use example configuration for an HD 189733b short.
  • Documentation

    • Added setup instructions, configuration guidance, processing details, asset requirements, and troubleshooting tips for short-video creation.

Beat-aligned cutting, word-accurate captions, ducked music, watermark and
-14 LUFS mastering, driven from a JSON config. Verified against the HD 189733b
Short built by hand: identical runtime, size and 3.0/255 loop seam.
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
shorty Ready Ready Preview Aug 24, 2026 3:04am

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e63ce5ef-022a-4a41-836d-3d4aa698790d

📥 Commits

Reviewing files that changed from the base of the PR and between 2300164 and 91957fa.

📒 Files selected for processing (1)
  • scripts/build-short.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/build-short.ts

📝 Walkthrough

Walkthrough

The PR adds a deterministic Shorts builder. It generates timed narration, plans scenes, assembles video, adds captions and branding, mixes audio, encodes MP4 output, and performs quality checks.

Changes

Shorts pipeline

Layer / File(s) Summary
Configuration, narration, and edit planning
scripts/build-short.ts, scripts/shorts/example.json, scripts/shorts/README.md
Defines video configuration, integrates Speaches timed narration, validates word timings, and derives scene boundaries from narration tokens.
Scene, caption, and audio-video assembly
scripts/build-short.ts, scripts/shorts/README.md
Renders and stitches scenes, applies playback adjustments, creates ASS captions and locator overlays, composites the watermark, mixes optional music, and encodes the final MP4.
CLI orchestration and quality checks
scripts/build-short.ts, .gitignore, scripts/shorts/README.md
Runs the build stages, reports failures, generates contact-sheet output, calculates the loop-seam metric, documents outputs and local assets, and ignores local build artifacts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Speaches
  participant EditPlanner
  participant ffmpeg
  participant QualityChecks
  CLI->>Speaches: request timed narration
  Speaches-->>CLI: return audio and word timings
  CLI->>EditPlanner: plan scene boundaries
  EditPlanner-->>CLI: return scene plan
  CLI->>ffmpeg: render scenes and final MP4
  ffmpeg-->>CLI: return encoded video
  CLI->>QualityChecks: probe output and compare loop frames
  QualityChecks-->>CLI: return QC metrics
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: a reproducible Shorts builder.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/short-builder

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/build-short.ts`:
- Line 257: Update the final FFmpeg muxing commands in the build flow associated
with paddedVoice and total to remove the -shortest option, allowing the existing
video stream to retain the configured tail and planned final scene duration.
Apply the same change to all corresponding occurrences, including the
additionally affected sites.
- Around line 349-354: Validate the raw process.argv[2] argument before passing
it to resolve in the build configuration loading flow. Update the guard around
configPath so a missing or empty argument immediately prints the usage message
and exits, while preserving path existence validation for provided arguments.

In `@scripts/shorts/README.md`:
- Around line 10-13: Update the setup instructions in the shorts README before
the example build command to list the required local ffmpeg installation with
libass support and a Speaches endpoint implementing /v1/audio/speech/timed with
word-timing responses. Keep the existing environment variable and build command
unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cea1c99a-61f1-49d2-9100-a785ff9dca27

📥 Commits

Reviewing files that changed from the base of the PR and between d12d959 and 2300164.

📒 Files selected for processing (4)
  • .gitignore
  • scripts/build-short.ts
  • scripts/shorts/README.md
  • scripts/shorts/example.json

Comment thread scripts/build-short.ts
async function mixAndRender(cfg: ShortConfig, stitched: string, voicePath: string, assPath: string, total: number, configDir: string, workDir: string, outPath: string) {
const preroll = cfg.preroll ?? 0.6
const paddedVoice = join(workDir, 'voice-padded.mp3')
await ff(['-y', '-i', voicePath, '-af', `adelay=${Math.round(preroll * 1000)}|${Math.round(preroll * 1000)}`, '-c:a', 'libmp3lame', '-q:a', '2', paddedVoice])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not truncate the configured tail.

voice-padded.mp3 ends after the narration. amix=duration=first preserves that end time. -shortest then ends the MP4 before cfg.tail and before the planned final scene duration.

Remove -shortest, or pad the final audio stream through total. The video stream already has the required duration.

Proposed fix
-    '-r', '30', '-shortest', '-movflags', '+faststart', outPath,
+    '-r', '30', '-movflags', '+faststart', outPath,

Also applies to: 300-300, 313-313

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build-short.ts` at line 257, Update the final FFmpeg muxing commands
in the build flow associated with paddedVoice and total to remove the -shortest
option, allowing the existing video stream to retain the configured tail and
planned final scene duration. Apply the same change to all corresponding
occurrences, including the additionally affected sites.

Comment thread scripts/build-short.ts
Comment on lines +349 to +354
const configPath = resolve(process.argv[2] || '')
if (!configPath || !existsSync(configPath)) {
console.error('usage: npx tsx scripts/build-short.ts <config.json>')
process.exit(1)
}
const cfg = JSON.parse(readFileSync(configPath, 'utf8')) as ShortConfig

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the raw CLI argument before calling resolve.

When the argument is absent, resolve('') returns the current directory. existsSync(configPath) then succeeds, and readFileSync fails with a directory error instead of showing usage.

Proposed fix
 async function main() {
-  const configPath = resolve(process.argv[2] || '')
-  if (!configPath || !existsSync(configPath)) {
+  const configArg = process.argv[2]
+  if (!configArg) {
     console.error('usage: npx tsx scripts/build-short.ts <config.json>')
     process.exit(1)
   }
+  const configPath = resolve(configArg)
+  if (!existsSync(configPath)) {
+    console.error(`config not found: ${configPath}`)
+    process.exit(1)
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const configPath = resolve(process.argv[2] || '')
if (!configPath || !existsSync(configPath)) {
console.error('usage: npx tsx scripts/build-short.ts <config.json>')
process.exit(1)
}
const cfg = JSON.parse(readFileSync(configPath, 'utf8')) as ShortConfig
const configArg = process.argv[2]
if (!configArg) {
console.error('usage: npx tsx scripts/build-short.ts <config.json>')
process.exit(1)
}
const configPath = resolve(configArg)
if (!existsSync(configPath)) {
console.error(`config not found: ${configPath}`)
process.exit(1)
}
const cfg = JSON.parse(readFileSync(configPath, 'utf8')) as ShortConfig
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build-short.ts` around lines 349 - 354, Validate the raw
process.argv[2] argument before passing it to resolve in the build configuration
loading flow. Update the guard around configPath so a missing or empty argument
immediately prints the usage message and exits, while preserving path existence
validation for provided arguments.

Comment thread scripts/shorts/README.md
Comment on lines +10 to +13
```bash
export SPEACHES_API_URL=https://speaches-production-293a.up.railway.app
npx tsx scripts/build-short.ts scripts/shorts/example.json
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the local media prerequisites.

This command requires ffmpeg with libass support. It also requires a Speaches endpoint that supports /v1/audio/speech/timed and returns word timings. Add these prerequisites before the run command so users can validate their environment before the build fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/shorts/README.md` around lines 10 - 13, Update the setup instructions
in the shorts README before the example build command to list the required local
ffmpeg installation with libass support and a Speaches endpoint implementing
/v1/audio/speech/timed with word-timing responses. Keep the existing environment
variable and build command unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant