Subtitle Edit

the subtitle editor :)


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

Installation

Pre-built binaries are distributed alongside Subtitle Edit. To build from source:

dotnet build src/seconv/SeConv.csproj -c Release

The executable is seconv (or seconv.exe on Windows).

Usage

seconv <pattern> <format> [options]
seconv <pattern> --format <name> [options]   # alternative syntax

The format may be passed as the second positional argument or via --format <name>. Multiple input patterns can be passed as separate quoted arguments or comma-separated:

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.

$ seconv movie.srt subrip --remove-formating
Error: Unknown option '--remove-formating'. Did you mean '--remove-formatting'?

Quick examples

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 *.mkv subrip --ocr-engine:applevision                       # macOS: OCR via built-in Apple Vision

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

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 <file>          # print format/encoding/duration/language for a file
seconv lint <pattern>       # validate subtitle(s); exit 1 if issues found
seconv optimize-nocr <db>   # remove nOCR lines that don't tell characters apart (in place + .bak, or --output:<path>)
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.

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.

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:

{ "success": false, "totalFiles": 0, "files": [], "errors": ["Unknown option '--bogus'."], "warnings": [] }

Inspect & validate

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:<path> Input folder; relative patterns resolve against it
--output-folder:<path> Output folder (default: input file’s directory)
--output-filename:<name> Output file name (single input only)
--output-filename-append:<text> 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, …)
--no-language-suffix Do not insert the language code before the extension (movie.srt instead of movie.en.srt for container tracks and --translate-to). A forced track keeps its marker (movie.forced.srt). With --overwrite and --translate-to the file is translated in place
--keep-timestamp (also --keep-timestamps) Give output files the source file’s modified/created date instead of the conversion time
--encoding:<name> 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:<name> 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:<rate> Source frame rate (used for frame-based input formats)
--target-fps:<rate> Target frame rate (with --fps, calls Subtitle.ChangeFrameRate)
--adjust-duration:<ms> Add/subtract milliseconds to each paragraph’s duration
--change-speed:<percent> Scale all times by 100/percent (e.g. 125 = 1.25× faster). Must be > 0
--renumber:<n> Renumber paragraphs starting at n

Format-specific

Option Applies to Description
--resolution:<WxH> ASSA, image-based Sets PlayResX/PlayResY for ASSA; sets canvas for image outputs (default 1920x1080)
--assa-style-file:<file> ASSA Apply [V4+ Styles] block from another ASSA file
--pac-codepage:<page> PAC Code page name (Latin, Greek, Hebrew, …) or numeric (0–12). See seconv list-pac-codepages
--pac-secondary-codepage:<page> PAC Code page for lines in another script (e.g. Cyrillic for the Russian lines of a Hebrew file); those lines are flagged “secondary code page”. Reading detects it automatically
--ebu-header-file:<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. <i>, <b>, and <font color=...> tags in the subtitle text are honored. The same values can be set in a --settings JSON file’s exportImages section (see Settings JSON); CLI flags win over the JSON.

Option Description
--font-name:<name> Font family (default: Arial)
--font-size:<pt> Font size in points (default: 50)
--font-color:<color> Text colour (default: white)
--font-bold Render text bold
--outline-color:<color> Outline colour (default: black)
--outline-width:<px> Outline width (default: 2.5; 0 disables)
--shadow-color:<color> Shadow colour (default: black)
--shadow-width:<px> Shadow width (default: 0 = off)
--background-color:<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:<px> Corner radius of the background box (default: 0)
--box-type:<type> none (default) | one-box | box-per-line
--box-padding:<px> Box padding: one value for all sides, or left,right,top,bottom (default: 5,5,3,3)
--line-spacing:<percent> Extra gap between lines as percent of line height (default: 0)
--alignment:<pos> Screen position: bottom-center (default), top-left, middle-right, …
--content-alignment:<align> Multi-line text justification: left | center (default) | right | from-alignment (follow the {\anX} tag)
--bottom-top-margin:<px> Vertical screen-edge margin (default: 5% of height)
--left-right-margin:<px> Horizontal screen-edge margin (default: 5% of width)
--override-position:<x\|y\|xy> 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:<color> Background of the full frame image (default: transparent)
--mode-3d:<mode> Draw each subtitle for frame-packed 3D video, once per eye: none (default) | half-side-by-side (sbs) | half-top-bottom (tab). Works for text → image and image → image; D-Cinema warns and ignores it
--depth-3d:<px> 3D depth, -100 to 100 (default: 0): positive brings the subtitle out of the screen, negative pushes it back. D-Cinema writes it as the image’s Z-position
--plane-3d:<file.ofs> A 3D Blu-ray’s 3D-Plane (OFS file from BD3D2MK3D or OFSExtractor): each subtitle gets the disc’s depth for the frames it is shown on. --depth-3d is used where the 3D-Plane has none. Needs --mode-3d

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.

