Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,59 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]
## [0.0.33] - 2026-03-23

### Added
- Branding overlay on rendered shorts: shows "reeln v{version} by https://streamn.dad" with a black-bordered white text at the top of the video for the first ~5 seconds with a smooth fade-out — enabled by default, configurable via `branding` config section, disable with `--no-branding` CLI flag
- `BrandingConfig` model (`enabled`, `template`, `duration`) for per-user branding customization
- Bundled `branding.ass` ASS template with `\fad(300,800)` animation and black outline for visibility over any background
- `--no-branding` flag on `render short` and `render preview` commands
- Branding renders only on the first iteration in multi-iteration mode
- Cross-fade transitions between iterations: uses ffmpeg `xfade` + `acrossfade` filters for smooth 0.5s fade transitions instead of hard cuts, with automatic fallback to concat demuxer if xfade fails
- Smart zoom support in the iteration pipeline: `--smart --iterate` now extracts frames once upfront and passes the zoom path through to each iteration's `plan_short()` call
- `speed_segments` in render profiles for variable speed within a single clip — e.g., normal speed → slow motion → normal speed, using the proven split/trim/concat ffmpeg pattern
- `--player-numbers` (`-n`) flag on `render short`, `render preview`, and `render apply` for roster-based player lookup: accepts comma-separated jersey numbers (e.g., `--player-numbers 48,24,2`), looks up names from the team roster CSV, and populates goal scorer and assist overlays automatically
- `--event-type` flag on render commands for scoring team resolution: `HOME_GOAL`/`AWAY_GOAL` determines which team's roster to look up
- `RosterEntry` data model and `load_roster()` / `lookup_players()` / `resolve_scoring_team()` core functions for roster management
- `GameInfo` now persists `level`, `home_slug`, and `away_slug` when `game init --level` is used, enabling roster lookup during rendering
- `build_overlay_context()` accepts optional `scoring_team` parameter to override the default (home team)
- Smart target zoom (`--crop smart`): extracts frames from clips, emits `ON_FRAMES_EXTRACTED` hook for vision plugins (e.g. reeln-plugin-openai) to detect action targets, then builds dynamic ffmpeg crop expressions that smoothly pan across detected targets
- `ZoomPoint`, `ZoomPath`, and `ExtractedFrames` data models for smart zoom contracts
- `ON_FRAMES_EXTRACTED` lifecycle hook for plugins to analyze extracted video frames
- `extract_frames()` method on the Renderer protocol and FFmpegRenderer for frame extraction
- `build_piecewise_lerp()` and `build_smart_crop_filter()` for dynamic ffmpeg crop expressions
- `--zoom-frames` option on `render short` and `render preview` (default 5, range 1-20)
- Zoom debug output: `debug/zoom/zoom_path.json` and frame symlinks when `--debug` is used with smart crop
- Smart pad mode (`--crop smart_pad`): follows action vertically like smart zoom but keeps black bars instead of filling the entire frame — falls back to static pad when no vision plugin provides data
- `build_smart_pad_filter()` for dynamic vertical pad positioning based on zoom path center_y
- Debug crosshair annotations: extracted frames in `debug/zoom/` now include annotated copies with green crop box and red crosshair overlays showing detected center points
- `--scale` option on `render short` and `render preview` (0.5-3.0, default 1.0): zooms in by scaling up the intermediate frame before crop/pad — works with all crop modes including smart tracking
- `--smart` flag on `render short` and `render preview`: enables smart tracking via vision plugin as an orthogonal option, composable with `--crop pad|crop` and `--scale`
- `build_overflow_crop_filter()` for pad + scale > 1.0: crops overflow after scale-up before padding
- Automatic fallback from smart crop to center crop (or smart_pad to static pad) when no vision plugin provides zoom data
- `--no-enforce-hooks` global CLI flag to temporarily disable registry-based hook enforcement for plugins
- `game finish` now relocates segment and highlights outputs from the shared output directory into `game_dir/outputs/`, preventing file collisions across multiple games per day
- `game init` now blocks with a clear error if an unfinished game exists — run `reeln game finish` first
- `GameState` tracks `segment_outputs` and `highlights_output` for file relocation
- `find_unfinished_games()` helper scans for active game directories
- `relocate_outputs()` helper moves output files into the game directory
- `reeln doctor` now collects and runs health checks from plugins that implement `doctor_checks()`
- `doctor` capability added to plugin duck-type detection
- `--tournament` CLI flag on `game init` for optional tournament name/context — flows through to plugins via hook context and overlay templates
- `tournament` and `level` fields now included in template context (`build_base_context()`), available as `{{tournament}}` and `{{level}}` in ASS subtitle templates

