feat(shorts): reproducible Short builder - #5
jeevesh2515 wants to merge 2 commits into
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe 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. ChangesShorts pipeline
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
.gitignorescripts/build-short.tsscripts/shorts/README.mdscripts/shorts/example.json
| 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]) |
There was a problem hiding this comment.
🎯 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.
| 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 |
There was a problem hiding this comment.
🎯 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.
| 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.
| ```bash | ||
| export SPEACHES_API_URL=https://speaches-production-293a.up.railway.app | ||
| npx tsx scripts/build-short.ts scripts/shorts/example.json | ||
| ``` |
There was a problem hiding this comment.
📐 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.
91957fa to
fd24db5
Compare
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.jsonTakes 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
/v1/audio/speech/timed, returning per-word timings. Fails loudly when timings are absent rather than silently degrading — edge-tts ≥7 returns none unlessboundary="WordBoundary"is passed, which produces perfect audio and an empty timing list.reverse+useTailturns a reused opening clip into a closing pull-back whose final frame is the opening frame — a free closing shot and a seamless loop.Verification
Run against the HD 189733b Short previously assembled by hand:
tscclean on both the server project and the script.Also
scripts/shorts/README.mdcovers 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
Documentation