Metadata-Version: 2.5
Name: subtitle-gen
Version: 0.1.0
Summary: Generate subtitles from audio/video with Qwen3 ASR, forced alignment, optional LLM segmentation, and translation.
Project-URL: Homepage, https://cnb.cool/azumaseren/subtitle-gen
Project-URL: Issues, https://cnb.cool/azumaseren/subtitle-gen/-/issues
License-Expression: GPL-3.0-only
License-File: LICENSE
Keywords: asr,forced-alignment,lrc,lyrics,speech-recognition,srt,subtitles,translation,vtt
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.11
Requires-Dist: accelerate>=0.33
Requires-Dist: blingfire>=0.1.8
Requires-Dist: nagisa>=0.2.11
Requires-Dist: rich>=15.0.0
Requires-Dist: silero-vad>=6.0
Requires-Dist: soundfile>=0.12
Requires-Dist: soynlp>=0.0.493
Requires-Dist: torch>=2.5
Requires-Dist: transformers<6,>=5.13
Requires-Dist: triton-windows>=3.3.0; sys_platform == 'win32'
Description-Content-Type: text/markdown

# subtitle-gen

Generate SRT, VTT, LRC, or JSON subtitles from audio/video with Qwen3 ASR, forced
alignment, local or LLM-assisted segmentation, proofreading, and optional translation.
Also supports proofreading and translating existing SRT files directly, and adding
timestamps to untimed lyrics/subtitle text by aligning it to audio.

Usable both as a command line tool and as a Python library.

## Requirements

- Python 3.11+
- `ffmpeg` / `ffprobe` on `PATH`
- A GPU is recommended, but CPU-only runs work (slower)

## Install

```console
pip install subtitle-gen
```

This pulls the default `torch` build from PyPI: CUDA-enabled on Linux (several
GB of NVIDIA libraries) and CPU-only on macOS and Windows. To pick a different
build — a smaller CPU-only install, or CUDA on Windows — install torch first and
let the package reuse it:

```console
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install subtitle-gen
```

Or run it without touching your environment:

```console
uv tool install subtitle-gen
```

`uv` projects resolve torch from the CUDA 12.8 index on Linux and Windows, as
[pyproject.toml](pyproject.toml) configures under `[tool.uv.sources]`.

## Configuration

Everything works with built-in defaults; a config file only overrides them.
`subtitle-gen` loads `./config.toml` when present, or the path given via
`--config`. [config.example.toml](config.example.toml) lists every option; a
minimal one looks like this:

```toml
[model]
asr_model = "Qwen/Qwen3-ASR-1.7B-hf"
low_vram_asr_model = "Qwen/Qwen3-ASR-0.6B-hf"
aligner_model = "Qwen/Qwen3-ForcedAligner-0.6B-hf"
device_map = "auto"
language = "Chinese"          # optional hint; omit to auto-detect

[segment]
mode = "hybrid"               # none | blingfire | local | hybrid | llm

[llm]                         # proofreading + translation
base_url = "https://api.deepseek.com/v1"
model = "deepseek-v4-pro"
api_key = ""                  # or set OPENAI_API_KEY

[llm.segmentation]            # used by mode = "llm" / "hybrid"
model = "deepseek-v4-flash"
```

LLM features need an OpenAI-compatible endpoint; `SUBTITLE_GEN_LLM_MODEL`
overrides the configured model.

## CLI usage

### Pipeline (audio/video → subtitles)

```console
subtitle-gen input.mp4 --out output.srt
subtitle-gen input.mp4 --out-dir outputs --format srt --format vtt
subtitle-gen input.mp4 --segment-mode local --out-dir outputs
subtitle-gen input.mp4 --segment-mode hybrid --out-dir outputs
subtitle-gen input.mp4 --translate zh --out-dir outputs
```

Pipeline options:

- `--segment-mode none|blingfire|local|hybrid|llm`
- `--language LANG` to hint the source language
- `--translate LANG` — also runs proofread before translation, outputs original/proofread/translation
- `--bilingual` to also write a bilingual output when translating
- `--overwrite-cache` to regenerate cached artifacts
- `--no-cache` to run without persistent cache

### Align untimed text (add timestamps to existing subtitles/lyrics)

```console
subtitle-gen align song.flac lyrics.lrc --no-vad
subtitle-gen align song.flac lyrics.txt --out lyrics.timed.lrc
subtitle-gen align song.flac lyrics.txt --format lrc --format srt
subtitle-gen align song.flac lyrics.txt --language Japanese
```

`align` runs ASR + forced alignment over the audio, then maps each input line
onto the resulting timed words with a global sequence alignment. The text file
may be plain text (one line per cue) or an LRC file; descriptive LRC metadata
tags (`[ti:...]`, `[ar:...]`, ...) are preserved in the output, while stale
timing tags (`[offset:...]`, `[length:...]`) are dropped because timestamps are
regenerated. By default it writes `<text>.timed.lrc`; use `--out` (single file,
format inferred from its extension) or `--format lrc|srt|vtt|json` (repeatable)
to control output. `--out` and `--format` cannot be combined.

VAD is used by default (good for speech). Pass `--no-vad` for songs/music, where
Silero VAD misses sung vocals — this aligns the whole audio with slightly
overlapping fixed-size windows so boundary words are not cut off.

It accepts the same model/cache flags as the pipeline (`--asr-model`,
`--low-vram`, `--language`, `--device-map`, `--cache`/`--no-cache`,
`--cache-dir`, `--overwrite-cache`, `--[no-]compile-aligner`,
`--[no-]compile-asr`), plus `--no-refine` to skip the per-line forced-alignment
refinement pass. With persistent cache enabled, both the rough pass and the
per-line refinement alignments are cached, so unchanged inputs are reused on
subsequent runs.

### Subcommands (work on existing SRT)

```console
subtitle-gen proofread subs.srt
subtitle-gen proofread subs.srt --out corrected.srt
subtitle-gen translate subs.srt --target "Chinese"
subtitle-gen translate subs.srt --target zh --bilingual
```

Both subcommands accept `--llm-model`, `--llm-concurrency`, `--batch-size` overrides.

LLM segmentation uses a separate `[llm.segmentation]` config; translation and
proofreading use `[llm]`. Set `api_key` (or `OPENAI_API_KEY`) and model for each.

## Library usage

```python
from pathlib import Path

from subtitle_gen import PipelineOptions, SubtitlePipeline, load_config

config = load_config()  # ./config.toml when present, otherwise defaults
result = SubtitlePipeline(config).run(
    PipelineOptions(input_path=Path("input.mp4"), translate="zh")
)
for item in result.subtitles:
    print(item.start, item.end, item.text, item.translation)
```

Other entry points exported from `subtitle_gen`:

- `align_lines_to_audio(config, audio_path, lines, ...)` — timestamp untimed text
- `SubtitleProofreader(llm, llm_config).proofread(items)`
- `SubtitleTranslator(llm, llm_config).translate(items, target_language)`
- `parse_srt` / `parse_lrc` / `write_subtitles` / `write_lrc` — standalone file I/O
- `OpenAICompatibleLLM` — the OpenAI-compatible client used by the above

## Development

```console
uv sync
uv run pytest
uv run ruff check .
```

## License

GPL-3.0-only. See [LICENSE](LICENSE).