### Changed
- Scale, framing (crop/pad), and smart tracking are now orthogonal axes — any combination works without dedicated enum values
- Short/preview renders now output to a `shorts/` subdirectory by default (e.g., `period-2/shorts/clip_short.mp4`) to prevent segment merges from picking up rendered files

### Deprecated
- `--crop smart` — use `--crop crop --smart` instead (still works, shows deprecation warning)
- `--crop smart_pad` — use `--crop pad --smart` instead (still works, shows deprecation warning)

### Fixed
- `team_level` in overlay context now uses the actual team level (e.g., "2016", "bantam") instead of the sport name — previously showed "hockey" instead of the level
- Segment merge and highlights merge output extension now matches input files instead of being hardcoded to `.mkv`
- Highlights merge now discovers segment files with any video extension (`.mp4`, `.mkv`, `.mov`, etc.), not just `.mkv`

## [0.0.32] - 2026-03-15

Expand Down
68 changes: 66 additions & 2 deletions docs/cli/render.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ reeln render short [CLIP] [OPTIONS]
| `--crop`, `-c` | Crop mode: `pad` (fit with bars) or `crop` (fill and trim). Default: `pad` |
| `--anchor`, `-a` | Crop anchor: `center`, `top`, `bottom`, `left`, `right`, or custom `x,y` (0.0–1.0). Default: `center` |
| `--pad-color` | Pad bar color (default: `black`) |
| `--scale` | Content scale, 0.5–3.0 (default: `1.0`). Values > 1.0 zoom in on the source. |
| `--smart` | Enable smart tracking via vision plugin (requires an `ON_FRAMES_EXTRACTED` handler). |
| `--zoom-frames` | Number of frames to extract for smart zoom analysis, 1–20 (default: `5`). |
| `--speed` | Playback speed, 0.5–2.0 (default: `1.0`) |
| `--lut` | LUT file for color grading (`.cube` or `.3dl`) |
| `--subtitle` | ASS subtitle overlay file (`.ass`) |
Expand All @@ -29,8 +32,11 @@ reeln render short [CLIP] [OPTIONS]
| `--render-profile`, `-r` | Named render profile from config |
| `--player` | Player name for overlay (populates `{{player}}` / `{{goal_scorer_text}}` in subtitle templates) |
| `--assists` | Assists, comma-separated (populates `{{goal_assist_1}}` / `{{goal_assist_2}}` in subtitle templates) |
| `--player-numbers`, `-n` | Jersey numbers: `scorer[,assist1[,assist2]]`. Looked up from team roster CSV. |
| `--event-type` | Event type for scoring team resolution (`HOME_GOAL`, `AWAY_GOAL`). Used with `--player-numbers`. |
| `--iterate` | Multi-iteration mode — apply iteration profiles from config |
| `--debug` | Write debug artifacts (ffmpeg commands, metadata) to `{game_dir}/debug/` |
| `--no-branding` | Disable the default branding overlay |
| `--profile` | Named config profile |
| `--config` | Explicit config file path |
| `--dry-run` | Show render plan without executing |
Expand All @@ -41,6 +47,23 @@ When `--iterate` is provided, reeln looks up the iteration profile list for the

When `--player` and/or `--assists` are provided, they populate the overlay template context — useful for rendering goal overlays without going through the game event tagging system. These flags override any player/assists data from linked game events. They require a `--render-profile` with a `subtitle_template` to take effect.

#### Player number roster lookup

When `--player-numbers` is provided (e.g. `--player-numbers 48,24,2`), reeln looks up player names from the team roster CSV:

1. The first number is the **goal scorer**, remaining numbers are **assists**
2. The **scoring team** is determined from `--event-type`: `HOME_GOAL`/`home_goal` → home team, `AWAY_GOAL`/`away_goal` → away team, anything else defaults to home
3. The team profile is loaded using `level` and team slug from `game.json`
4. Player names are formatted as `#48 Smith` (number + last name)
5. Unknown numbers fall back to `#48` with a warning

Requirements:
- A game directory (`--game-dir` or auto-detected)
- The game must have been initialized with `--level` (to store team profile references)
- The team profile must have a `roster_path` pointing to a valid CSV file

If `--player` or `--assists` are also provided, they take precedence over the roster lookup.

Builds an ffmpeg filter graph to reframe the input clip as a vertical short suitable for social media platforms (YouTube Shorts, Instagram Reels, TikTok).

#### Auto-discovery
Expand All @@ -64,9 +87,50 @@ Encoding parameters (codec, preset, CRF, audio codec, audio bitrate) flow from t
- **`pad`** — Fits the entire source frame into the target dimensions with letterbox/pillarbox bars. Nothing is cropped. The `--pad-color` option controls bar color.
- **`crop`** — Fills the target dimensions by cropping the source. The `--anchor` option controls which region of the source is kept.