3D (--mode-3d) matches the “3D” option in the export dialog (SE4’s 3D export). A half side-by-side or half top/bottom video holds a squeezed view per eye, so the subtitle is squeezed the same way and drawn once in each view, where the alignment, margins or {\pos} would put it. --depth-3d moves the two copies apart. With an image source (.sup, VobSub, DVB-sub, …) it turns a 2D track into a 3D one, keeping each subtitle’s position.

A 3D Blu-ray’s subtitle track is flat: the player moves it by a depth stored for every frame, in the track’s 3D-Plane. --plane-3d reads that 3D-Plane from an .ofs file, so each subtitle stands where the disc puts it. A subtitle gets the depth nearest to the viewer during its frames. The offsets are in pixels of the 1920 wide Blu-ray view, scaled to --resolution and halved for half side-by-side.

# 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

# 2D Blu-ray sup → half side-by-side 3D Blu-ray sup, standing slightly out of the screen
seconv movie.sup bluraysup --mode-3d:half-side-by-side --depth-3d:4 --output-filename-append:.3d

# 3D Blu-ray subtitle track → half side-by-side sup, at the disc's own depth (tsMuxeR: "3d-plane: 2")
seconv track.sup bluraysup --mode-3d:half-side-by-side --plane-3d:3D-Plane-02.ofs --output-filename-append:.3d

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:<list> 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:<n> 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:<path-to-Latin.nocr>.
binaryocr (alias: binary) In-process Built-in BinaryOCR matcher (different accuracy profile, similar speed). Required: --ocr-db:<path-to-Latin.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, …). Images are OCR’ed in batches: the prepared images of a file are written to a folder and one paddleocr process reads the whole folder, so the roughly twenty-second model load is paid once per file rather than once per image.
applevision (alias: apple-vision) In-process (macOS only) macOS’s built-in Vision framework — the same recognizer as the GUI’s “Apple Vision” engine. Nothing to install. Pass --ocr-language as a Vision tag (en-US, de-DE, zh-Hans, …; default en-US); de, deu and German are mapped to the matching tag too. seconv list-ocr-engines --json lists the tags this Mac supports.
Option Description
--ocr-engine:<engine> tesseract (default) | nocr | binaryocr | ollama | llamacpp | paddle | applevision
--ocr-language:<lang> Tesseract: ISO 639-2 (eng, deu); Paddle: short (en); Ollama/llama.cpp: human (English); Apple Vision: Vision tag (en-US)
--ocr-db:<path> OCR database file: .nocr for nocr, .db for binaryocr (required for both)
--dictionary-folder:<path> 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)
--ocr-auto-detect-assa-alignment Add an ASSA alignment tag from where each subtitle image sits in the video frame — the same logic as Auto-detect ASSA alignment in the OCR window. The frame is divided into a 3×3 grid: a caption at the top centre gets {\an8}, a sign at the left edge {\an4}, and so on; bottom-centre is the default and gets no tag. A tall image holding a few short lines placed apart (one at the top, one at the bottom) is tagged per line and written as separate subtitles with the same time codes. Sources that do not report a frame size (MP4 VobSub) are left untagged. Ignored with --time-codes-only.
--ollama-url:<url> Default http://localhost:11434/api/chat
--ollama-model:<model> Default llama3.2-vision
--ocr-model:<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:<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:<text\|file> 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 — except for applevision, which always reads the original images (binarising costs Vision umlauts and trailing punctuation, and the GUI does not binarise for it either), and for nocr / binaryocr, which split letters on the original image’s transparency and cannot segment the opaque isolated bitmap.

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 and 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`.

# 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

# Apple Vision (macOS) — nothing to install
seconv movie.sup subrip --ocr-engine:applevision
seconv movie.sup subrip --ocr-engine:applevision --ocr-language:de-DE

# MKV with image (PGS or VobSub) tracks — OCR runs automatically
seconv movie.mkv subrip --ocr-engine:tesseract --ocr-language:eng
seconv "*.mkv" subrip --input-folder:"$HOME/Movies" --ocr-engine:applevision   # a whole folder on a Mac

# 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:<language> 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, and --no-language-suffix keeps the plain name (way.srt) — combine it with --overwrite to translate a file in place.

Option Description
--translate-to:<lang> Target language — enables translation
--translate-from:<lang> Source language (default: auto-detect per file)
--translate-engine:<engine> llamacpp (default) | ollama | lmstudio | openai-compatible | libretranslate | nllb-serve | nllb-api | deepl
--translate-url:<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:<model> ollama/lmstudio/openai-compatible: model name. llamacpp: a .gguf file name from the models folder or a full path (default: the first installed translate model).
--translate-api-key:<key> API key for openai-compatible (sent as a Bearer token, optional) and deepl (required; a free-tier :fx key is routed to api-free.deepl.com, a Pro key to api.deepl.com). --translate-url overrides the DeepL endpoint (default https://api-free.deepl.com/) or the OpenAI-compatible endpoint (default http://localhost:8000/v1/chat/completions).
--translate-prompt:<text\|file> Prompt for llamacpp / ollama / lmstudio / openai-compatible — inline text or a path to a text file. See 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) and drop a .gguf translation model (e.g. TranslateGemma) into the models folder, or pass it via --translate-model:<path.gguf>.
  • Point --translate-url at any llama-server you already have running.
# 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

# Any OpenAI-compatible API (vLLM, Together, ...)
seconv movie.srt subrip --translate-to:de --translate-engine:openai-compatible --translate-url:https://api.example.com/v1 --translate-api-key:<key> --translate-model:<model>

# DeepL
seconv movie.srt subrip --translate-to:de --translate-engine:deepl --translate-api-key:<key>

# 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). 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.

# 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:<path> Multiple-replace rules applied per paragraph after operations. Accepts the legacy SE MultipleSearchAndReplaceGroups XML and the file the SE5 GUI exports from Edit → 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:<path.xml> SE CustomFormatItem XML (use with --format customtext)
--settings:<path.json> 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. tools.ocrUseWordSplitList: true lets Fix common OCR errors split run-together words via the word split list (off by default, as in the GUI, because it also splits valid words missing from the dictionary)
--profile:<name> 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 Edit → 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.

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):

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 version="1.0" encoding="utf-8"?>
<MultipleSearchAndReplaceGroups>
  <Group>
    <Name>Demo</Name>
    <IsActive>true</IsActive>
    <Rules>
      <Rule>
        <Active>true</Active>
        <FindWhat>colour</FindWhat>
        <ReplaceWith>color</ReplaceWith>
        <SearchType>Normal</SearchType>
      </Rule>
      <Rule>
        <Active>true</Active>
        <FindWhat>\bteh\b</FindWhat>
        <ReplaceWith>the</ReplaceWith>
        <SearchType>RegularExpression</SearchType>
      </Rule>
    </Rules>
  </Group>
</MultipleSearchAndReplaceGroups>

Custom text format XML

<?xml version="1.0" encoding="utf-8"?>
<CustomFormatItem>
  <Name>JSON-ish</Name>
  <Extension>.json</Extension>
  <FormatHeader>{
  "lines": [</FormatHeader>
  <FormatParagraph>    {"start": "{start}", "end": "{end}", "text": "{text}"},</FormatParagraph>
  <FormatFooter>  ]
}</FormatFooter>
  <FormatTimeCode>hh:mm:ss.zzz</FormatTimeCode>
</CustomFormatItem>
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:

seconv dump-settings > my-settings.json   # edit, then: --settings:my-settings.json

The keys and defaults below are exactly what dump-settings emits:

{
  "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:",
    "ocrUseWordSplitList": false
  },
  "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",
    "mode3D": "None",
    "depth3D": 0,
    "alignment": "BottomCenter",
    "contentAlignment": "Center"
  }
}

The exportImages section styles text → image rendering (see 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, mode3D, alignment, and contentAlignment are emitted as enum names but also accept the CLI spellings (one-box, half-sbs, 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:

{
  "profiles": {
    "broadcast": {
      "general": { "subtitleMaximumDisplayMilliseconds": 6000 },
      "removeTextForHearingImpaired": { "removeInterjections": true }
    },
    "uhd": {
      "exportImages": { "fontSize": 100 }
    }
  }
}
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[:<ms>] 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:<ms> Bridge gaps shorter than N ms (extends previous end time)
--convert-colors-to-dialog Convert colors to dialog
--delete-first:<n> Delete first N entries
--delete-last:<n> Delete last N entries
--delete-contains:<word> Delete entries containing the given word
--fix-common-errors Fix common subtitle errors (all 40 rules)
--fix-common-errors-rules:<list> Run a subset of FCE rules (CSV; supports all,-RuleId)
--fce-language:<code> 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:<list> 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
# 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:

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:<list> to pick a subset — supplying that option implies --fix-common-errors.

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:

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); it does not select which rules run.

Opt-in rules. FixMisreadQuotes can change correct text (British-style 'quotes' become "quotes"), so it is not part of all — neither the --fix-common-errors default, all, nor a negation-only spec runs it. Name it explicitly to use it, e.g. --fix-common-errors-rules:all,FixMisreadQuotes.

FixCommonOcrErrors runs only when a dictionary folder is available — bundled for English, or supplied via --dictionary-folder for other languages (see OCR options). 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
FixMisreadQuotes Fix apostrophes misread as double quotes (OCR, English) en only, opt-in
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:<list> to remove only some kinds of formatting; supplying that option implies --remove-formatting.

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:

# "{\pos(10,20)}<i>Hi</i>"
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 <i>, {\i0}, {\i1}
RemoveBold Remove bold <b>, {\b0}, {\b1}
RemoveUnderline Remove underline <u>, {\u0}, {\u1}
RemoveFontName Remove font name <font face="..">, {\fnArial}
RemoveAlignment Remove alignment {\an1}–{\an9}, {\a1}–{\a9}
RemoveColor Remove color <font 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
dvdsup, spdvdsup DVD sup (SP-wrapped DVD subpictures) — 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
imscimage, imsc-image IMSC 1.1 image profile — image (.ttml plus one PNG per subtitle next to it)
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

More examples

Bulk format conversion

seconv "*.srt" assa --input-folder:./input --output-folder:./output --overwrite

MKV → SRT extraction

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

seconv subs.sup subrip \
  --ocr-engine:tesseract \
  --ocr-language:eng \
  --remove-text-for-hi \
  --split-long-lines \
  --overwrite

Round-trip via image-based output

seconv subs.srt bluraysup --resolution:1920x1080 --overwrite
seconv subs.srt bdnxml --overwrite

Styled Blu-Ray sup (SE4 /convert parity)

# 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

seconv new.srt ebustl --ebu-header-file:original.stl --overwrite

Time shift + frame-rate conversion

seconv subs.srt subrip --fps:24 --target-fps:25 --offset:500 --overwrite

Plain text export

seconv movie.srt plaintext --overwrite

Custom JSON output via template

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