Files
videobeaux/program-docs/docs-lagkage.md
2025-11-28 22:26:57 -05:00

406 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `lagkage` JSONdriven layered compositor for videobeaux
`lagkage` is a videobeaux program that lets you build complex, multilayer video compositions from a **single JSON file**.
You define:
- A **base video** (passed via `-i` as usual)
- A set of **layers** (images, GIFs, or videos) in a JSON layout file
- Perlayer properties: position, size, opacity, zoom, crop, audio gain
- Global behavior: sequence ordering, audio mix mode
`lagkage` then builds an ffmpeg filtergraph that:
- Scales, crops, and blends each layer into the base frame
- Treats GIFs as animated overlays (preprocessed to loop for the base duration)
- Handles audio according to your chosen mode (base/json/all/external/none)
- Optionally applies **perlayer audio gain**, so you can do a rough premix
directly from the JSON without opening a DAW
---
## 1. CLI usage
From your videobeaux project root:
```bash
videobeaux -P lagkage \
-i media/base.mp4 \
-o out/composite.mp4 \
--layout-json lagkage_layouts/layout_ex01_logo_corner.json \
--force
```
### Arguments
- `-i, --input`
Base video to composite onto. **Required**.
- `-o, --output`
Final rendered video. **Required**.
- `--layout-json PATH`
Path to a JSON file that describes all overlay layers. **Required**.
- `--sequence-direction {forward,backward,random}`
Optional override for the order in which layers are applied. If omitted, the
layout JSONs own `sequence_direction` is used.
- `--audio-mode {base,all,json_only,external,none}`
How to construct the final audio track:
- `base` (default) only the base videos audio. JSON layers are visual only.
- `all` mix **base audio + all JSON layer audio** together.
- `json_only` ignore the base audio, use only JSON layer audio.
- `external` ignore base + JSON audio, use the file supplied via `--audio-src`.
- `none` no audio at all (silent output).
- `--audio-src PATH`
Path to an external audio file. Only used when `--audio-mode=external`.
- `--force`
From the global videobeaux CLI: forces overwrite of existing outputs and adds
`-y` to ffmpeg under the hood.
---
## 2. Layout JSON format
A layout JSON looks like this at the top level:
```json
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo",
"filename": "../media/reem.png",
"type": "img",
"mode": "place",
"place": "top_right",
"size": 18,
"opacity": 0.95
}
]
}
```
### 2.1 Top level
- `sequence_direction` (string, optional)
Controls how `lagkage` orders the layers before applying them:
- `"forward"` smallest `layer_number` first (default)
- `"backward"` largest `layer_number` first
- `"random"` random permutation of layers
You can override this perrun using `--sequence-direction` on the CLI.
- `layers` (array, **required**)
Each element is a **layer object** describing one overlay media item.
### 2.2 Perlayer fields
All layers share a common set of fields. Some are optional depending on mode.
#### Identity and source
- `layer_number` (int, **recommended**)
Used for ordering and for sequence modes. Lowest layer_number is “first” in
forward mode. If omitted, it defaults to 0 for sorting, but its best to set
explicit numbers.
- `name` (string, optional)
Short humanreadable label for the layer. Not used by the code, but useful
for documentation.
- `filename` (string, **required**)
Path to the media file for this layer. Resolution rules:
- Absolute paths (`/Users/you/...`) are used asis.
- URLs (`https://...`) are passed directly to ffmpeg.
- Paths starting with `'../media/'` are normalized to `media/...` **relative
to your project root**.
- Paths starting with `'media/'` are also treated as projectroot relative.
- Any other relative path is treated as **layoutrelative**:
resolved against the folder that contains the JSON.
- `type` (string, **required**)
The kind of media:
- `"img"` PNG/JPEG/etc.; single frame, no audio
- `"gif"` animated GIF; preprocessed to a looping RGBA video
- `"video"` standard video file; may contain audio
This mainly affects whether we run the GIF preprocessing pipeline.
#### Positioning: `mode`, `place`, `pos_x`, `pos_y`
A layer can be positioned in **place** mode (logical corners/center) or in
**free** mode (explicit pixel coordinates).
- `mode` (string, **required**)
- `"place"` snap the layer into a logical slot:
- uses `place`
- `"free"` place the layer at exact coordinates:
- uses `pos_x`, `pos_y`
- `place` (string, required if `mode="place"`)
One of:
- `"top_left"`
- `"top_right"`
- `"bottom_left"`
- `"bottom_right"`
- `"center"`
- `pos_x`, `pos_y` (int, required if `mode="free"`)
Pixel coordinates in the **base videos pixel space**. (0,0) is topleft of
the base frame. These are the topleft of the scaled/cropped layer box.
You *can* push a layer partially or fully offscreen by using negative values
or values greater than the base width/height.
#### Size and opacity
- `size` (float, optional; default `100`)
Percentage of the **base video width** that this layer should occupy. The
layer is scaled to:
```text
box_w = base_width * (size / 100)
box_h = box_w / aspect_ratio
```
Both are forced to even integers (necessary for some codecs).
- `opacity` (float, optional; default `1.0`)
Range `[0.0, 1.0]`. Implemented using `colorchannelmixer` on the layers
alpha channel before overlay:
- `1.0` fully opaque
- `0.5` half transparent
- `0.0` fully transparent (invisible)
#### Zoom and crop
These operate in **layer space** before the final overlay.
- `zoom` (float, optional; default `1.0`)
- `1.0` no zoom (layer fits exactly inside the computed `box_w x box_h`)
- `>1.0` zoom in; layer is scaled up to `zoom * box` then centercropped
back down to the box. This gives the effect of “zooming inside” a fixed
PIP area.
- `crop_x`, `crop_y`, `crop_w`, `crop_h` (float, optional)
**Percentages** of the original source width/height, all in `[0,100]`.
If all four are present, lagkage applies:
```text
crop=in_w*crop_w/100 : in_h*crop_h/100 : in_w*crop_x/100 : in_h*crop_y/100
```
This happens *before* zoom/scale. Use this to punch out a region from a
larger frame (for example, turning a fullframe video into a “face cam” PIP).
#### Perlayer audio gain
These fields only matter if:
- the layers media actually has audio (e.g. `type: "video"` with an audio stream),
- and `--audio-mode` is `all` or `json_only` so layer audio is in the mix.
- `audio_gain_db` (float, optional)
Gain in decibels applied to the layers audio *before* mixing:
- `0` keep original level
- `-6` roughly “half as loud” perceptually
- `+3` a bit louder
- `+12` very loud / likely to clip unless sources are quiet
- `audio_gain` (float, optional)
Linear multiplier, converted internally to dB as:
```text
audio_gain_db = 20 * log10(audio_gain)
```
Only used if `audio_gain_db` is not set.
For example, `audio_gain: 0.5` ≈ `-6.02 dB`.
---
## 3. Audio behavior in detail
### 3.1 Modes
- `--audio-mode base` (default)
- Map `0:a?` (base audio only).
- JSON layers are *visual only*; perlayer audio gains are ignored.
- `--audio-mode all`
- Take audio from:
- base input (index 0) if it has audio
- all JSON layers whose preprocessed media has audio
- Apply **perlayer gains** (where specified) on JSON layers.
- Use `amix` to combine all sources into a single output stream.
- `--audio-mode json_only`
- Ignore base audio completely.
- Mix only JSON layer audio (with perlayer gains) via `amix`.
- `--audio-mode external`
- Append `--audio-src PATH` as an extra ffmpeg input.
- Map only that inputs audio (`N:a?`) to the output.
- JSON layer audio and base audio are ignored.
- `--audio-mode none`
- Add `-an` to ffmpeg.
- Output video is silent.
### 3.2 When do perlayer gains matter?
Perlayer audio gains (`audio_gain_db` / `audio_gain`) are applied:
- only on JSON layers that actually have an audio stream, and
- only when `--audio-mode` is `all` or `json_only`.
They do **not** affect:
- the base input audio (theres currently no `base_audio_gain_db`),
- the external audio when `--audio-mode=external`,
- GIF overlays (their preprocessed videos are silent),
- image layers (no audio).
---
## 4. GIF handling
GIFs are preprocessed before the main pass so the compositor can treat them
like normal video streams with alpha.
For each `type: "gif"` layer:
1. lagkage runs a helper ffmpeg command that:
- loops the GIF (`-stream_loop -1`, `-ignore_loop 0`)
- clamps to the base duration using `-t base_duration`
- forces even dimensions with `scale=trunc(iw/2)*2:trunc(ih/2)*2`
- converts to RGBA pixels (`format=rgba`)
- writes a `.mov` with `qtrle` codec and **alpha preserved**
2. The resulting `.mov` is used as the actual overlay source.
This is why animated GIFs loop smoothly across the entire base clip and retain
their transparency when overlaid.
---
## 5. Path resolution rules
Given a `layout_exNN.json` at:
```text
lagkage_layouts/layout_exNN_*.json
```
and a layer `filename` value, lagkage resolves paths as follows:
1. If `filename` contains `"://"` → treat as URL and pass directly to ffmpeg.
2. If `filename` is an absolute path (e.g. `/Users/you/file.mp4`) → use asis.
3. If `filename` starts with `"../media/"` → normalize to `media/...` relative
to your project root.
4. If `filename` starts with `"media/"` → treat as projectroot relative.
5. Otherwise → resolve relative to the **layout JSONs directory**.
---
## 6. Example layouts & commands
This repo includes a set of example layouts and matching commands that showcase:
- place vs free mode
- cropping and zoom
- animated GIF overlays
- multiple layers with different sizes & opacities
- different `sequence_direction` values
- all audio modes, including perlayer gain
See:
- `lagkage_layouts/` 20 example layout JSON files (layout_ex01_*.json … layout_ex20_*.json)
- `lagkage_examples_full.txt` example commands, one per layout
You can copy one example, tweak the filenames to match your media, and then
iterate from there.
---
## 7. Minimal “hello lagkage” example
`lagkage_layouts/layout_ex01_logo_corner.json`:
```json
{
"sequence_direction": "forward",
"layers": [
{
"layer_number": 1,
"name": "logo_corner",
"filename": "../media/reem.png",
"type": "img",
"mode": "place",
"place": "top_right",
"size": 18,
"opacity": 0.95
}
]
}
```
Command:
```bash
videobeaux -P lagkage -i media/base.mp4 -o out/ex01_logo_corner.mp4 --layout-json lagkage_layouts/layout_ex01_logo_corner.json --audio-mode base --force
```
This is the simplest setup:
- Base video at `media/base.mp4`
- One PNG logo (`reem.png`) in the topright corner
- Base audio only
- No zoom/crop or audio mixing yet
From here, add more layers, switch to `audio-mode=all`, and start using
`audio_gain_db` to explore full lagkage functionality.