mirror of
https://github.com/vondas-network/videobeaux.git
synced 2026-01-28 01:31:16 +01:00
406 lines
11 KiB
Markdown
406 lines
11 KiB
Markdown
# `lagkage` – JSON‑driven layered compositor for videobeaux
|
||
|
||
`lagkage` is a videobeaux program that lets you build complex, multi‑layer 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
|
||
- Per‑layer 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 (pre‑processed to loop for the base duration)
|
||
- Handles audio according to your chosen mode (base/json/all/external/none)
|
||
- Optionally applies **per‑layer audio gain**, so you can do a rough pre‑mix
|
||
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 JSON’s 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 video’s 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 per‑run using `--sequence-direction` on the CLI.
|
||
|
||
- `layers` (array, **required**)
|
||
|
||
Each element is a **layer object** describing one overlay media item.
|
||
|
||
|
||
### 2.2 Per‑layer 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 it’s best to set
|
||
explicit numbers.
|
||
|
||
- `name` (string, optional)
|
||
|
||
Short human‑readable 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 as‑is.
|
||
- 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 project‑root relative.
|
||
- Any other relative path is treated as **layout‑relative**:
|
||
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; pre‑processed 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 video’s pixel space**. (0,0) is top‑left of
|
||
the base frame. These are the top‑left of the scaled/cropped layer box.
|
||
|
||
You *can* push a layer partially or fully off‑screen 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 layer’s
|
||
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 center‑cropped
|
||
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 full‑frame video into a “face cam” PIP).
|
||
|
||
|
||
#### Per‑layer audio gain
|
||
|
||
These fields only matter if:
|
||
|
||
- the layer’s 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 layer’s 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*; per‑layer 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 **per‑layer 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 per‑layer gains) via `amix`.
|
||
|
||
- `--audio-mode external`
|
||
|
||
- Append `--audio-src PATH` as an extra ffmpeg input.
|
||
- Map only that input’s 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 per‑layer gains matter?
|
||
|
||
Per‑layer 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 (there’s 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 pre‑processed 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 as‑is.
|
||
3. If `filename` starts with `"../media/"` → normalize to `media/...` relative
|
||
to your project root.
|
||
4. If `filename` starts with `"media/"` → treat as project‑root relative.
|
||
5. Otherwise → resolve relative to the **layout JSON’s 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 per‑layer 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 top‑right 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.
|