final_video.mp4
Generate TikTok-style vertical videos (9:16) that sync clips to a music template's bass pattern. When bass drops, the last frame freezes; when it lifts, the next clip plays. Ends with a 4-second SUBSCRIBE outro.
pip install viralvideoOr from source:
git clone https://github.com/maxylev/viralvideo.git
cd viralvideo
pip install -e .These must be installed separately — they are not bundled with the Python package:
| Tool | Install | Needed for |
|---|---|---|
| ffmpeg | brew install ffmpeg or ffmpeg.org |
All stages |
| ffprobe | Included with ffmpeg | All stages |
| Node.js | brew install node or nodejs.org |
yt-dlp YouTube signature solving |
| OpenRouter API key | openrouter.ai | --stage final only |
The CLI checks for ffmpeg and ffprobe at startup and exits with install instructions if missing.
Installed automatically via pip:
| Package | Purpose |
|---|---|
| yt-dlp | YouTube video downloading |
| numpy / scipy | Bass detection audio analysis |
| requests | OpenRouter API calls |
# Create a URL file (see URL format below)
cat > urls.txt << 'EOF'
https://www.youtube.com/watch?v=VIDEO_ID?start=00:01:16&crop=left:15
https://www.youtube.com/watch?v=VIDEO_ID?start=00:00:30
EOF
# Test run — preview timing and cropping, no API calls
viralvideo upscale -i urls.txt -s test
# Final run — enhance freeze frames, produce final video
viralvideo upscale -i urls.txt -s finalviralvideo upscale [OPTIONS]
Required:
| Option | Flag | Description |
|---|---|---|
--input |
-i |
Text file with YouTube URLs (one per line) |
--stage |
-s |
test, final, or final:RANGE |
Optional:
| Option | Flag | Default | Description |
|---|---|---|---|
--audio |
-a |
bass_pulse |
Audio template: built-in name or path to .wav/.mp4 |
--resolution |
-r |
1080x1920 |
Output resolution in WxH format |
--output |
-o |
output/ next to input file |
Output directory |
--outro-text |
SUBSCRIBE |
Text shown on the outro screen (seconds 20–24) | |
--model |
-m |
google/gemini-3.1-flash-image-preview |
OpenRouter model for frame enhancement |
--api-key |
$OPENROUTER_API_KEY |
OpenRouter API key (or set env var) |
| Value | Behavior |
|---|---|
test |
Build video with raw freeze frames — no API calls |
final |
Enhance all freeze frames via OpenRouter |
final:1 |
Enhance only video 1, reuse cached frames for the rest |
final:2-4 |
Enhance videos 2, 3, 4; reuse cached for 1 and 5 |
final:1,3-5 |
Enhance videos 1, 3, 4, 5 |
Specifying a video number in the range always calls the API, overwriting any cached enhanced frame. Videos not in the range reuse a cached enhanced_*.png if it exists.
viralvideo upscale -i urls.txt -s test -r 1080x1920 # TikTok 1080p
viralvideo upscale -i urls.txt -s test -r 720x1280 # 720p
viralvideo upscale -i urls.txt -s test -r 540x960 # 540p (fast preview)The --audio flag accepts a built-in name or a file path:
# Built-in (ships with the package)
viralvideo upscale -i urls.txt -s test -a bass_pulse
# Custom audio
viralvideo upscale -i urls.txt -s test -a /path/to/my_template.wav
viralvideo upscale -i urls.txt -s test -a /path/to/my_template.mp4| Name | Description |
|---|---|
bass_pulse |
5-bass-note pattern at ~2s intervals, 24s total |
# Default model
viralvideo upscale -i urls.txt -s final -m google/gemini-3.1-flash-image-preview
# Alternative model
viralvideo upscale -i urls.txt -s final -m google/gemini-2.5-flashThe last 4 seconds show a black screen with centered text. Customize or disable:
viralvideo upscale -i urls.txt -s test --outro-text "FOLLOW FOR MORE"
viralvideo upscale -i urls.txt -s test --outro-text "" # black screen, no textBy default, outputs go to output/ next to the input file. Redirect with --output:
viralvideo upscale -i urls.txt -s test -o /tmp/my_videoOne URL per line. Each URL supports start and crop parameters:
https://www.youtube.com/watch?v=VIDEO_ID?start=HH:MM:SS&crop=SIDE[:PCT]
| Parameter | Required | Description |
|---|---|---|
start |
yes | Playback start offset (HH:MM:SS, MM:SS, or seconds) |
crop |
no | Crop alignment: left, right, center, top, bottom |
crop pct |
no | Percentage shift from center. 0 = center, 100 = fully to that edge. Bare crop=left defaults to 100%. |
# Center crop (default)
https://www.youtube.com/watch?v=abc123?start=00:01:16
# Full left crop
https://www.youtube.com/watch?v=abc123?start=00:01:16&crop=left
# 15% shift left from center
https://www.youtube.com/watch?v=abc123?start=00:01:16&crop=left:15
# Full bottom crop
https://www.youtube.com/watch?v=abc123?start=00:00:30&crop=bottom
# 30% shift down from center
https://www.youtube.com/watch?v/abc123?start=00:00:30&crop=bottom:30
5 URLs = 5 videos, one per bass note detected in the template.
Template audio
──────────────╥────────────────────╥──────────────────╥─── ...
║ BASS ║ BASS ║
║ ║ ║
Video ║ ║ ║
── vid 1 ────║── freeze ──────────║── vid 2 ──────────║── freeze ── ...
║ (last frame vid 1) ║ ║ (last frame vid 2)
- Analyze the template audio for bass notes using hysteresis threshold on low-frequency RMS
- Download each YouTube URL at its
startoffset viayt-dlp - Crop each video to 9:16 using the
cropparameter for vertical framing - Segment:
playduring vocals/silence,freezeon the last frame during bass,outrowith customizable text for seconds 20–24 - Enhance freeze frames via OpenRouter API (final stage only)
- Assemble all segments at 30fps at the target resolution, mux with audio, trim to 24 seconds
All videos are cropped to 9:16. The crop parameter controls framing:
- Landscape (wider than 9:16):
left/rightshift horizontal crop - Portrait (narrower than 9:16):
top/bottomshift vertical crop - Percentage:
crop=left:0= center,crop=left:50= halfway,crop=left:100= fully left
Changing the crop parameter changes the cached filename, so re-running with a different crop will re-crop automatically.
Outputs go to an output/ directory next to the input file by default, or wherever --output points:
output/
├── test/ # --stage test
│ ├── source_*.mp4 Downloaded videos
│ ├── cropped_*.mp4 9:16 cropped videos
│ ├── seg_*.mp4 Individual segments
│ ├── play_last_*.png Extracted last frames
│ └── test_video.mp4 Final test video
└── final/ # --stage final
├── (same as test/)
├── enhanced_*.png AI-upscaled freeze frames
└── final_video.mp4 Final enhanced video
If downloads fail due to authentication, place a cookies.txt (Netscape format) next to your input file. It will be picked up automatically.
pip install -e ".[dev]"
pytestsrc/viralvideo/
├── __init__.py Version + built-in audio registry
├── cli.py CLI entry point + dependency checks
├── upscale.py Upscale video type (play/freeze/bass)
├── audio.py Bass detection & segment analysis
├── downloader.py YouTube download & URL parsing
├── enhance.py OpenRouter frame enhancement
├── ffmpeg_tools.py ffmpeg/ffprobe wrappers & crop logic
└── assets/audio/
└── bass_pulse.wav Built-in audio template
MIT