#### Smart tracking

Rendering has three orthogonal axes that compose independently:

- **Framing** (`--crop pad|crop`) — how the source fits the target dimensions
- **Scale** (`--scale`) — content zoom level
- **Tracking** (`--smart`) — dynamic crop/pan following the action

When `--smart` is enabled, reeln extracts frames from the clip and emits the `ON_FRAMES_EXTRACTED` hook. A vision plugin (e.g. `reeln-plugin-openai`) analyzes the frames and returns a zoom path — a sequence of (timestamp, center_x, center_y) points describing where the action is. The render filter chain then dynamically adjusts the crop or pad position to follow the action.

- **Smart crop** (`--crop crop --smart`) — fills the target by cropping, with the crop window tracking the action point.
- **Smart pad** (`--crop pad --smart`) — fits the source with pillarbox bars, panning horizontally to keep the action centered. Vertical position stays fixed (vertical panning is disorienting in pad mode).

If no vision plugin handles `ON_FRAMES_EXTRACTED`, `--smart` falls back to static center positioning with a warning.

:::{note}
The legacy crop modes `--crop smart` and `--crop smart_pad` still work but are deprecated. Use `--crop crop --smart` and `--crop pad --smart` instead.
:::

```bash
# Smart crop — fills 9:16, tracking the action
reeln render short clip.mkv --crop crop --smart

# Smart pad — fits with bars, panning horizontally
reeln render short clip.mkv --crop pad --smart

# Smart crop with zoom
reeln render short clip.mkv --crop crop --smart --scale 1.5

# More frames for finer tracking
reeln render short clip.mkv --smart --zoom-frames 10
```

#### Variable speed segments

For variable speed within a single clip (e.g., normal → slow motion → normal), use `speed_segments` in a render profile. This is a profile-only feature — there is no CLI flag. See {doc}`/guide/configuration` for details.

:::{note}
`speed_segments` cannot be combined with `--smart` tracking. Static crop/pad with speed segments works.
:::

#### Filter chain order

LUT (color grade) → speed (`setpts`) → scale → pad/crop → subtitle overlay.
LUT (color grade) → speed (`setpts`) → scale → overflow crop (pad + scale > 1.0) → crop/pad → final scale (crop only) → subtitle overlay.

**Examples:**

Expand Down Expand Up @@ -101,7 +165,7 @@ Generate a fast low-resolution preview of a clip.
reeln render preview [CLIP] [OPTIONS]
```

Accepts the same options as `render short` (including `--render-profile`, `--iterate`, and `--debug`). Produces a scaled-down, lower-quality version for quick review before committing to a full render.
Accepts the same options as `render short` (including `--render-profile`, `--iterate`, `--scale`, `--smart`, `--zoom-frames`, and `--debug`). Produces a scaled-down, lower-quality version for quick review before committing to a full render.

Preview differences:
- Uses `ultrafast` preset (vs `medium`)
Expand Down
125 changes: 125 additions & 0 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Named render profiles define reusable rendering parameter overrides. Add a `rend
| `crop_mode` | string | `"pad"` or `"crop"` (short-form only) |
| `anchor_x` | float | Crop anchor X position, 0.0–1.0 (short-form only) |
| `anchor_y` | float | Crop anchor Y position, 0.0–1.0 (short-form only) |
| `scale` | float | Content scale, 0.5–3.0 (default: 1.0). Values > 1.0 zoom in. |
| `smart` | bool | Enable smart tracking via vision plugin |
| `pad_color` | string | Pad bar color (short-form only) |
| `codec` | string | Video codec override |
| `preset` | string | Encoder preset override |
Expand All @@ -118,6 +120,35 @@ Named render profiles define reusable rendering parameter overrides. Add a `rend

All fields are optional — `null` or omitted means "inherit from base config".

#### Variable speed (speed_segments)

For variable-speed rendering within a single clip, use `speed_segments` instead of the scalar `speed` field. Each segment defines a speed and a source-time boundary:

```json
{
"render_profiles": {
"slowmo-middle": {
"speed_segments": [
{"until": 5.0, "speed": 1.0},
{"until": 8.0, "speed": 0.5},
{"speed": 1.0}
]
}
}
}
```

This plays the first 5 seconds at normal speed, then 3 seconds at half speed, then the rest at normal speed. The last segment must omit `until` (it runs to the end of the clip).

Rules:
- At least 2 segments required (use scalar `speed` for uniform speed)
- `until` values must be strictly increasing and positive
- Speeds must be in the range 0.25–4.0
- `speed` and `speed_segments` are mutually exclusive — set one or the other, not both
- `speed_segments` cannot be combined with `--smart` tracking

`speed_segments` is profile-only — there is no CLI flag for it. Configure it in a render profile and use `--render-profile` to apply it.

Profiles are used with `--render-profile` on `render short`, `render preview`, `render apply`, `game segment`, and `game highlights`.

#### Builtin templates
Expand Down Expand Up @@ -152,6 +183,37 @@ reeln render short clip.mkv --render-profile player-overlay \
reeln render short clip.mkv --iterate --game-dir . --event <event-id>
```

