Generate TikTok-style viral videos from templates
Project description
viralvideo
https://github.com/user-attachments/assets/deb0e499-ac0a-4aec-bdab-f0b7923f3470
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.
Install
pip install viralvideo
Or from source:
git clone https://github.com/maxylev/viralvideo.git
cd viralvideo
pip install -e .
External dependencies
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 |
Quick start
# 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 final
Command reference
viralvideo 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) |
Stage values
| 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.
Resolution examples
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)
Audio templates
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 |
Model selection
# 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-flash
Outro text
The 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 text
Output directory
By default, outputs go to output/ next to the input file. Redirect with --output:
viralvideo upscale -i urls.txt -s test -o /tmp/my_video
URL file format
One 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%. |
Examples
# 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.
How it works
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
Crop behavior
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.
Output files
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
Cookies for yt-dlp
If downloads fail due to authentication, place a cookies.txt (Netscape format) next to your input file. It will be picked up automatically.
Development
pip install -e ".[dev]"
pytest
Project structure
src/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
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters