lai-caption · diff

git:20260416.b0c60d0 to git:20260417.89d056b

226 added, 105 removed. Audit A to A.

---
name: lai-caption
- description: Convert between 30+ caption/subtitle formats (SRT, VTT, ASS, JSON, TextGrid, LRC, FCPXML, Premiere, …) and shift timing. Trigger on "convert captions", "SRT to VTT", "转换字幕格式", "shift timing", "ASS styling", "karaoke effect", "导入Premiere", or any caption-format question. Do NOT trigger to fix timing accuracy (`/lai-align`) or translate (`/lai-translate`).
+ description: Use when converting between caption/subtitle formats or adjusting timing. Supports 30+ formats including SRT, VTT, ASS, TTML, JSON, TextGrid, LRC, and NLE formats (Premiere, FCPXML). Use this skill whenever the user needs to change subtitle format, shift caption timing, add ASS styling, create karaoke effects, or export captions for video editing software. Also triggers on "convert captions", "转换字幕格式", "change format", "shift timing", "SRT to VTT", "ASS styling", "转换成SRT", "make ASS subtitles", "karaoke effect", "导入Premiere", even if they just say "I have an SRT and need a VTT" or "shift all timestamps by 2 seconds".
allowed-tools: Read, Bash(laicap:*), Bash(lai:*)
---
# Caption Format Conversion
- Convert, shift, and style caption files. Format is auto-detected from file extension.
+ Convert between 30+ caption/subtitle formats with optional timing adjustment
+ and ASS styling. Preserves text content, timing, speaker labels, and
+ word-level alignment data where the target format supports it.
- `laicap-convert` and `lai caption convert` are **the same command** — the former is the shortcut entry-point. For pipeline / non-interactive use, add `--direct -Y` (direct execution, skip confirmation).
+ **CLI syntax note**: LattifAI uses `nemo_run` configuration syntax. Options use
+ `key=value` format (e.g., `caption.ass.font_size=48`), NOT traditional flags.
+ This applies to all `lai` subcommands.
- ## Convert
+ ## When to Use
- `laicap-convert` (== `lai caption convert`) takes two positional args: `input_path` and `output_path`. All styling/rendering keys are **flat top-level config** (`render.*`, `ass.*`), **not** `caption.*`.
+ - Convert between caption formats (SRT to VTT, JSON to ASS, etc.)
+ - Shift timing (offset all timestamps by N seconds)
+ - Apply ASS styling (fonts, colors, karaoke effects)
+ - Prepare captions for specific platforms or players
+ - Export aligned JSON to human-readable formats
+ ## When NOT to Use
+
+ - Need to fix timing accuracy -- use `/lai-align` instead
+ - Need to generate captions from audio -- use `/lai-transcribe`
+ - Need translation -- use `/lai-translate`
+
+ ## CLI Usage
+
+ ### Format Conversion
+
```bash
laicap-convert input.srt output.vtt
```
- Common pairs:
+ Full command form:
```bash
- laicap-convert aligned.json video.srt # JSON → playback formats
- laicap-convert video.srt video.ass # basic → styled
- laicap-convert transcript.md transcript.srt # Gemini markdown → SRT
- laicap-convert aligned.json aligned.TextGrid # → Praat
+ lai caption convert input.srt output.vtt
```
- Add `-Y` for non-interactive runs. Use `input_format=srt` (top-level flag) to override auto-detection when the extension is wrong.
+ Format is auto-detected from file extension.
- ## Shift Timing
+ ### Common Conversions
```bash
- laicap-shift input.srt output.srt 2.5 # forward 2.5 s
- laicap-shift input.srt output.srt -1.0 # backward 1 s
+ # SRT to VTT (for web playback)
+ laicap-convert video.srt video.vtt
+
+ # JSON to SRT (for players)
+ laicap-convert aligned.json aligned.srt
+
+ # SRT to ASS (for styled subtitles)
+ laicap-convert video.srt video.ass
+
+ # JSON to TextGrid (for Praat linguistic analysis)
+ laicap-convert aligned.json aligned.TextGrid
+
+ # Gemini MD to SRT
+ laicap-convert transcript.md transcript.srt
+
+ # Any format to JSON (preserve all data)
+ laicap-convert caption.srt caption.json
```
- ## ASS Styling
+ ### Timing Shift
- Top-level `ass.*` keys (see `lai caption convert --help` for the full list):
+ Offset all timestamps by a fixed amount:
```bash
- laicap-convert input.srt output.ass \
- ass.font_name="Noto Sans CJK SC" \
- ass.font_size=48 \
- ass.primary_color="#FFFFFF"
+ laicap-shift input.srt output.srt 2.5 # Shift forward 2.5 seconds
+ laicap-shift input.srt output.srt -1.0 # Shift backward 1 second
```
- ### Karaoke (per-word highlighting)
-
- Requires word-level JSON input (produced by `/lai-align` with `caption.render.word_level=true`, or `/lai-youtube` with the same flag — the upstream flag populates `words` arrays in the JSON).
+ Full command form:
```bash
- laicap-convert aligned.json out.ass \
- ass.karaoke_effect=sweep \
- ass.karaoke_color_scheme=azure-gold
+ lai caption shift input.srt output.srt 2.5
```
- > **`render.word_level` scope** — default is word-scope (per-word `\k` / `\kf` / `\ko` emission). Only pass `render.word_level=false` to switch to line-scope (a single override at event start, whole text block animates together — CapCut-style "fade in"). You don't need `render.word_level=true` to enable karaoke; `ass.karaoke_effect` alone does it.
+ ### ASS Styling Options
- - `ass.karaoke_effect`: `sweep` (classic karaoke fill), `instant` (hard on/off), `outline` (outline-only highlight)
- - `ass.karaoke_color_scheme` (12 presets, each tunes primary/secondary/outline/back):
- `azure-gold`, `sakura-purple`, `mint-ocean`, `gardenia-green`, `sunset-warm`,
- `prussian-elegant`, `burgundy-classic`, `langgan-spring`, `mars-teal`,
- `spring-field`, `navy-pink`, `apricot-dark`
- - `ass.kinetic_style` (orthogonal per-word animation, 15 options grouped by feel):
- - **Impact**: `bounce`, `pop`, `shake`, `pulse`, `swing`
- - **Smooth**: `fade`, `zoom`, `rise`, `typewriter`, `blur_in`
- - **Stylized**: `glow`, `neon`, `wave`, `flicker`, `stagger`
+ When outputting to ASS format:
- ### Bilingual ASS
+ ```bash
+ laicap-convert input.srt output.ass \
+ caption.ass.font_name="Noto Sans CJK SC" \
+ caption.ass.font_size=48 \
+ caption.ass.primary_color="#FFFFFF" \
+ caption.ass.outline_color="#000000"
+ ```
- Format selection, typography conventions (line order / color / font-size ratio), and four scenario recipes (learning SRT / social ASS / karaoke / dual-track upload) all live in **`/lai-translate` §Bilingual Delivery Guide** — don't duplicate. Minimal one-liner for a bilingual JSON from `/lai-translate`:
+ ### Bilingual ASS
```bash
- laicap-convert aligned_bilingual.json out.ass \
- ass.primary_color="#FFFFFF" \
- ass.translation_color="#FFC209"
+ laicap-convert bilingual.srt bilingual.ass \
+ caption.ass.style=bilingual \
+ caption.ass.line1_color="#00FF00" \
+ caption.ass.line2_color="#FFFF00"
```
- Add `ass.karaoke_effect=sweep ass.karaoke_color_scheme=...` for per-word karaoke on the source line.
+ ### Karaoke Effect (Word-Level)
- ### Speaker Color
+ Requires word-level timing data (from alignment JSON):
- `ass.speaker_color=...` paints dialogue per speaker (needs `/lai-diarize` output in the source). Accepts:
+ ```bash
+ laicap-convert aligned.json karaoke.ass \
+ caption.ass.karaoke_effect=true
+ ```
- - `""` — disabled (default)
- - `"auto"` — 10-color LattifAI palette (cycles for >10 speakers)
- - `"#RRGGBB,#RRGGBB,..."` — CSV of explicit colors, one per speaker in appearance order
+ ### Speaker-Colored ASS
+ Different colors per speaker:
+
```bash
- # Host = cyan-blue, Guest = pink — short CSV palette
- laicap-convert diarized.json out.ass \
- render.include_speaker_in_text=true \
- ass.speaker_color="#658AE4,#F7C3D9"
+ laicap-convert diarized.json speakers.ass \
+ caption.render.include_speaker_in_text=true \
+ caption.ass.speaker_color=true
```
- ## Broadcast-Grade Profiles (standardization.*)
+ ## Supported Formats
- `standardization.*` reflows segments to meet CPL/CPS/duration rules before writing. Useful for delivering SRT/VTT to Netflix-class platforms or tighter YouTube specs.
+ ### Standard Subtitle Formats
- Fields (all top-level):
+ | Format | Extension | Read | Write | Notes |
+ |--------|-----------|------|-------|-------|
+ | SubRip | `.srt` | Yes | Yes | Most universal format |
+ | WebVTT | `.vtt` | Yes | Yes | Web standard, supports styling |
+ | ASS/SSA | `.ass`, `.ssa` | Yes | Yes | Rich styling, karaoke |
+ | LRC | `.lrc` | Yes | Yes | Lyrics format |
+ | Plain text | `.txt` | Yes | Yes | Text only, no timing |
+ | Markdown | `.md` | Yes | Yes | Gemini-style with timestamps |
- - `standardization.min_duration` / `max_duration` — segment duration bounds (s)
- - `standardization.min_gap` — minimum inter-segment gap (s)
- - `standardization.max_lines` / `max_chars_per_line` — line wrapping limits
- - `standardization.optimal_cps` — target characters-per-second for readability
- - `standardization.start_margin` / `end_margin` — pre/post roll per segment
- - `standardization.margin_collision_mode` — `trim` (default), `drop`, …
+ ### Data / Interchange Formats
- Ready-made profiles:
+ | Format | Extension | Read | Write | Notes |
+ |--------|-----------|------|-------|-------|
+ | JSON | `.json` | Yes | Yes | Full data (word-level, speakers) |
+ | TSV | `.tsv` | Yes | Yes | Tab-separated |
+ | CSV | `.csv` | Yes | Yes | Comma-separated |
+ | Audacity | `.aud` | Yes | Yes | Audacity label track |
- ```bash
- # Netflix-ish: 42 CPL × 2 lines, 0.8-7 s
- laicap-convert diarized.json out.netflix.srt \
- standardization.min_duration=0.8 \
- standardization.max_duration=7.0 \
- standardization.min_gap=0.08 \
- standardization.max_lines=2 \
- standardization.max_chars_per_line=42 \
- standardization.start_margin=0.05 \
- standardization.end_margin=0.15
+ ### Linguistic Formats
- # YouTube-ish: shorter cues, narrower lines
- laicap-convert diarized.json out.youtube.srt \
- standardization.min_duration=0.5 \
- standardization.max_duration=5.0 \
- standardization.max_chars_per_line=35 \
- standardization.start_margin=0.03 \
- standardization.end_margin=0.10
- ```
+ | Format | Extension | Read | Write | Notes |
+ |--------|-----------|------|-------|-------|
+ | TextGrid (Praat) | `.TextGrid` | Yes | Yes | Phonetic analysis |
+ | TTML | `.ttml`, `.xml` | Yes | Yes | W3C timed text |
- `start_margin` / `end_margin` also benefit karaoke exports (give lyrics breathing room) — combine with the karaoke recipe above.
+ ### NLE (Non-Linear Editing) Formats
- ## Supported Formats
+ | Format | Extension | Read | Write | Notes |
+ |--------|-----------|------|-------|-------|
+ | FCPXML | `.fcpxml` | Yes | Yes | Final Cut Pro |
+ | Premiere XML | `.prproj.xml` | No | Yes | Adobe Premiere |
- | Category | Formats (read & write unless noted) |
- |----------|-------------------------------------|
- | Standard | `.srt`, `.vtt`, `.ass` / `.ssa`, `.lrc`, `.txt`, `.md` |
- | Data | `.json`, `.tsv`, `.csv`, `.aud` |
- | Linguistic | `.TextGrid`, `.ttml` / `.xml` |
- | NLE | `.fcpxml`; `.prproj.xml` (write-only) |
+ ## Parameters
- Full list: `lai caption convert --help`.
+ ### Convert
- ### Data Preservation
+ | Parameter | CLI Syntax | Default | Description |
+ |-----------|-----------|---------|-------------|
+ | Input | positional arg 1 | required | Source caption file |
+ | Output | positional arg 2 | required | Target caption file |
+ | Input format | `caption.input_format=X` | auto | Override format detection |
+ | Word-level | `caption.render.word_level=true` | `false` | Include word timing |
+ | Include speaker | `caption.render.include_speaker_in_text=true` | `false` | Embed speaker labels |
+ | Translation first | `caption.render.translation_first=true` | `false` | Show translation above source |
- Convert to `.json` first to keep word-level timing, speakers, and translations — then fan out to delivery formats.
+ ### Shift
- - **Word-level timing**: JSON, TextGrid, ASS-karaoke only
- - **Speakers**: JSON, VTT, ASS, TextGrid (partial in SRT)
- - **Full styling**: ASS only
+ | Parameter | CLI Syntax | Default | Description |
+ |-----------|-----------|---------|-------------|
+ | Input | positional arg 1 | required | Source caption file |
+ | Output | positional arg 2 | required | Target caption file |
+ | Offset | positional arg 3 | required | Seconds to shift (positive or negative) |
- ## Common Issues
+ ### ASS Options
- | Problem | Fix |
- |---------|-----|
- | Unknown input format | Specify `input_format=srt` (top-level flag, not `caption.input_format`) |
- | Encoding error | Re-save the file as UTF-8 |
- | Karaoke has no highlighting | Source JSON needs `words` arrays — re-run `/lai-align` with `caption.render.word_level=true`, then convert with just `ass.karaoke_effect=sweep` (no `render.word_level` needed; default is word-scope) |
- | `No parameter named 'caption'` / `'input'` | For `laicap-convert`, styling keys are flat (`render.*`, `ass.*`) and input/output are positional (`input_path`/`output_path`) — there is no `caption.*` namespace here |
- | Plain text missing timing | Add timing via `/lai-align` first |
+ | Parameter | CLI Syntax | Default | Description |
+ |-----------|-----------|---------|-------------|
+ | Font | `caption.ass.font_name=X` | system default | Font family |
+ | Font size | `caption.ass.font_size=N` | 24 | Font size |
+ | Primary color | `caption.ass.primary_color=#HEX` | `#FFFFFF` | Text color |
+ | Outline color | `caption.ass.outline_color=#HEX` | `#000000` | Outline color |
+ | Karaoke | `caption.ass.karaoke_effect=true` | `false` | Karaoke highlighting |
+ | Speaker color | `caption.ass.speaker_color=true` | `false` | Per-speaker coloring |
+ | Bilingual style | `caption.ass.style=bilingual` | default | Dual-line layout |
+ ## Workflow
+
+ ### Step 1 -- Identify source format
+
+ Read the input file to determine its format and content:
+ - File extension for format detection
+ - Check for word-level data (JSON with `alignment` or `words` field)
+ - Check for speaker labels
+ - Check for bilingual content
+
+ ### Step 2 -- Choose target format
+
+ | Need | Recommended Format |
+ |------|--------------------|
+ | Universal playback | `.srt` |
+ | Web embedding | `.vtt` |
+ | Styled subtitles | `.ass` |
+ | Downstream processing | `.json` |
+ | Linguistic analysis | `.TextGrid` |
+ | Video editing (FCP) | `.fcpxml` |
+ | Lyrics display | `.lrc` |
+
+ ### Step 3 -- Run conversion
+
+ ```bash
+ laicap-convert <input> <output> [options]
+ ```
+
+ ### Step 4 -- Verify output
+
+ Read the first few segments to confirm:
+ - Text preserved correctly
+ - Timing intact
+ - Speaker labels present (if expected)
+ - Styling applied (for ASS)
+
+ ## Data Preservation
+
+ When converting between formats, data preservation varies:
+
+ | Data | JSON | SRT | VTT | ASS | TextGrid | TXT |
+ |------|------|-----|-----|-----|----------|-----|
+ | Text | Yes | Yes | Yes | Yes | Yes | Yes |
+ | Timing | Yes | Yes | Yes | Yes | Yes | No |
+ | Speakers | Yes | Partial | Yes | Yes | Yes | No |
+ | Word-level | Yes | No | No | Karaoke | Yes | No |
+ | Translation | Yes | Bilingual | Bilingual | Bilingual | No | No |
+ | Styling | No | No | Basic | Full | No | No |
+
+ **Rule**: Convert to JSON first to preserve maximum data. Then convert
+ from JSON to the target format for delivery.
+
+ ## Error Handling
+
+ | Condition | Action |
+ |-----------|--------|
+ | Unknown input format | Specify with `caption.input_format=srt` |
+ | Empty file | Check file has content |
+ | Encoding error | Ensure file is UTF-8 |
+ | Missing timing data (for SRT/VTT) | Input may be plain text. Add timing via `/lai-align` |
+ | Karaoke requested but no word-level | Align first with `caption.render.word_level=true` |
+
## Related Skills
- - `/lai-align` — fix timing accuracy (conversion doesn't change timing)
- - `/lai-transcribe` — generate captions from audio first
- - `/lai-translate` — translate before bilingual conversion
- - `/lai-diarize` — add speaker labels for speaker-colored output
+ | Skill | Use When |
+ |-------|----------|
+ | `/lai-align` | Need precise timing before conversion |
+ | `/lai-transcribe` | Need to generate captions first |
+ | `/lai-translate` | Need translation before bilingual conversion |
+ | `/lai-diarize` | Need speaker labels before speaker-colored output |
+
+ ### Common Workflow Chains
+
+ ```bash
+ # Align -> Convert to multiple formats
+ lai alignment align audio.mp3 caption.srt aligned.json \
+ caption.render.word_level=true
+ laicap-convert aligned.json aligned.srt
+ laicap-convert aligned.json aligned.vtt
+ laicap-convert aligned.json karaoke.ass caption.ass.karaoke_effect=true
+
+ # Shift + Convert
+ laicap-shift input.srt shifted.srt -2.0
+ laicap-convert shifted.srt shifted.vtt
+
+ # JSON -> bilingual ASS after translation
+ laicap-convert translated.json bilingual.ass \
+ caption.ass.style=bilingual \
+ caption.ass.line1_color="#FFFFFF" \
+ caption.ass.line2_color="#FFD700"
+ ```
+
+ ## Non-Goals
+
+ - Does NOT fix timing accuracy -- use `/lai-align`
+ - Does NOT translate text -- use `/lai-translate`
+ - Does NOT generate captions from audio -- use `/lai-transcribe`
+ - Does NOT edit caption text content
+ - Does NOT merge multiple caption files into one