#### Smart tracking

Smart tracking (`--smart` or `"smart": true` in a profile) requires a vision plugin that handles the `ON_FRAMES_EXTRACTED` hook. Without one, `--smart` falls back to static center positioning with a warning.

The [reeln-plugin-openai](https://github.com/StreamnDad/reeln-plugin-openai) package provides smart zoom via OpenAI's vision API. Enable it in plugin settings:

```json
{
"plugins": {
"enabled": ["openai"],
"settings": {
"openai": {
"api_key": "sk-...",
"smart_zoom_enabled": true,
"smart_zoom_model": "gpt-4o"
}
}
}
}
```

When smart tracking is active, the render pipeline:

1. Extracts frames from the clip (`--zoom-frames` controls how many, default 5)
2. Emits `ON_FRAMES_EXTRACTED` — the vision plugin analyzes frames and returns a zoom path
3. Builds dynamic ffmpeg expressions that follow the action throughout the clip

Smart tracking composes with both crop modes:
- **crop + smart** — the crop window tracks the action point
- **pad + smart** — pillarbox bars pan horizontally to center the action (vertical position stays fixed)

### Iterations section

The `iterations` section maps event types to ordered lists of profile names. This is used for multi-iteration rendering where each event type gets a different sequence of render passes:
Expand Down Expand Up @@ -183,6 +245,28 @@ reeln game highlights --iterate

Each profile in the list is applied in order, and the iteration outputs are concatenated end-to-end into a single final file. For example, a goal event with profiles `["fullspeed", "slowmo", "goal-overlay"]` produces a video that plays the clip at full speed, then slow motion, then with the goal overlay — all stitched together automatically.

### Branding section

The `branding` section controls the branding overlay shown at the start of rendered shorts:

```json
{
"branding": {
"enabled": true,
"template": "builtin:branding",
"duration": 5.0
}
}
```

| Key | Default | Description |
|---|---|---|
| `enabled` | `true` | Whether to show branding overlay |
| `template` | `"builtin:branding"` | Template path — `"builtin:branding"` for the default, or a path to a custom `.ass` file |
| `duration` | `5.0` | How long the branding is visible in seconds |

The builtin branding overlay displays "reeln v{version} by https://streamn.dad" in bold white text with a black outline at the top of the video, fading in over 300ms and fading out over 800ms. To disable branding entirely, set `enabled` to `false` or use `--no-branding` on the CLI.

### Orchestration section

The `orchestration` section controls the plugin pipeline behavior:
Expand Down Expand Up @@ -347,6 +431,47 @@ When any command is run with `--debug`, pipeline debug artifacts are written to

Debug artifacts are automatically removed by `game prune` (no `--all` flag needed). Open `debug/index.html` in a browser for a quick overview of all operations performed on a game.

## Team profiles and rosters

Team profiles are stored as JSON files in the config directory under `teams/{level}/{slug}.json`. When you initialize a game with `--level`, the team level and slugs are persisted in `game.json`, enabling roster-based player lookup during rendering.

### Setting up rosters for player number lookup

1. **Create team profiles** with `roster_path` pointing to a CSV file:

```json
{
"team_name": "Eagles",
"short_name": "EGL",
"level": "bantam",
"roster_path": "/path/to/eagles_roster.csv",
"colors": ["#C8102E", "#000000"]
}
```

2. **Create the roster CSV** with `number`, `name`, and `position` columns:

```text
number,name,position
48,John Smith,C
24,Jane Doe,D
2,Bob Jones,RW
```

3. **Initialize games with `--level`** to persist team profile references:

```bash
reeln game init eagles bears --level bantam --sport hockey
```

4. **Use `--player-numbers` during rendering** to look up players from the roster:

```bash
reeln render short clip.mkv --player-numbers 48,24,2 --event-type HOME_GOAL -r overlay
```

This resolves to `#48 Smith` (scorer) with assists `#24 Doe` and `#2 Jones`.

## Schema versioning

Every config file includes a `config_version` field. When the schema changes, reeln provides migration functions to upgrade configs automatically.
Expand Down
2 changes: 1 addition & 1 deletion reeln/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from __future__ import annotations

__version__ = "0.0.32"
__version__ = "0.0.33"
Loading
Loading