Skip to main content

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)
  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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

viralvideo-0.1.0.tar.gz (2.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

viralvideo-0.1.0-py3-none-any.whl (2.0 MB view details)

Uploaded Python 3

File details

Details for the file viralvideo-0.1.0.tar.gz.

File metadata

  • Download URL: viralvideo-0.1.0.tar.gz
  • Upload date:
  • Size: 2.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for viralvideo-0.1.0.tar.gz
Algorithm Hash digest
SHA256 79bc9600f859ca68240b972c6a46b4ac2519b71251d7940c026f460636b3ef69
MD5 c790c49505f2c3af0eefd35587eca193
BLAKE2b-256 ed9525e7ae42f986ccf4e71cdf4b57bc9cc98289f87ee292c545a18d5f7ae784

See more details on using hashes here.

Provenance

The following attestation bundles were made for viralvideo-0.1.0.tar.gz:

Publisher: publish.yml on maxylev/viralvideo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file viralvideo-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: viralvideo-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 2.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for viralvideo-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e2a4be1ce2d912e8746006098bef4809fa1f93dc13e2f3bc2be9fa8bc4674c84
MD5 a96d005f274404c377f92e45c1f9782e
BLAKE2b-256 69f3c91fc1acd9d3b93d5a275284b7420af9803f2688843f8e16792242c27235

See more details on using hashes here.

Provenance

The following attestation bundles were made for viralvideo-0.1.0-py3-none-any.whl:

Publisher: publish.yml on maxylev/viralvideo

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page