Skip to content

maxylev/viralvideo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

viralvideo

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.

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)
  1. Analyze the template audio for bass notes using hysteresis threshold on low-frequency RMS
  2. Download each YouTube URL at its start offset via yt-dlp
  3. Crop each video to 9:16 using the crop parameter for vertical framing
  4. Segment: play during vocals/silence, freeze on the last frame during bass, outro with customizable text for seconds 20–24
  5. Enhance freeze frames via OpenRouter API (final stage only)
  6. 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/right shift horizontal crop
  • Portrait (narrower than 9:16): top/bottom shift 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

About

Create a viral video for TikTok, YouTube Shorts, and Instagram Reels.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages