# Command Line (seconv) `seconv` is Subtitle Edit's headless command-line converter. It reuses the same core libraries as the desktop app (`libse`, `libuilogic`), so it supports the same formats, operations, and OCR engines — without any GUI dependency. Useful for scripts, CI pipelines, server-side workflows, and bulk conversion. `seconv` lives in the main Subtitle Edit repository and ships in lockstep with the desktop app — no separately maintained fork, no version drift. ## Highlights - **380+ subtitle formats** — text, binary, and image-based. - **Container input** — Matroska (`.mkv` / `.mks`), MP4, MCC, MXF, AVI (`.avi` / `.divx`), transport stream teletext, Blu-Ray `.sup`. - **OCR for image-based sources** via five engines (Tesseract subprocess, nOCR built-in, BinaryOCR built-in, Ollama HTTP, PaddleOCR subprocess). - **Auto-translate** via local LLMs (llama.cpp with automatic server start, Ollama, LM Studio) or self-hosted services (LibreTranslate, NLLB). - **Image-based output** — Blu-Ray sup, BDN-XML, DOST, FCP (Final Cut Pro + image), D-Cinema interop / SMPTE 2014, images-with-time-code. - **Operations pipeline** — offset, fps change, change-speed, renumber, adjust-duration, fix-common-errors, merge/split, balance, redo casing, RTL fixes, multiple-replace, custom-text format, plain text. - **Cross-platform** — Windows, Linux, macOS. Only requires the .NET runtime; no display or GUI needed. ## Installation Pre-built binaries are distributed alongside Subtitle Edit. To build from source: ```bash dotnet build src/seconv/SeConv.csproj -c Release ``` The executable is `seconv` (or `seconv.exe` on Windows). ## Usage ```bash seconv [options] seconv --format [options] # alternative syntax ``` The format may be passed as the second positional argument or via `--format `. Multiple input patterns can be passed as separate quoted arguments or comma-separated: ```bash seconv "file1.srt" "file2.srt" subrip --overwrite seconv "*.srt,*.ass" subrip --input-folder:./in ``` Options accept either `--option:value`, `--option=value`, or `--option value`. The colon form is shown throughout this page. An unrecognised option is an error, never a silent no-op: `seconv` exits 1 and suggests the closest real option rather than converting the file without the operation you asked for. ```console $ seconv movie.srt subrip --remove-formating Error: Unknown option '--remove-formating'. Did you mean '--remove-formatting'? ``` ### Quick examples ```bash seconv *.srt sami # SRT → SAMI seconv movie.srt subrip --encoding:windows-1252 # encoding override seconv movie.srt subrip --encoding:source # keep input's encoding on output seconv *.sub subrip --fps:25 --output-folder:./out # frame-based → time-based seconv movie.mkv subrip --track-number:3 # extract MKV text track #3 seconv movie.sup subrip --ocr-engine:tesseract --ocr-language:eng # OCR a Blu-Ray .sup seconv movie.sup subrip --ocr-engine:nocr --ocr-db:Latin.nocr # OCR via nOCR seconv movie.sup subrip --ocr-engine:binaryocr --ocr-db:Latin.db # OCR via BinaryOCR seconv movie.sup subrip --ocr-engine:ollama --ollama-model:llama3.2-vision seconv movie.sup subrip --ocr-engine:llamacpp # OCR via llama.cpp (auto-starts llama-server) seconv subs.srt bluraysup --resolution:1920x1080 # render text → Blu-Ray sup seconv subs.srt bdnxml --resolution:1920x1080 # render text → BDN-XML seconv subs.srt bluraysup --background-color:"#B4000000" # ... with a black background box seconv subs.srt customtext --custom-format:my-template.xml # custom template seconv *.srt subrip --multiple-replace:rules.csv # search-and-replace pass (GUI export: .csv/.template/.xml) seconv subs.srt subrip --offset:-2000 --renumber:1 --overwrite # offset 2s back, renumber from 1 ``` ## Subcommands ```bash seconv formats # list all supported formats (also /formats, --formats) seconv list-encodings # list text encodings (for --encoding and --input-encoding-fallback) seconv list-pac-codepages # list PAC code pages seconv list-ocr-engines # list OCR engines + installation status seconv list-fce-rules # list FixCommonErrors rule IDs seconv list-rf-rules # list remove-formatting rule IDs (alias: list-remove-formatting-rules) seconv dump-settings # print a full --settings JSON with libse defaults (alias: default-settings) seconv info # print format/encoding/duration/language for a file seconv lint # validate subtitle(s); exit 1 if issues found seconv --help # show help (same text as -h, /? and /help) seconv --help-json # print the whole command-line schema as JSON seconv --version # print version and exit ``` ### Machine-readable output Every subcommand above accepts `--json`, and so does a conversion run. Scripts and agents should prefer it: the tables are hundreds of box-drawing lines, while the JSON gives you the exact tokens each option accepts. ```bash seconv formats --json | jq -r '.formats[] | select(.inputOnly | not) | .id' seconv list-fce-rules --json | jq -r '.rules[].id' seconv list-ocr-engines --json | jq -r '.engines[] | select(.ready) | .id' ``` In `formats --json`, `id` is the token `--format` matches on (the display name with spaces removed); `name` is the human-readable name; `inputOnly` marks formats that can be loaded but not used as a conversion target. `seconv --help-json` describes the command line itself — every option with its aliases, type (`flag`, `string`, `integer`, `number`), whether it is an operation, its closed value set where it has one, and the subcommand that enumerates valid values otherwise. It is reflected off the parser, so it cannot drift from the options actually accepted. ```bash seconv --help-json | jq -r '.options[] | select(.group == "operation") | .name' seconv --help-json | jq -r '.options[] | select(.discover) | "\(.name)\t\(.discover)"' ``` Under `--json`, stdout is always a single JSON document — on success *and* on failure. A usage error (unknown option, bad value, no files matched) comes back in the same envelope as a failed conversion, with the message in `errors`: ```json { "success": false, "totalFiles": 0, "files": [], "errors": ["Unknown option '--bogus'."], "warnings": [] } ``` ### Inspect & validate ```bash seconv info movie.srt # human-readable table seconv info movie.srt --json # machine-parseable seconv lint *.srt # check overlaps, line lengths, tags, ... seconv lint *.srt --json # CI-friendly: exit 1 on any issue ``` ## Options ### File / I/O | Option | Description | |---|---| | `--input-folder:` | Input folder; relative patterns resolve against it | | `--output-folder:` | Output folder (default: input file's directory) | | `--output-filename:` | Output file name (single input only) | | `--output-filename-append:` | Text appended to the output file name stem, before any language/track suffix: `movie.ts` → `movie_fixed.eng.srt`. Ignored with `--output-filename` | | `--overwrite` | Overwrite existing files (default: rotate to `name_2.ext`, `_3.ext`, ...) | | `--keep-timestamp` (also `--keep-timestamps`) | Give output files the source file's modified/created date instead of the conversion time | | `--encoding:` | Encoding name or codepage. Special values: `utf-8`, `utf-8-no-bom` (also `utf-8-nobom`, `utf8-nobom`), a code page number, or `source` to keep the input file's detected encoding. Defaults: auto-detect on input, UTF-8 BOM on output | | `--input-encoding-fallback:` | Encoding to assume when the input is not UTF-8 / has no BOM, instead of the ANSI auto-detection (names as in `seconv list-encodings`). Ignored when `--encoding` is set | ### Time / frame | Option | Description | |---|---| | `--offset:hh:mm:ss:ms` | Shift all timecodes (also accepts plain ms, signed: `-1500`, `+1500`) | | `--fps:` | Source frame rate (used for frame-based input formats) | | `--target-fps:` | Target frame rate (with `--fps`, calls `Subtitle.ChangeFrameRate`) | | `--adjust-duration:` | Add/subtract milliseconds to each paragraph's duration | | `--change-speed:` | Scale all times by 100/percent (e.g. `125` = 1.25× faster). Must be > 0 | | `--renumber:` | Renumber paragraphs starting at `n` | ### Format-specific | Option | Applies to | Description | |---|---|---| | `--resolution:` | ASSA, image-based | Sets `PlayResX`/`PlayResY` for ASSA; sets canvas for image outputs (default `1920x1080`) | | `--assa-style-file:` | ASSA | Apply `[V4+ Styles]` block from another ASSA file | | `--pac-codepage:` | PAC | Code page name (`Latin`, `Greek`, `Hebrew`, …) or numeric (0–12). See `seconv list-pac-codepages` | | `--ebu-header-file:` | EBU STL | Reuse the GSI header block from an existing `.stl` file | | `--plaintext-merge` | Plain text (`txt`) | Merge all subtitles into one space-separated block (no blank lines). Takes precedence over the two options below | | `--plaintext-unbreak` | Plain text (`txt`) | Unbreak each subtitle, joining its lines into one | | `--plaintext-no-blank-line` | Plain text (`txt`) | Do not put a blank line between subtitles (default keeps it) | ### Image output styling When rendering a text subtitle to an image-based target (Blu-Ray `sup`, VobSub, BDN-XML, DOST, FCP / Final Cut Pro + image, D-Cinema, images-with-time-code, WebVTT thumbnails), these options control how the text is drawn. ``, ``, and `` tags in the subtitle text are honored. The same values can be set in a `--settings` JSON file's `exportImages` section (see [Settings JSON](#settings-json)); CLI flags win over the JSON. | Option | Description | |---|---| | `--font-name:` | Font family (default: `Arial`) | | `--font-size:` | Font size in points (default: `50`) | | `--font-color:` | Text colour (default: `white`) | | `--font-bold` | Render text bold | | `--outline-color:` | Outline colour (default: `black`) | | `--outline-width:` | Outline width (default: `2.5`; `0` disables) | | `--shadow-color:` | Shadow colour (default: `black`) | | `--shadow-width:` | Shadow width (default: `0` = off) | | `--background-color:` | Background box colour, e.g. `black` or `#B4000000` (semi-transparent black). Implies `--box-type:one-box` unless `--box-type` is given | | `--background-corner-radius:` | Corner radius of the background box (default: `0`) | | `--box-type:` | `none` (default) \| `one-box` \| `box-per-line` | | `--box-padding:` | Box padding: one value for all sides, or `left,right,top,bottom` (default: `5,5,3,3`) | | `--line-spacing:` | Extra gap between lines as percent of line height (default: `0`) | | `--alignment:` | Screen position: `bottom-center` (default), `top-left`, `middle-right`, ... | | `--content-alignment:` | Multi-line text justification: `left` \| `center` (default) \| `right` \| `from-alignment` (follow the `{\anX}` tag) | | `--bottom-top-margin:` | Vertical screen-edge margin (default: 5% of height) | | `--left-right-margin:` | Horizontal screen-edge margin (default: 5% of width) | | `--override-position:` | Image → image only (DVB-sub, PGS, VobSub pass-through): ignore the source bitmap position on that axis and place it by `--alignment` and the margins instead. The other axis keeps the source position. Matches SE4's transport-stream "override original X/Y position" | | `--full-frame` | Draw each subtitle onto a frame-sized image instead of one cropped to the text. Only `fcpimage` and `bluraysup` use it; other image targets warn and ignore it | | `--full-frame-background-color:` | Background of the full frame image (default: `transparent`) | Colours accept hex (`#AARRGGBB`, `#RRGGBB`, with or without `#`) or a colour name (`white`, `black`, `yellow`, ...). **Full frame** (`--full-frame`) draws the subtitle onto a canvas the size of the video frame, using the alignment and margins to place it there, so every image can be dropped on an editing timeline at 0,0 instead of being positioned one by one. It matches the "Full frame image" checkbox in the export dialog, and applies to `fcpimage` and `bluraysup` only. The background is transparent unless `--full-frame-background-color` says otherwise, so the images sit on a track above the video. ```bash # SRT → UHD Blu-Ray sup with a semi-transparent black background box (SE4-style) seconv movie.srt bluraysup --resolution:3840x2160 --background-color:"#B4000000" # Custom font, bold, box per line seconv movie.srt bluraysup --font-name:Verdana --font-size:60 --font-bold --box-type:box-per-line # Final Cut Pro + image, one frame-sized png per subtitle seconv movie.srt fcpimage --full-frame ``` ### Containers / tracks `seconv` automatically extracts subtitle tracks from container files: | Extension | Sources | |---|---| | `.mkv`, `.mks`, `.webm` | Matroska text tracks (S_TEXT/UTF8, SSA, ASS, HDMV/TEXTST) and image tracks (S_HDMV/PGS via OCR) | | `.mp4`, `.m4v`, `.m4s`, `.3gp`, `.mov`, `.m4a`, `.m4b`, `.cmaf` | MP4 text tracks and WebVTT VTTC | | `.mcc` | MacCaption 1.0 | | `.ts`, `.m2ts`, `.mts` | Transport stream — teletext (no OCR) and DVB-sub (via OCR) | | `.sup` | Blu-Ray sup (via OCR) | | `.sub`, `.idx` | VobSub (via OCR) — pass either file of the pair; the companion is found automatically. A `.sub` with no `.idx` is read with its stream timing and a default palette | | `.avi`, `.divx` | XSUB / DivX subtitles (via OCR) | | `.mxf` | MXF timed-text essences (TTML, SRT, …; auto-detected per essence). Image essences are not extracted | When a container has multiple usable tracks, one output file is written per track with the track's language code as a suffix: ``` movie.mkv → movie.eng.srt movie.deu.srt movie.fra.srt ``` If two tracks share a language, the track number is added: `movie.#3.eng.srt`. An AVI stream header carries no language, so a multi-stream `.avi` names its outputs after the stream number instead (`movie.xsub_track1.srt`, `movie.xsub_track2.srt`); `--track-number` takes those same AVI stream numbers. | Option | Description | |---|---| | `--track-number:` | Comma-separated track numbers to keep | | `--forced-only` | MKV: keep only forced tracks | | `--teletext-only` | TS: skip DVB-sub OCR (teletext only) | | `--teletext-only-page:` | TS: extract only this teletext page | ### OCR | `--ocr-engine` | Type | Setup | |---|---|---| | `tesseract` *(default)* | Subprocess | Install Tesseract (`apt install tesseract-ocr`, `brew install tesseract`, or the Windows installer); ensure it is on `PATH`. Pass `--ocr-language` as ISO 639-2 (`eng`, `deu`, `spa`, …). | | `nocr` | In-process | Built-in nOCR matcher. Required: `--ocr-db:`. | | `binaryocr` *(alias: `binary`)* | In-process | Built-in BinaryOCR matcher (different accuracy profile, similar speed). Required: `--ocr-db:`. | | `ollama` | HTTP | Local Ollama server with a vision-capable model (e.g. `llama3.2-vision`, `qwen2.5vl`). Configure via `--ollama-url` (default `http://localhost:11434/api/chat`) and `--ollama-model` (default `llama3.2-vision`). Pass `--ocr-language` as a human name like `English`. | | `llamacpp` *(aliases: `llama.cpp`, `llama`)* | HTTP | llama.cpp with a curated OCR vision model (best-first: GLM-OCR, LFM2.5-VL 3B, PaddleOCR-VL, HunyuanOCR 1.5, LightOnOCR). With no `--ocr-url`, seconv finds `llama-server` (SE data folder next to seconv, installed SE data folder, then `PATH`) and an OCR model — the first model in that order that is installed, unless `--ocr-model` names one — starts the server on a free loopback port, and stops it at exit. seconv never downloads engines/models — install them via the SE UI's OCR window (engine "llama.cpp") or point `--ocr-url` at a running server. Pass `--ocr-language` as a human name like `English`. | | `paddle` *(alias: `paddleocr`)* | Subprocess | Install via `pip install paddleocr` (3.7 or newer, for the PP-OCRv6 models); ensure the `paddleocr` binary is on `PATH`. Pass `--ocr-language` as a short code (`en`, `de`, …). | | Option | Description | |---|---| | `--ocr-engine:` | `tesseract` (default) \| `nocr` \| `binaryocr` \| `ollama` \| `llamacpp` \| `paddle` | | `--ocr-language:` | Tesseract: ISO 639-2 (`eng`, `deu`); Paddle: short (`en`); Ollama/llama.cpp: human (`English`) | | `--ocr-db:` | OCR database file: `.nocr` for `nocr`, `.db` for `binaryocr` (required for both) | | `--dictionary-folder:` | Folder with Hunspell dictionaries + `*_OCRFixReplaceList.xml`; enables the "Fix common OCR errors" pass of `--fix-common-errors` (English is bundled, so this is only needed for other languages) | | `--ollama-url:` | Default `http://localhost:11434/api/chat` | | `--ollama-model:` | Default `llama3.2-vision` | | `--ocr-model:` | llama.cpp OCR model: the file name of a model in the llama.cpp models folder - curated (e.g. `GLM-OCR-Q8_0.gguf`) or your own vision model with its `mmproj` sidecar next to it - or a full path to a `.gguf` with its `mmproj` sidecar next to it. Default: the first downloaded OCR model. | | `--ocr-url:` | llama.cpp: endpoint of an already-running `llama-server` (a bare `host:port` is completed to `/v1/chat/completions`); skips the local auto-start. | | `--ocr-prompt:` | Prompt for the prompt-driven OCR engines (`llamacpp`, `ollama`); rejected for the others. `{language}` is replaced with `--ocr-language`. A value that names an existing file, or ends in `.txt`/`.prompt`/`.md`, is read from that file; inline text gets `\n`/`\r`/`\t` unescaped. Default: the same prompt as the SE OCR window, except that LFM2.5-VL gets its own tuned prompt unless `--ocr-prompt` is given. | | `--time-codes-only` | Image sources (`.sup`, VobSub `.sub`/`.idx`, MKV PGS/VobSub, MP4 VobSub, TS DVB-sub, AVI XSUB) → text format with time codes only and empty text. **Skips OCR entirely** — no OCR engine required. Ignored for text inputs and image output targets. | | `--no-vobsub-isolate-colors` | Disable VobSub OCR colour isolation, which is **on by default**. Isolation rebuilds each subpicture as a crisp black-on-white bitmap via histogram-based colour analysis — the most frequent opaque colour (the glyph fill) becomes black and the gray outline / anti-alias colours collapse into the white background, which helps on discs whose outlines otherwise melt adjacent characters together (`Yuri` → `Yurl`). Pass this flag to OCR the raw palette instead. Ignored for non-VobSub sources and with `--time-codes-only`. | | `--no-pgs-isolate-colors` | Disable PGS / DVB-sub OCR colour isolation, which is likewise **on by default**. | > **OCR database files are not bundled with `seconv`.** The `nocr` and `binaryocr` engines need a `.nocr` or `.db` file passed via `--ocr-db`. Sources: > > - If you have the desktop UI installed: `%AppData%\Subtitle Edit\OCR\` (Windows) or `~/.config/Subtitle Edit/OCR/` (Linux/macOS). > - From the repo: [`Ocr/Latin.nocr`](https://github.com/SubtitleEdit/subtitleedit/raw/main/Ocr/Latin.nocr) and [`Ocr/Latin.db`](https://github.com/SubtitleEdit/subtitleedit/raw/main/Ocr/Latin.db). > - Other languages: download from the SE UI (Tools → "OCR with nOCR" / BinaryOCR → download). Run `seconv list-ocr-engines` for the per-engine installation-status table. Long OCR runs report progress as images *finished* of the current source (per PID for TS DVB-sub, per track for MKV). On a terminal this rewrites a single line - ` OCR 42/345 (12%)...`; when stdout is a pipe or a file it prints one plain line per 10% instead, so a log of a 5000-image run holds ten lines rather than one endless one. Auto-translate reports the same way (` Translated 42/345 (12%)...`). Both are suppressed by `--quiet` and `--json`. ```bash # Tesseract seconv movie.sup subrip --ocr-engine:tesseract --ocr-language:eng # nOCR (no external dependency) seconv movie.sup subrip --ocr-engine:nocr --ocr-db:"C:\Users\me\AppData\Roaming\Subtitle Edit\Ocr\Latin.nocr" # BinaryOCR seconv movie.sup subrip --ocr-engine:binaryocr --ocr-db:"C:\Users\me\AppData\Roaming\Subtitle Edit\Ocr\Latin.db" # llama.cpp — auto-starts a local llama-server with a downloaded OCR model seconv movie.sup subrip --ocr-engine:llamacpp seconv movie.sup subrip --ocr-engine:llamacpp --ocr-model:GLM-OCR-Q8_0.gguf seconv movie.sup subrip --ocr-engine:llamacpp --ocr-url:http://127.0.0.1:8080 # Override the OCR prompt (inline or from a file); {language} = --ocr-language seconv movie.sup subrip --ocr-engine:llamacpp --ocr-language:German \ --ocr-prompt:"Identify the number of lines, then extract the text of each line exactly as written. The language is {language}." seconv movie.sup subrip --ocr-engine:llamacpp --ocr-prompt:my-ocr-prompt.txt # MKV with image (PGS or VobSub) tracks — OCR runs automatically seconv movie.mkv subrip --ocr-engine:tesseract --ocr-language:eng # AVI with XSUB (DivX) subtitles — OCR runs automatically seconv movie.avi subrip --ocr-engine:tesseract --ocr-language:eng # VobSub .sub + .idx pair — the .idx companion is auto-detected seconv movie.sub subrip --ocr-engine:tesseract --ocr-language:eng # Transport-stream teletext (no OCR needed) seconv broadcast.ts subrip # Time codes only — extract timing with no OCR (empty text); works for any image source seconv movie.sup subrip --time-codes-only seconv movie.sub subrip --time-codes-only ``` ### Auto-translate `--translate-to:` machine-translates each file as part of the conversion (after OCR for image sources, before the cleanup operations). Languages are given as a code or English name (`de`, `German`, `da`, `Danish`, …); the source language is auto-detected per file unless `--translate-from` is set. Translated output is named with the target language code — `way.srt --translate-to:zh-CN` writes `way.zh-CN.srt` (for container tracks the target code replaces the track's own language suffix, since the content leaves in the target language). An explicit `--output-filename` is used as-is. | Option | Description | |---|---| | `--translate-to:` | Target language — enables translation | | `--translate-from:` | Source language (default: auto-detect per file) | | `--translate-engine:` | `llamacpp` (default) \| `ollama` \| `lmstudio` \| `libretranslate` \| `nllb-serve` \| `nllb-api` | | `--translate-url:` | Endpoint of an already-running translate server. For `llamacpp` this skips the local server auto-start; a bare `host:port` is completed to `/v1/chat/completions`. | | `--translate-model:` | `ollama`/`lmstudio`: model name. `llamacpp`: a `.gguf` file name from the models folder or a full path (default: the first installed translate model). | | `--translate-prompt:` | Prompt for `llamacpp` / `ollama` / `lmstudio` — inline text or a path to a text file. See [Custom prompt](#custom-prompt) below. | **llama.cpp (default engine).** With no `--translate-url`, seconv runs a local `llama-server` for you: it looks for the binary in Subtitle Edit's data folder (`llama.cpp` next to `seconv`, then `%AppData%\Subtitle Edit\llama.cpp` / `~/Library/Application Support/Subtitle Edit/llama.cpp` / `~/.config/Subtitle Edit/llama.cpp`) and falls back to `llama-server` on `PATH`. The server is started on a free localhost port with the model's correct chat-template flags and stopped again when seconv exits. Models resolve against the data folder's `models` subfolder. > **seconv never downloads engines or models** (same policy as Tesseract/PaddleOCR). Get llama.cpp + a translation model in one of these ways: > > - Run auto-translate with llama.cpp once in the Subtitle Edit UI (Tools → Auto-translate → llama.cpp) — it downloads both into the data folder that seconv probes. > - Install llama.cpp yourself (`brew install llama.cpp`, `winget install ggml.llamacpp`, or a [GitHub release](https://github.com/ggml-org/llama.cpp/releases)) and drop a `.gguf` translation model (e.g. [TranslateGemma](https://huggingface.co/SandLogicTechnologies/translategemma-4b-it-GGUF)) into the models folder, or pass it via `--translate-model:`. > - Point `--translate-url` at any llama-server you already have running. ```bash # Translate to German via a local llama.cpp server (started and stopped automatically) seconv movie.srt subrip --translate-to:de # Specific source language and model seconv movie.srt subrip --translate-from:en --translate-to:da --translate-model:translategemma-4b_Q4_K_M.gguf # Already-running llama-server (local or remote) seconv movie.srt subrip --translate-to:de --translate-url:http://192.168.1.10:8080 # Ollama seconv movie.srt subrip --translate-to:da --translate-engine:ollama --translate-model:gemma2 # OCR a Blu-Ray sup and translate the result in one pass seconv movie.sup subrip --ocr-engine:tesseract --ocr-language:eng --translate-to:de ``` #### Custom prompt `--translate-prompt` is the command-line equivalent of the prompt field the GUI offers for the local-LLM engines (see [Prompts: chat models and completion models](../features/auto-translate.md#prompts-chat-models-and-completion-models)). It applies to `llamacpp`, `ollama` and `lmstudio`; the translation services (`libretranslate`, `nllb-serve`, `nllb-api`) have no prompt, and passing it to them is an error rather than a silent no-op. The same placeholders as in the GUI are substituted: `{0}` = source language, `{1}` = target language (both as English names, e.g. `English` / `German`). A prompt that also contains `{2}` is a *completion template*: the subtitle text is placed at `{2}` and the filled-in block is sent as-is, which is what raw-completion translation models such as MiLMMT-46 are trained on. The value is either inline text or the path to a text file. Reading from a file is the practical way to pass a multi-line completion template; in inline text `\n` (also `\r`, `\t`, `\\`) is unescaped, so a short template still fits on one command line. The two are told apart by shape: an existing file is always read, and so is a value that *looks* like a path — no spaces, or a `.txt` / `.prompt` / `.md` extension. A path-shaped value that does not exist is an error, so a typo (`--translate-prompt:prompts/mine.tmpl`) fails immediately instead of being handed to the model as the prompt and quietly mistranslating the whole batch. Prompt text is recognised by containing a space, a line break, or a `{0}`/`{1}`/`{2}` placeholder — which every real prompt does. ```bash # Inline instruction prompt seconv movie.srt subrip --translate-to:de \ --translate-prompt:"Translate from {0} to {1}. Keep the line breaks. Use informal address. Output only the translation:" # Completion template, inline seconv movie.srt subrip --translate-to:da --translate-prompt:"Translate this from {0} to {1}:\n{0}: {2}\n{1}:" # The same, from a file (recommended for anything multi-line) seconv movie.srt subrip --translate-to:da --translate-prompt:milmmt.prompt # Ollama / LM Studio take the same option seconv movie.srt subrip --translate-to:da --translate-engine:ollama --translate-model:gemma2 \ --translate-prompt:my-prompt.txt ``` Precedence for `llamacpp`, highest first: `--translate-prompt`, then a curated model's own trained prompt (MiLMMT-46, Hy-MT2 — applied automatically when that model is selected), then `tools.llamaCppPrompt` from a `--settings` file, then the built-in default. `--translate-prompt` deliberately overrides the curated template too: an option given on the command line must never be a no-op. `ollama` and `lmstudio` have no per-model template, so it is simply `--translate-prompt` > `--settings` > built-in default. ### Templates / replacements | Option | Description | |---|---| | `--multiple-replace:` | Multiple-replace rules applied per paragraph after operations. Accepts the legacy SE *MultipleSearchAndReplaceGroups* XML **and** the file the SE5 GUI exports from *Tools → Multiple replace → export* — either `.template` (JSON) or `.csv`. Supports case-insensitive, `CaseSensitive`, and `RegularExpression` rules; only active rules are applied. The format is chosen by extension, then by content | | `--custom-format:` | SE *CustomFormatItem* XML (use with `--format customtext`) | | `--settings:` | JSON file overlaying `Configuration.Settings` (general / tools / removeTextForHearingImpaired) plus image-output styling (exportImages). Optional `profiles` map for named overlays. The `tools` section also carries the auto-translate prompts (`llamaCppPrompt`, `ollamaPrompt`, `lmStudioPrompt`), so a `profiles` entry can hold a per-target-language prompt | | `--profile:` | Selects a named overlay from the settings file's `profiles` map. Requires `--settings` | #### Multiple-replace rule files The quickest way to share rules between the GUI and `seconv`: in Subtitle Edit open **Tools → Multiple replace**, then **export** the rules as `.template` (JSON) or `.csv`, and pass that file to `--multiple-replace`. Both are read directly — no conversion needed. ```bash seconv *.srt subrip --multiple-replace:my-rules.csv # exported from the GUI seconv *.srt subrip --multiple-replace:my-rules.template # JSON export seconv *.srt subrip --multiple-replace:fixes.xml # legacy SE4 XML ``` CSV columns (a header row is recognised; `Type` is `CaseInsensitive` / `CaseSensitive` / `RegularExpression`): ```csv Category,Find,ReplaceWith,Description,Active,Type Demo,colour,color,,true,CaseInsensitive Demo,"\bteh\b",the,,true,RegularExpression ``` The legacy XML shape is still accepted (its `SearchType` value `Normal` = the GUI's `CaseInsensitive`): ```xml Demo true true colour color Normal true \bteh\b the RegularExpression ``` #### Custom text format XML ```xml JSON-ish .json { "lines": [ {"start": "{start}", "end": "{end}", "text": "{text}"}, ] } hh:mm:ss.zzz ``` ```bash seconv subs.srt customtext --custom-format:lines.xml ``` Available template tokens: `{title}`, `{number}`, `{start}`, `{end}`, `{duration}`, `{gap}`, `{text}`, `{text-line-1}`, `{text-line-2}`, `{actor}`, `{cps-comma}`, `{cps-period}`, `{text-length}`, `{bookmark}`, `{media-file-name}`, `{media-file-name-full}`, `{#lines}`, `{#total-words}`, `{#total-characters}`, `{tab}`. Time-code tokens accept `hh`/`h`, `mm`/`m`, `ss`/`s`, and `zzz`/`zz`/`z` for milliseconds (or `zzzzzzz...` for total milliseconds without breakdown). #### Settings JSON > **This is seconv's own schema — not the Subtitle Edit GUI's `Settings.json`.** The desktop app's settings file uses different key names (e.g. the GUI's `IsRemoveTextUppercaseLineOn` is `removeIfAllUppercase` here) and a different structure; feeding it to `--settings` won't do what you expect (unrecognized keys are ignored with a warning). To get a correct starter file, run **`seconv dump-settings`** — it prints this whole schema populated with the current libse defaults: > > ```bash > seconv dump-settings > my-settings.json # edit, then: --settings:my-settings.json > ``` The keys and defaults below are exactly what `dump-settings` emits: ```json { "general": { "subtitleLineMaximumLength": 43, "subtitleMinimumDisplayMilliseconds": 1000, "subtitleMaximumDisplayMilliseconds": 8000, "currentFrameRate": 23.976, "defaultFrameRate": 23.976, "minimumMillisecondsBetweenLines": 24, "maxNumberOfLines": 2, "mergeLinesShorterThan": 33, "subtitleMaximumCharactersPerSeconds": 25, "subtitleOptimalCharactersPerSeconds": 15, "subtitleMaximumWordsPerMinute": 400, "dialogStyle": "DashBothLinesWithSpace", "continuationStyle": "None" }, "tools": { "mergeShortLinesMaxGap": 250, "mergeShortLinesOnlyContinuous": true, "llamaCppPrompt": "Translate from {0} to {1}, keep punctuation as input, keep line breaks exactly the same, do not censor the translation, give only the output without comments:", "ollamaPrompt": "Translate from {0} to {1}, keep punctuation as input, keep line breaks exactly the same, do not censor the translation, give only the output without comments or notes:", "lmStudioPrompt": "Translate from {0} to {1}, keep punctuation as input, keep line breaks exactly the same, do not censor the translation, give only the output without comments:" }, "removeTextForHearingImpaired": { "removeTextBetweenBrackets": true, "removeTextBetweenParentheses": true, "removeTextBetweenCurlyBrackets": true, "removeTextBetweenQuestionMarks": true, "removeTextBetweenCustom": false, "removeTextBetweenCustomBefore": "\u00B6", "removeTextBetweenCustomAfter": "\u00B6", "removeTextBetweenOnlySeparateLines": false, "removeTextBeforeColon": true, "removeTextBeforeColonOnlyIfUppercase": true, "removeTextBeforeColonOnlyOnSeparateLine": false, "removeInterjections": false, "removeInterjectionsOnlyOnSeparateLine": false, "removeIfContains": false, "removeIfAllUppercase": false, "removeIfContainsText": "\u00B6", "removeIfOnlyMusicSymbols": true }, "exportImages": { "fontName": "Arial", "fontSize": 50, "fontColor": "#FFFFFFFF", "isBold": false, "outlineColor": "#FF000000", "outlineWidth": 2.5, "shadowColor": "#FF000000", "shadowWidth": 0, "backgroundColor": "#00FFFFFF", "backgroundCornerRadius": 0, "boxType": "None", "boxPaddingLeft": 5, "boxPaddingRight": 5, "boxPaddingTop": 3, "boxPaddingBottom": 3, "lineSpacingPercent": 0, "isFullFrame": false, "fullFrameBackgroundColor": "#00FFFFFF", "alignment": "BottomCenter", "contentAlignment": "Center" } } ``` The `exportImages` section styles text → image rendering (see [Image output styling](#image-output-styling) for the semantics); the equivalent CLI flags override it. Colours are emitted as `#AARRGGBB` (so `backgroundColor` / `fullFrameBackgroundColor` default to fully transparent, `#00FFFFFF`, and `boxType` to `None`); `boxType`, `alignment`, and `contentAlignment` are emitted as enum names but also accept the CLI spellings (`one-box`, `bottom-center`, …). Two optional `exportImages` keys are read but not emitted: `bottomTopMargin` and `leftRightMargin` (pixels; default 5% of the frame height / width). The `tools` section holds the merge-short-lines settings and the auto-translate prompts. The `general` section mirrors `Configuration.Settings.General`; any key left out keeps the libse default. The profile-shaping values (`minimumMillisecondsBetweenLines`, `maxNumberOfLines`, `mergeLinesShorterThan`, `subtitleMaximumCharactersPerSeconds`, `subtitleOptimalCharactersPerSeconds`, `subtitleMaximumWordsPerMinute`, `dialogStyle`, `continuationStyle`) feed Fix common errors and the split/merge operations, so set them to reproduce an SE4 profile. `dialogStyle` and `continuationStyle` take the enum names (case-insensitive): `dialogStyle` ∈ `DashBothLinesWithSpace`, `DashBothLinesWithoutSpace`, `DashSecondLineWithSpace`, `DashSecondLineWithoutSpace`; `continuationStyle` ∈ `None`, `NoneTrailingDots`, `NoneTrailingEllipsis`, `OnlyTrailingDots`, `LeadingTrailingDots`, `LeadingTrailingEllipsis`, `LeadingTrailingDash`, … (see the Fix common errors continuation styles). Keys that seconv does not recognize are ignored, so a settings file written for a newer version still applies everything this one understands — but they are listed in a warning, so a typo (or a key your seconv is too old to know) does not silently give you default output. A `profiles` map (not emitted by `dump-settings`) adds named overlays with the same sections, selected with `--profile`: ```json { "profiles": { "broadcast": { "general": { "subtitleMaximumDisplayMilliseconds": 6000 }, "removeTextForHearingImpaired": { "removeInterjections": true } }, "uhd": { "exportImages": { "fontSize": 100 } } } } ``` ```bash seconv *.srt subrip --settings:my.json --profile:broadcast --remove-text-for-hi ``` ### Verbosity | Option | Description | |---|---| | `--quiet` / `-q` | Suppress per-file progress, the parameters table, and the OCR/translate progress lines; only print the final summary | | `--verbose` / `-v` | Print extra diagnostic information, including full exception details (stack traces) on errors | | `--json` | Emit per-file results as JSON to stdout (suppresses Spectre output). Also accepted by every subcommand. Failures use the same envelope, so stdout is always one JSON document | ## Operations Operations run after the structural transforms (offset, fps, renumber, adjust-duration, change-speed) in a fixed, sensible order regardless of where they appear on the command line: | Option | Description | |---|---| | `--apply-duration-limits` | Apply duration limits | | `--apply-min-gap[:]` | Enforce a minimum gap between paragraphs. Without a value, uses `minimumMillisecondsBetweenLines` from `--settings` (libse default: 24 ms) | | `--balance-lines` | Balance line lengths | | `--beautify-time-codes` | Beautify time codes | | `--bridge-gaps:` | Bridge gaps shorter than N ms (extends previous end time) | | `--convert-colors-to-dialog` | Convert colors to dialog | | `--delete-first:` | Delete first N entries | | `--delete-last:` | Delete last N entries | | `--delete-contains:` | Delete entries containing the given word | | `--fix-common-errors` | Fix common subtitle errors (all 40 rules) | | `--fix-common-errors-rules:` | Run a subset of FCE rules (CSV; supports `all,-RuleId`) | | `--fce-language:` | Force the language for FCE language-gated rules (code or English name, e.g. `es` / `Spanish`); default: auto-detect from content | | `--fix-rtl-via-unicode-chars` | Fix RTL via Unicode characters | | `--merge-same-texts` | Merge entries with same text | | `--merge-same-time-codes` | Merge entries with same time codes | | `--merge-short-lines` | Merge short lines | | `--redo-casing` | Redo text casing | | `--remove-formatting` | Remove **all** formatting tags | | `--remove-formatting-rules:` | Remove only some kinds of formatting (CSV; supports `all,-RuleId`) | | `--remove-line-breaks` | Remove line breaks | | `--remove-text-for-hi` | Remove text for hearing impaired | | `--remove-unicode-control-chars` | Remove Unicode control characters | | `--reverse-rtl-start-end` | Reverse RTL start/end | | `--split-long-lines` | Split long lines | ```bash # Common cleanup pass seconv *.srt subrip --remove-text-for-hi --merge-same-texts --split-long-lines --overwrite ``` #### `--apply-min-gap` vs `--fix-common-errors-rules:FixOverlappingDisplayTimes` Both touch boundary timings but solve different problems: - **`--apply-min-gap[:]`** walks every pair of adjacent paragraphs and, when the gap is shorter than ``, **shortens the previous cue's end time** to open it up. It does **not** move the next cue's start. Pairs are also **skipped** when shortening would drop the previous cue below `General.SubtitleMinimumDisplayMilliseconds` — so this is best-effort, not a hard guarantee. Use it for delivery specs (e.g. broadcast standards that ask for 2 frames between cues) while accepting that minimum-display-duration collisions are left alone. - **`FixOverlappingDisplayTimes`** is one rule inside Fix Common Errors and only resolves cases where a paragraph's end > the next paragraph's start (a true overlap). It does *not* enforce a non-zero minimum gap once overlaps are gone. In most batch pipelines `--apply-min-gap` is the better choice; reach for the FCE rule when you specifically want to keep the tight gaps the source already has and only repair true overlaps. ### FixCommonErrors rule selection `--fix-common-errors` (no value) runs all 40 rules (39 fixes plus the `FixCommonOcrErrors` pass). Pass `--fix-common-errors-rules:` to pick a subset — supplying that option implies `--fix-common-errors`. ```bash seconv movie.srt subrip --fix-common-errors # all rules seconv movie.srt subrip --fix-common-errors-rules:FixCommas,FixMissingSpaces seconv movie.srt subrip --fix-common-errors-rules:all,-FixDanishLetterI # all except one seconv movie.srt subrip --fix-common-errors --fce-language:es # force the Spanish gate seconv list-fce-rules # show rule IDs (marks language gates) ``` **Language-specific rules.** A few rules only make sense for one language and mirror the GUI's *Fix Common Errors* window, which only offers them when the detected language matches: `FixAloneLowercaseIToUppercaseI` (English), `FixDanishLetterI` (Danish), `FixSpanishInvertedQuestionAndExclamationMarks` (Spanish), and `FixTurkishAnsiToUnicode` (Turkish). `seconv list-fce-rules` marks each gated rule with its language in a *Language gate* column. These run **only when the language matches** — so e.g. the Spanish inverted-`¿` fix never lands on English content (issue #11037). This holds however the rule was selected: naming it in `--fix-common-errors-rules` picks it into the run but does **not** bypass the gate, which makes mixed-language batches safe (a Spanish rule in your rule set self-skips on the English and French files). The language is auto-detected from the content. To force it: - **`--fce-language:`** forces the language used for *all* gated rules (and the OCR-fix pass). Use it when a genuinely Spanish/Danish/Turkish file mis-detects (e.g. it's too short), or to run a named language rule on content that would auto-detect as something else. Accepts a two-letter code, three-letter code, or English name (`es`, `spa`, `Spanish`); an unrecognized value warns and falls back to auto-detection. **Rule selection is CLI-only.** The set of rules is chosen with `--fix-common-errors-rules`, not through the `--settings` JSON. The settings file shapes *how* the rules behave (line length, min gap, dialog/continuation style, CPS — see [Settings JSON](#settings-json)); it does not select which rules run. `FixCommonOcrErrors` runs only when a dictionary folder is available — bundled for English, or supplied via `--dictionary-folder` for other languages (see [OCR options](#ocr)). Without one, that rule is skipped and every other rule still runs. #### Rule ID ↔ GUI equivalent The CLI rule IDs match the check-box rules in the desktop app's *Fix Common Errors* window. If you prototyped a fix set in the GUI, use this table (or `seconv list-fce-rules`, which prints the same three columns) to find the matching `--fix-common-errors-rules` IDs. *Language gate* marks rules that only run for one detected language (override with `--fce-language`, or by naming the rule). | Rule ID | GUI equivalent | Language gate | |---|---|---| | `AddMissingQuotes` | Add missing quotes (") | — | | `Fix3PlusLines` | Fix subtitles with more than two lines | — | | `FixAloneLowercaseIToUppercaseI` | Fix alone lowercase 'i' to 'I' (English) | en only | | `FixCommas` | Fix commas | — | | `FixContinuationStyle` | Fix continuation style | — | | `FixDanishLetterI` | Fix Danish letter 'i' | da only | | `FixDialogsOnOneLine` | Split dialogs on one line | — | | `FixDoubleApostrophes` | Fix double apostrophe characters ('') to a single quote (") | — | | `FixDoubleDash` | Fix '--' -> '...' | — | | `FixDoubleGreaterThan` | Remove '>>' | — | | `FixEllipsesStart` | Remove leading '...' | — | | `FixEmptyLines` | Remove empty lines/unused line breaks | — | | `FixHyphensInDialog` | Fix dash in dialogs via style | — | | `FixHyphensRemoveDashSingleLine` | Remove dialog dashes in single lines | — | | `FixInvalidItalicTags` | Fix invalid italic tags | — | | `FixLongDisplayTimes` | Fix long display times | — | | `FixLongLines` | Break long lines | — | | `FixMissingOpenBracket` | Fix missing [ or ( in line | — | | `FixMissingPeriodsAtEndOfLine` | Add period after lines where next line starts with uppercase letter | — | | `FixMissingSpaces` | Fix missing spaces | — | | `FixMusicNotation` | Replace music symbols with preferred symbol | — | | `FixOverlappingDisplayTimes` | Fix overlapping display times | — | | `FixShortDisplayTimes` | Fix short display times | — | | `FixShortGaps` | Fix short gaps | — | | `FixShortLines` | Remove line breaks in short texts with only one sentence | — | | `FixShortLinesAll` | Remove line breaks in short texts (all except dialogs) | — | | `FixShortLinesPixelWidth` | Unbreak subtitles that can fit on one line (pixel width) | — | | `FixSpanishInvertedQuestionAndExclamationMarks` | Fix Spanish inverted question and exclamation marks | es only | | `FixStartWithUppercaseLetterAfterColon` | Start with uppercase letter after colon/semicolon | — | | `FixStartWithUppercaseLetterAfterParagraph` | Start with uppercase letter after paragraph | — | | `FixStartWithUppercaseLetterAfterPeriodInsideParagraph` | Start with uppercase letter after period inside paragraph | — | | `FixTurkishAnsiToUnicode` | Fix Turkish ANSI (Icelandic) letters to Unicode | tr only | | `FixUnnecessaryLeadingDots` | Remove unnecessary leading dots | — | | `FixUnneededPeriods` | Remove unneeded periods | — | | `FixUnneededSpaces` | Remove unneeded spaces | — | | `FixUppercaseIInsideWords` | Fix uppercase 'i' inside lowercase words (OCR error) | — | | `NormalizeStrings` | Normalize strings | — | | `RemoveDialogFirstLineInNonDialogs` | Remove start dash in first line for non-dialogs | — | | `RemoveSpaceBetweenNumbers` | Remove space between numbers | — | | `FixCommonOcrErrors` | Fix common OCR errors (using OCR replace list) | — | ### Remove-formatting rule selection `--remove-formatting` (no value) strips **every** tag — HTML plus SSA/ASSA override blocks. Pass `--remove-formatting-rules:` to remove only some kinds of formatting; supplying that option implies `--remove-formatting`. ```bash seconv movie.ass subrip --remove-formatting # remove every tag seconv movie.ass subrip --remove-formatting-rules:RemoveItalic,RemoveBold seconv movie.ass subrip --remove-formatting-rules:all,-RemoveColor # every named rule except colors seconv list-rf-rules # show rule IDs ``` **`all` is narrower than the bare flag.** The bare `--remove-formatting` removes tags wholesale, including ones no named rule covers — positioning (`{\pos(..)}`), fades, karaoke timing, and any other ASSA override. `--remove-formatting-rules:all` is the *union of the six named rules*, so those tags survive it: ```bash # "{\pos(10,20)}Hi" seconv in.ass subrip --remove-formatting # -> "Hi" seconv in.ass subrip --remove-formatting-rules:all # -> "{\pos(10,20)}Hi" ``` This mirrors the desktop app, where batch convert's *Remove formatting* function has a separate *Remove all formatting* check box above the six per-kind ones. #### Rule ID ↔ GUI equivalent | Rule ID | GUI equivalent | Removes | |---|---|---| | `RemoveItalic` | Remove italic | ``, `{\i0}`, `{\i1}` | | `RemoveBold` | Remove bold | ``, `{\b0}`, `{\b1}` | | `RemoveUnderline` | Remove underline | ``, `{\u0}`, `{\u1}` | | `RemoveFontName` | Remove font name | ``, `{\fnArial}` | | `RemoveAlignment` | Remove alignment | `{\an1}`–`{\an9}`, `{\a1}`–`{\a9}` | | `RemoveColor` | Remove color | ``, `{\c&H..&}`, `{\1c&H..&}` | ## Output format aliases | Aliases | Format | |---|---| | `srt`, `subrip` | SubRip | | `ass`, `assa` | Advanced Sub Station Alpha | | `ssa` | Sub Station Alpha | | `vtt`, `webvtt` | WebVTT | | `smi`, `sami` | SAMI | | `sbv` | YouTube SBV | | `pac` | PAC (Screen Electronics) — binary | | `unipac`, `pacunicode` | PAC Unicode | | `ebu`, `ebustl`, `stl` | EBU STL — binary | | `cavena`, `cavena890` | Cavena 890 — binary | | `cheetah`, `cheetahcaption` | CheetahCaption — binary | | `capmaker`, `capmakerplus` | CapMakerPlus — binary | | `ayato` | Ayato — binary | | `bluraysup`, `blurayup`, `sup` | Blu-Ray sup — image | | `vobsub` | VobSub — image | | `bdnxml`, `bdn-xml` | BDN-XML — image (folder of PNGs + index.xml) | | `bdnxml8bit`, `bdn-xml8-bit` | BDN-XML with 8-bit palette-indexed PNGs — image | | `dost`, `dostimage` | DOST/image | | `fcpimage`, `fcp` | FCP/image | | `dcinemainterop`, `dcinema-interop` | D-Cinema interop/png | | `dcinemasmpte2014`, `dcinema-smpte` | D-Cinema SMPTE 2014/png | | `imageswithtimecode`, `imagesintc` | Images with time codes in file name | | `webvttthumbnail`, `webvtt-thumbnail`, `vttthumb` | WebVTT Thumbnail — image (sprite sheet + `.vtt`) | | `plaintext`, `text`, `txt` | Plain text (HTML stripped) | | `customtext`, `customtextformat` | Custom-templated text (requires `--custom-format`) | Run `seconv formats` for the full catalog (380+ entries, including input-only formats like Matroska, MP4, and MCC), or `seconv formats --json` for the machine-readable list whose `id` field is exactly what `--format` accepts. ## Exit codes | Code | Meaning | |---|---| | `0` | Conversion succeeded for all matched files | | `1` | Any error: bad usage, unknown option, rejected option value, no files matched, parse error, OCR engine missing, invalid `--settings` file, or one or more files failed to convert | There are only these two. Every failure path — including argument parsing — exits 1. ## Legacy syntax - Old SE 4.x `/parameter:value` syntax is auto-translated to `--parameter:value`. - Older smashed-together long options (`--inputfolder`, `--ocrengine`, `--FixCommonErrors`, …) are kept as hidden aliases for the new POSIX-style names (`--input-folder`, `--ocr-engine`, `--fix-common-errors`, …), so existing scripts keep working. ## More examples ### Bulk format conversion ```bash seconv "*.srt" assa --input-folder:./input --output-folder:./output --overwrite ``` ### MKV → SRT extraction ```bash seconv movie.mkv subrip --overwrite # one SRT per language seconv movie.mkv subrip --track-number:3 --overwrite # only track 3 seconv movie.mkv subrip --forced-only --overwrite # forced only ``` ### Blu-Ray sup OCR with cleanup ```bash seconv subs.sup subrip \ --ocr-engine:tesseract \ --ocr-language:eng \ --remove-text-for-hi \ --split-long-lines \ --overwrite ``` ### Round-trip via image-based output ```bash seconv subs.srt bluraysup --resolution:1920x1080 --overwrite seconv subs.srt bdnxml --overwrite ``` ### Styled Blu-Ray sup (SE4 `/convert` parity) ```bash # SE4: SubtitleEdit.exe /convert movie.srt blu-raysup /Resolution:3840x2160 (+ styling from Settings.xml) seconv movie.srt bluraysup \ --resolution:3840x2160 \ --font-name:Arial --font-size:100 \ --background-color:"#B4000000" \ --overwrite ``` ### EBU STL with reused header ```bash seconv new.srt ebustl --ebu-header-file:original.stl --overwrite ``` ### Time shift + frame-rate conversion ```bash seconv subs.srt subrip --fps:24 --target-fps:25 --offset:500 --overwrite ``` ### Plain text export ```bash seconv movie.srt plaintext --overwrite ``` ### Custom JSON output via template ```bash seconv movie.srt customtext --custom-format:lines-template.xml --output-filename:movie.json --overwrite ``` ## Architecture `seconv` depends only on Subtitle Edit's core libraries — no Avalonia / GUI runtime: ``` src/ ├── libse/ Core subtitle library (NuGet-shippable, netstandard2.1;net10.0) ├── libuilogic/ Shared headless logic (BatchConverter pipeline, OCR matchers, image renderer) ├── seconv/ This CLI └── ui/ Avalonia desktop UI (not referenced by seconv) ``` This means `seconv` can run on systems without a display (CI, headless servers, Docker) without bundling Avalonia or any X server. ## See also - [Supported Formats](supported-formats.md) — full format catalog - [Batch Convert](../features/batch-convert.md) — GUI equivalent - [OCR](../features/ocr.md) — engine details and language packs