subtitlemaker 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- subtitlemaker-0.1.0/.github/dependabot.yml +10 -0
- subtitlemaker-0.1.0/.github/workflows/ci.yml +82 -0
- subtitlemaker-0.1.0/.github/workflows/publish.yml +59 -0
- subtitlemaker-0.1.0/.gitignore +27 -0
- subtitlemaker-0.1.0/.python-version +1 -0
- subtitlemaker-0.1.0/CHANGELOG.md +33 -0
- subtitlemaker-0.1.0/CONTRIBUTING.md +84 -0
- subtitlemaker-0.1.0/LICENSE +21 -0
- subtitlemaker-0.1.0/PKG-INFO +207 -0
- subtitlemaker-0.1.0/README.md +175 -0
- subtitlemaker-0.1.0/pyproject.toml +70 -0
- subtitlemaker-0.1.0/src/subtitlemaker/__init__.py +3 -0
- subtitlemaker-0.1.0/src/subtitlemaker/__main__.py +5 -0
- subtitlemaker-0.1.0/src/subtitlemaker/_cuda.py +91 -0
- subtitlemaker-0.1.0/src/subtitlemaker/cli.py +771 -0
- subtitlemaker-0.1.0/src/subtitlemaker/config.py +158 -0
- subtitlemaker-0.1.0/src/subtitlemaker/formats/__init__.py +80 -0
- subtitlemaker-0.1.0/src/subtitlemaker/formats/_timestamp.py +18 -0
- subtitlemaker-0.1.0/src/subtitlemaker/formats/srt.py +27 -0
- subtitlemaker-0.1.0/src/subtitlemaker/formats/txt.py +10 -0
- subtitlemaker-0.1.0/src/subtitlemaker/formats/vtt.py +33 -0
- subtitlemaker-0.1.0/src/subtitlemaker/log.py +88 -0
- subtitlemaker-0.1.0/src/subtitlemaker/media.py +55 -0
- subtitlemaker-0.1.0/src/subtitlemaker/progress.py +97 -0
- subtitlemaker-0.1.0/src/subtitlemaker/segmenter.py +304 -0
- subtitlemaker-0.1.0/src/subtitlemaker/transcribe.py +253 -0
- subtitlemaker-0.1.0/tests/__init__.py +0 -0
- subtitlemaker-0.1.0/tests/conftest.py +83 -0
- subtitlemaker-0.1.0/tests/fixtures/expected-default.srt +60 -0
- subtitlemaker-0.1.0/tests/fixtures/expected-one-line.srt +84 -0
- subtitlemaker-0.1.0/tests/fixtures/jfk.flac +0 -0
- subtitlemaker-0.1.0/tests/fixtures/segments.json +472 -0
- subtitlemaker-0.1.0/tests/test_batch.py +212 -0
- subtitlemaker-0.1.0/tests/test_cli.py +50 -0
- subtitlemaker-0.1.0/tests/test_config.py +106 -0
- subtitlemaker-0.1.0/tests/test_formats.py +73 -0
- subtitlemaker-0.1.0/tests/test_integration.py +64 -0
- subtitlemaker-0.1.0/tests/test_output_formats.py +143 -0
- subtitlemaker-0.1.0/tests/test_pipeline.py +72 -0
- subtitlemaker-0.1.0/tests/test_segmenter.py +101 -0
- subtitlemaker-0.1.0/tests/test_snapshot.py +55 -0
- subtitlemaker-0.1.0/tests/test_text.py +76 -0
- subtitlemaker-0.1.0/tests/test_timing.py +137 -0
- subtitlemaker-0.1.0/tests/test_transcribe.py +100 -0
- subtitlemaker-0.1.0/tests/test_words.py +160 -0
- subtitlemaker-0.1.0/uv.lock +1128 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: Python ${{ matrix.python-version }}
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- run: uv sync --locked
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: uv run ruff check src tests
|
|
34
|
+
- name: Test
|
|
35
|
+
run: uv run pytest
|
|
36
|
+
|
|
37
|
+
# The lowest versions the package claims to support still have to work.
|
|
38
|
+
lowest-versions:
|
|
39
|
+
name: Lowest supported dependencies
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v7
|
|
43
|
+
with:
|
|
44
|
+
persist-credentials: false
|
|
45
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.10"
|
|
48
|
+
enable-cache: true
|
|
49
|
+
- run: uv run --resolution lowest-direct pytest
|
|
50
|
+
|
|
51
|
+
# Runs a real (tiny) Whisper model on the CPU.
|
|
52
|
+
integration:
|
|
53
|
+
name: Integration (tiny model)
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v7
|
|
57
|
+
with:
|
|
58
|
+
persist-credentials: false
|
|
59
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
60
|
+
with:
|
|
61
|
+
python-version: "3.12"
|
|
62
|
+
enable-cache: true
|
|
63
|
+
- uses: actions/cache@v6
|
|
64
|
+
with:
|
|
65
|
+
path: ~/.cache/huggingface
|
|
66
|
+
key: huggingface-whisper-tiny-v1
|
|
67
|
+
- run: uv sync --locked
|
|
68
|
+
- run: uv run pytest -m integration
|
|
69
|
+
|
|
70
|
+
build:
|
|
71
|
+
name: Build package
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v7
|
|
75
|
+
with:
|
|
76
|
+
persist-credentials: false
|
|
77
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
78
|
+
with:
|
|
79
|
+
python-version: "3.12"
|
|
80
|
+
- run: uv build
|
|
81
|
+
- name: Check the package metadata
|
|
82
|
+
run: uvx twine check --strict dist/*
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Pushing a tag like v0.1.0 builds the package and publishes it to PyPI.
|
|
4
|
+
# "Run workflow" on the Actions tab builds and checks it without publishing.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build and check
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v7
|
|
19
|
+
with:
|
|
20
|
+
persist-credentials: false
|
|
21
|
+
- uses: astral-sh/setup-uv@v10.3.3
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
- name: The tag must match the package version
|
|
25
|
+
if: github.ref_type == 'tag'
|
|
26
|
+
run: |
|
|
27
|
+
version=$(uv run --no-project python -c "import re; print(re.search(r'__version__ = \"(.+?)\"', open('src/subtitlemaker/__init__.py').read()).group(1))")
|
|
28
|
+
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
|
|
29
|
+
echo "::error::tag $GITHUB_REF_NAME does not match version $version in src/subtitlemaker/__init__.py"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
- run: uv sync --locked
|
|
33
|
+
- run: uv run ruff check src tests
|
|
34
|
+
- run: uv run pytest
|
|
35
|
+
- run: uv build
|
|
36
|
+
- name: Check the package metadata
|
|
37
|
+
run: uvx twine check --strict dist/*
|
|
38
|
+
- uses: actions/upload-artifact@v7
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
if-no-files-found: error
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
name: Publish to PyPI
|
|
46
|
+
needs: build
|
|
47
|
+
if: github.ref_type == 'tag'
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment:
|
|
50
|
+
name: pypi
|
|
51
|
+
url: https://pypi.org/p/subtitlemaker
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write # trusted publishing: PyPI trusts this workflow, no token needed
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v8
|
|
56
|
+
with:
|
|
57
|
+
name: dist
|
|
58
|
+
path: dist/
|
|
59
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.mypy_cache/
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
|
|
14
|
+
# Local test media and generated subtitles
|
|
15
|
+
*.srt
|
|
16
|
+
*.vtt
|
|
17
|
+
*.mp4
|
|
18
|
+
*.mkv
|
|
19
|
+
*.mp3
|
|
20
|
+
*.wav
|
|
21
|
+
*.m4a
|
|
22
|
+
!tests/fixtures/**
|
|
23
|
+
|
|
24
|
+
# Editors / OS
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
.DS_Store
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-07
|
|
8
|
+
|
|
9
|
+
First public release. It grew out of a single prototype script.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Subtitle output as SRT, WebVTT and plain text (`-f srt,vtt,txt`).
|
|
13
|
+
- Batch mode: several files and folders (`-r` to recurse), `--output-dir`, `--skip-existing`. The model is loaded once, and a file that fails does not stop the others.
|
|
14
|
+
- CPU support. `--device auto|cuda|cpu` falls back to the CPU when no usable GPU is found, with a warning.
|
|
15
|
+
- `--model` to choose the Whisper model (size, Hugging Face repo id or local path) and `--compute-type`. The default is `large-v3` on a GPU and `small` on the CPU.
|
|
16
|
+
- Word timestamps (`--word-timestamps`, on by default): subtitles start at the first word and end at the last, and long sentences are split at sentence ends and real word times.
|
|
17
|
+
- Subtitle style options (`--max-line-length`, `--max-lines`, `--max-cps`, `--min-duration`, `--max-duration`, `--min-gap`) and a TOML config file.
|
|
18
|
+
- `--quiet` and `--verbose`, `--version`, and a progress bar. Messages go to stderr.
|
|
19
|
+
- Optional `cuda` extra that installs the CUDA 12 libraries from PyPI.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- The default task is `transcribe` (subtitles in the spoken language). The prototype translated to English by default; use `-t translate` for that.
|
|
23
|
+
- Subtitles are no longer shifted later to make room for readability padding. Padding only extends a subtitle into free time.
|
|
24
|
+
- `--max-lines` accepts 1 or 2.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- A line could be one character longer than the maximum.
|
|
28
|
+
- The first subtitle was delayed by the minimum gap.
|
|
29
|
+
- The GPU libraries are loaded without re-launching the Python process, so `python -m subtitlemaker` works.
|
|
30
|
+
- A GPU without the CUDA 12 libraries no longer ends in a traceback in the middle of a run.
|
|
31
|
+
|
|
32
|
+
[Unreleased]: https://github.com/CodeByVibes/subtitlemaker/compare/v0.1.0...HEAD
|
|
33
|
+
[0.1.0]: https://github.com/CodeByVibes/subtitlemaker/releases/tag/v0.1.0
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for wanting to help. Bug reports, ideas and pull requests are all welcome.
|
|
4
|
+
|
|
5
|
+
## Reporting a problem
|
|
6
|
+
|
|
7
|
+
Please include:
|
|
8
|
+
|
|
9
|
+
- what you ran (the full command) and what you expected,
|
|
10
|
+
- the output, ideally with `-v` for debug messages,
|
|
11
|
+
- your OS, Python version and `subtitlemaker --version`,
|
|
12
|
+
- whether it was on the GPU or the CPU.
|
|
13
|
+
|
|
14
|
+
If a particular file triggers the problem and it is short and shareable, attach it. Otherwise a description of it (length, languages, container) helps.
|
|
15
|
+
|
|
16
|
+
## Setting up
|
|
17
|
+
|
|
18
|
+
You need Python 3.10 or newer and [uv](https://docs.astral.sh/uv/).
|
|
19
|
+
|
|
20
|
+
```console
|
|
21
|
+
git clone https://github.com/CodeByVibes/subtitlemaker.git
|
|
22
|
+
cd subtitlemaker
|
|
23
|
+
uv sync # add --extra cuda for NVIDIA GPU support
|
|
24
|
+
uv run subtitlemaker --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Checks
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
uv run ruff check src tests # lint
|
|
31
|
+
uv run pytest # unit, snapshot and pipeline tests: fast, no GPU or model needed
|
|
32
|
+
uv run pytest -m integration # runs the real "tiny" model on a short clip (downloads 73 MB once)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
CI runs the lint and the fast tests on Python 3.10 to 3.13, and the integration tests once. Please make sure these pass before opening a pull request.
|
|
36
|
+
|
|
37
|
+
The code style is plain and spacious. Run `ruff check`, but do not run `ruff format` over `src/`, it would rewrite the whole code base.
|
|
38
|
+
|
|
39
|
+
## How the code is organised
|
|
40
|
+
|
|
41
|
+
| Module | Responsibility |
|
|
42
|
+
| ------------------ | ----------------------------------------------------------------- |
|
|
43
|
+
| `cli.py` | Arguments, batch handling, the per-file steps. |
|
|
44
|
+
| `config.py` | `SubtitleStyle` and `ModelConfig`, config file loading, validation. |
|
|
45
|
+
| `transcribe.py` | Choosing and loading the model, GPU/CPU fallback, languages. |
|
|
46
|
+
| `_cuda.py` | Loading the pip-installed CUDA libraries. |
|
|
47
|
+
| `segmenter.py` | Whisper segments to subtitle cues. **Pure logic**: no printing, files or model. |
|
|
48
|
+
| `formats/` | Output writers (`srt`, `vtt`, `txt`) and the registry. |
|
|
49
|
+
| `media.py` | Reading durations, finding media files in folders. |
|
|
50
|
+
| `progress.py`, `log.py` | The progress bar and logging. |
|
|
51
|
+
|
|
52
|
+
Keep `segmenter.py` free of I/O. That is what makes the subtitle behaviour testable without a model.
|
|
53
|
+
|
|
54
|
+
### Adding an output format
|
|
55
|
+
|
|
56
|
+
1. Write `formats/yourformat.py` with a `write_yourformat(cues, path)` function. A cue is a dict with `start` and `end` (seconds) and `text` (lines separated by `\n`).
|
|
57
|
+
2. Register it in `formats/__init__.py`.
|
|
58
|
+
3. Add tests next to the ones in `tests/test_output_formats.py`. If the format has an external specification, check a generated file with an independent tool (we use `ffmpeg` for SRT and VTT).
|
|
59
|
+
|
|
60
|
+
### Changing subtitle behaviour
|
|
61
|
+
|
|
62
|
+
Timing and splitting changes show up in the snapshot tests. If a change is intended, regenerate the stored files, read the diff, and explain it in the pull request:
|
|
63
|
+
|
|
64
|
+
```console
|
|
65
|
+
UPDATE_SNAPSHOTS=1 uv run pytest tests/test_snapshot.py
|
|
66
|
+
git diff tests/fixtures
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Tests
|
|
70
|
+
|
|
71
|
+
- Test behaviour, not implementation. The existing tests build "segments" by hand (`tests/conftest.py`) and run them through the real code.
|
|
72
|
+
- `tests/conftest.py` has a fake Whisper model (`fake_model`) and a `run(...)` helper so the whole command line can be tested without a GPU.
|
|
73
|
+
- A test that has never failed is a test you don't know works. When you fix a bug, write the test first and watch it fail.
|
|
74
|
+
|
|
75
|
+
## Commits and pull requests
|
|
76
|
+
|
|
77
|
+
Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `build:`, `chore:`. Keep each commit to one logical change, and add a line to `CHANGELOG.md` under "Unreleased" for anything a user would notice.
|
|
78
|
+
|
|
79
|
+
## Releasing (maintainers)
|
|
80
|
+
|
|
81
|
+
1. Move the "Unreleased" entries in `CHANGELOG.md` under a new version heading with the date.
|
|
82
|
+
2. Set the version in `src/subtitlemaker/__init__.py` (it is the single source for the package version).
|
|
83
|
+
3. Commit, then tag: `git tag v0.2.0 && git push origin main v0.2.0`.
|
|
84
|
+
4. The `Publish` workflow builds the package, checks the tag matches the version, runs the tests and uploads to PyPI with trusted publishing. No tokens are stored anywhere.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Johan Isevind
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: subtitlemaker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate subtitles from audio and video files using faster-whisper.
|
|
5
|
+
Project-URL: Homepage, https://github.com/CodeByVibes/subtitlemaker
|
|
6
|
+
Project-URL: Issues, https://github.com/CodeByVibes/subtitlemaker/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/CodeByVibes/subtitlemaker/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Johan Isevind <codebyvibes@isevind.se>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: faster-whisper,speech-to-text,srt,subtitles,transcription,vtt,whisper
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
22
|
+
Classifier: Topic :: Multimedia :: Video
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: av<17,>=13
|
|
25
|
+
Requires-Dist: faster-whisper>=1.2.1
|
|
26
|
+
Requires-Dist: rich>=13
|
|
27
|
+
Requires-Dist: tomli>=2; python_version < '3.11'
|
|
28
|
+
Provides-Extra: cuda
|
|
29
|
+
Requires-Dist: nvidia-cublas-cu12; extra == 'cuda'
|
|
30
|
+
Requires-Dist: nvidia-cudnn-cu12==9.*; extra == 'cuda'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# SubtitleMaker
|
|
34
|
+
|
|
35
|
+
[](https://github.com/CodeByVibes/subtitlemaker/actions/workflows/ci.yml)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
Generate subtitles from audio and video files with [faster-whisper](https://github.com/SYSTRAN/faster-whisper).
|
|
39
|
+
It runs locally, on a GPU if you have one and on the CPU if you don't.
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
$ subtitlemaker interview.mkv
|
|
43
|
+
$ ls
|
|
44
|
+
interview.mkv interview.srt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- **SRT, WebVTT and plain text** output, any combination in one run.
|
|
48
|
+
- **Subtitles that follow the speech.** Cues start at the first word and end at the last, using word timestamps, and long sentences are split at sentence ends where possible.
|
|
49
|
+
- **Readable by default:** at most two lines of 42 characters, 20 characters per second, 1 to 7 seconds on screen. All of it is configurable.
|
|
50
|
+
- **Batch mode:** pass many files or whole folders; the model is loaded once and one bad file doesn't stop the rest.
|
|
51
|
+
- **GPU or CPU.** `--device auto` uses an NVIDIA GPU when there is one and falls back to the CPU when there isn't.
|
|
52
|
+
- **Mixed-language audio** (`-l en,sv`), and optional translation to English (`-t translate`).
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
You need Python 3.10 to 3.13. FFmpeg is **not** required.
|
|
57
|
+
|
|
58
|
+
SubtitleMaker is a command line tool, so [pipx](https://pipx.pypa.io/) or [uv](https://docs.astral.sh/uv/) is the easiest way to install it:
|
|
59
|
+
|
|
60
|
+
```console
|
|
61
|
+
# CPU only
|
|
62
|
+
pipx install subtitlemaker
|
|
63
|
+
|
|
64
|
+
# NVIDIA GPU (also installs the CUDA 12 libraries, see below)
|
|
65
|
+
pipx install "subtitlemaker[cuda]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
With uv: `uv tool install subtitlemaker` or `uv tool install "subtitlemaker[cuda]"`. Plain `pip install` works too, ideally inside a virtual environment.
|
|
69
|
+
|
|
70
|
+
> **Platforms:** developed and tested on Linux. It should work on macOS and Windows on the CPU, but that has not been tested yet. GPU support is only set up for Linux.
|
|
71
|
+
|
|
72
|
+
### The first run downloads a model
|
|
73
|
+
|
|
74
|
+
Whisper models are downloaded from Hugging Face the first time you use them and cached afterwards (`~/.cache/huggingface`, or wherever `HF_HOME` points). Sizes of the model files:
|
|
75
|
+
|
|
76
|
+
| `--model` | Download | Notes |
|
|
77
|
+
| ------------ | -------- | ---------------------------------------------- |
|
|
78
|
+
| `tiny` | 73 MB | Fast, rough. Good for trying things out. |
|
|
79
|
+
| `small` | 462 MB | **Default on CPU.** |
|
|
80
|
+
| `turbo` | 1.6 GB | Close to `large-v3` quality, much faster. |
|
|
81
|
+
| `large-v3` | 2.9 GB | **Default on GPU.** Most accurate. |
|
|
82
|
+
|
|
83
|
+
`base`, `medium`, the `.en` variants, `distil-*` models, any Hugging Face repo id such as `user/model`, and local paths to converted models also work.
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
```console
|
|
88
|
+
# Subtitles next to the file, in the spoken language
|
|
89
|
+
subtitlemaker talk.mp4
|
|
90
|
+
|
|
91
|
+
# Several formats at once: talk.srt, talk.vtt and talk.txt
|
|
92
|
+
subtitlemaker talk.mp4 -f srt,vtt,txt
|
|
93
|
+
|
|
94
|
+
# English subtitles for foreign-language audio
|
|
95
|
+
subtitlemaker film.mkv -t translate
|
|
96
|
+
|
|
97
|
+
# Tell it the language instead of detecting it
|
|
98
|
+
subtitlemaker film.mkv -l sv
|
|
99
|
+
|
|
100
|
+
# A whole folder (add -r for sub folders), skipping files that are already done
|
|
101
|
+
subtitlemaker ~/Videos --skip-existing -r
|
|
102
|
+
|
|
103
|
+
# Put all the results in one place
|
|
104
|
+
subtitlemaker ~/Videos/*.mp4 -d ~/subtitles -f srt,vtt
|
|
105
|
+
|
|
106
|
+
# Smaller and faster, CPU only
|
|
107
|
+
subtitlemaker talk.mp4 --device cpu --model tiny
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Run `subtitlemaker --help` for every option.
|
|
111
|
+
|
|
112
|
+
### Output
|
|
113
|
+
|
|
114
|
+
| Format | Contents |
|
|
115
|
+
| ------ | -------------------------------------------------------------------------------- |
|
|
116
|
+
| `srt` | SubRip, the most widely supported. This is the default. |
|
|
117
|
+
| `vtt` | WebVTT, for web players. Markup characters in the text are escaped. |
|
|
118
|
+
| `txt` | The text only, one subtitle per line, no timing. |
|
|
119
|
+
|
|
120
|
+
Files are written next to each input and named after it (`talk.mp4` becomes `talk.srt`). Existing files are overwritten unless you pass `--skip-existing`.
|
|
121
|
+
|
|
122
|
+
- `-o FILE` names the output for a single input. With several formats it is a base name and the extension is replaced for each one.
|
|
123
|
+
- `-d DIR` writes all outputs into one folder (created if needed). Two inputs that would write the same file are refused before anything runs.
|
|
124
|
+
|
|
125
|
+
The exit code is `0` when everything succeeded, `1` if any file failed (the others are still processed), and `2` for invalid options.
|
|
126
|
+
|
|
127
|
+
### Languages
|
|
128
|
+
|
|
129
|
+
By default Whisper detects the language from the start of each file. If it guesses wrong, or the start is music, name the language with `-l`.
|
|
130
|
+
|
|
131
|
+
For audio that switches language, list the languages (`-l en,sv`) or add `-m`. The language is then re-detected for every segment. Be aware that `-t translate` turns every segment into English.
|
|
132
|
+
|
|
133
|
+
### Subtitle style
|
|
134
|
+
|
|
135
|
+
| Option | Default | Meaning |
|
|
136
|
+
| ------------------- | ------- | ---------------------------------------------------- |
|
|
137
|
+
| `--max-line-length` | 42 | Maximum characters per line. |
|
|
138
|
+
| `--max-lines` | 2 | Lines per subtitle, 1 or 2. |
|
|
139
|
+
| `--max-cps` | 20 | Reading speed: characters per second. |
|
|
140
|
+
| `--min-duration` | 1.0 | Shortest time a subtitle stays on screen (seconds). |
|
|
141
|
+
| `--max-duration` | 7.0 | Longest time a subtitle stays on screen (seconds). |
|
|
142
|
+
| `--min-gap` | 0.05 | Smallest gap between two subtitles (seconds). |
|
|
143
|
+
|
|
144
|
+
Timing follows the speech. A subtitle appears when its first word is spoken. If it would be too short or too quick to read, it is held longer, but only into free time: it never delays or overlaps the next subtitle. If two lines are spoken too close together, the first one is allowed to be shorter than `--min-duration`.
|
|
145
|
+
|
|
146
|
+
### Config file
|
|
147
|
+
|
|
148
|
+
Options you always use can go in `~/.config/subtitlemaker/config.toml` (or `$XDG_CONFIG_HOME/subtitlemaker/config.toml`), or in a file given with `--config`. Command line options override the file, and the file overrides the defaults.
|
|
149
|
+
|
|
150
|
+
```toml
|
|
151
|
+
[style]
|
|
152
|
+
max_line_length = 38
|
|
153
|
+
max_lines = 2
|
|
154
|
+
max_cps = 17
|
|
155
|
+
min_duration = 1.0
|
|
156
|
+
max_duration = 6.0
|
|
157
|
+
min_gap = 0.05
|
|
158
|
+
|
|
159
|
+
[model]
|
|
160
|
+
name = "turbo" # auto, a size, a Hugging Face repo id or a path
|
|
161
|
+
device = "auto" # auto, cuda or cpu
|
|
162
|
+
compute_type = "auto" # auto, float16, int8_float16, int8, float32, ...
|
|
163
|
+
word_timestamps = true
|
|
164
|
+
vad_filter = true
|
|
165
|
+
condition_on_previous_text = true
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Unknown keys and wrong types are reported with the file name, so typos don't pass silently.
|
|
169
|
+
|
|
170
|
+
## GPU and CPU
|
|
171
|
+
|
|
172
|
+
`--device auto` (the default) picks the GPU when CTranslate2 can see one and warns and uses the CPU otherwise.
|
|
173
|
+
|
|
174
|
+
| Where | Default model | Precision | Notes |
|
|
175
|
+
| ----- | ------------- | --------- | --------------------------------------------------- |
|
|
176
|
+
| GPU | `large-v3` | `float16` | Needs an NVIDIA GPU, a current driver and CUDA 12 libraries. |
|
|
177
|
+
| CPU | `small` | `int8` | Works everywhere, much slower than a GPU. |
|
|
178
|
+
|
|
179
|
+
For a rough idea, on one machine (RTX 4070 Laptop GPU, 24 CPU cores) a two minute clip took about 7 seconds with `large-v3` on the GPU and about 9 seconds with `small` on the CPU. Your results will differ.
|
|
180
|
+
|
|
181
|
+
### CUDA libraries
|
|
182
|
+
|
|
183
|
+
CTranslate2 needs the CUDA 12 versions of cuBLAS and cuDNN 9. Install SubtitleMaker with the `cuda` extra and they come from PyPI (`nvidia-cublas-cu12`, `nvidia-cudnn-cu12`) and are loaded automatically, so a different system-wide CUDA version doesn't matter. These packages are licensed by NVIDIA, not under this project's license, and they are large.
|
|
184
|
+
|
|
185
|
+
If the GPU can't be used, `--device auto` falls back to the CPU and tells you why. With an explicit `--device cuda` it stops with an error that says what is missing.
|
|
186
|
+
|
|
187
|
+
## How it works
|
|
188
|
+
|
|
189
|
+
1. faster-whisper transcribes the audio with word timestamps (voice activity detection is on, so stretches without speech are skipped).
|
|
190
|
+
2. Words are grouped into subtitles that fit the line limits, preferably breaking at the end of a sentence.
|
|
191
|
+
3. Timing is adjusted for readability without moving speech, as described above.
|
|
192
|
+
4. The result is written in each requested format.
|
|
193
|
+
|
|
194
|
+
## Limitations
|
|
195
|
+
|
|
196
|
+
- At most two lines per subtitle.
|
|
197
|
+
- Lines are wrapped at spaces, so languages written without spaces (Chinese, Japanese, Thai) are not wrapped well. This is untested.
|
|
198
|
+
- Speakers are not identified, and no sound descriptions like `[music]` are added.
|
|
199
|
+
- A long pause in the middle of a sentence does not split the subtitle yet.
|
|
200
|
+
|
|
201
|
+
## Contributing
|
|
202
|
+
|
|
203
|
+
Bug reports and pull requests are welcome, see [CONTRIBUTING.md](CONTRIBUTING.md). Changes are listed in [CHANGELOG.md](CHANGELOG.md).
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
[MIT](LICENSE). SubtitleMaker builds on [faster-whisper](https://github.com/SYSTRAN/faster-whisper) (MIT), [CTranslate2](https://github.com/OpenNMT/CTranslate2) (MIT) and OpenAI's [Whisper](https://github.com/openai/whisper) models (MIT).
|