Metadata-Version: 2.4
Name: viralvideo
Version: 0.1.0
Summary: Generate TikTok-style viral videos from templates
Author: viralvideo contributors
License-Expression: MIT
Keywords: tiktok,video,upscale,bass-pulse,short-form
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Requires-Dist: requests>=2.28
Requires-Dist: yt-dlp>=2024.1
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build>=0.10; extra == "dev"
Requires-Dist: twine>=4.0; extra == "dev"

# viralvideo

<center>


https://github.com/user-attachments/assets/deb0e499-ac0a-4aec-bdab-f0b7923f3470


</center>

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

```bash
pip install viralvideo
```

Or from source:

```bash
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](https://ffmpeg.org) | All stages                       |
| **ffprobe**            | Included with ffmpeg                                      | All stages                       |
| **Node.js**            | `brew install node` or [nodejs.org](https://nodejs.org)   | yt-dlp YouTube signature solving |
| **OpenRouter API key** | [openrouter.ai](https://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

```bash
# 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

```bash
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:

```bash
# 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

```bash
# 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:

```bash
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`:

```bash
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

```bash
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
