ymakhloufi / subtitle-toolbox
A PHP library and command line tool that reads, edits and writes subtitles, transcripts and chapter lists in more than 30 formats.
Requires
- php: ^8.2
- ext-dom: *
- ext-iconv: *
Requires (Dev)
- phpunit/phpunit: ^11.5
- yama6a/php-glyph-ocr: ^0.3
Suggests
- ext-curl: *, for DeepLEngine, GoogleTranslateEngine and the translate command
- ext-mbstring: *, for full Unicode upper and lower case. Without it, only A to Z change case
- ext-zlib: *, to decode PNG images, which PgsFormatter needs, to compress PNG images and to read zlib-compressed MKV tracks
- yama6a/php-glyph-ocr: ^0.3, for GlyphOcrEngine, the pure PHP OCR of PGS and VobSub image cues where Tesseract is not installed
Provides
None
Conflicts
- yama6a/php-glyph-ocr: <0.3 || >=0.4
Replaces
None
- dev-master
- 2.x-dev
- 2.14.0
- 2.13.0
- 2.12.0
- 2.11.0
- 2.10.0
- 2.9.0
- 2.8.0
- 2.7.4
- 2.7.3
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.1
- 2.6.0
- 2.5.0
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.3
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.0
- 2.1.0
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.70.6
- 1.70.5
- 1.70.4
- 1.70.3
- 1.70.2
- 1.70.1
- 1.70.0
- 1.69.0
- 1.68.0
- 1.67.1
- 1.67.0
- 1.66.0
- 1.65.1
- 1.65.0
- 1.64.0
- 1.63.0
- 1.62.1
- 1.62.0
- 1.61.0
- 1.60.0
- 1.59.0
- 1.58.0
- 1.57.0
- 1.56.0
- 1.55.0
- 1.54.1
- 1.54.0
- 1.53.0
- 1.52.0
- 1.51.0
- 1.50.0
- 1.49.0
- 1.48.0
- 1.47.0
- 1.46.0
- 1.45.0
- 1.44.0
- 1.43.0
- 1.42.0
- 1.41.0
- 1.40.0
- 1.39.0
- 1.38.0
- 1.37.0
- 1.36.0
- 1.35.0
- 1.34.0
- 1.33.0
- 1.32.0
- 1.31.0
- 1.30.0
- 1.29.1
- 1.29.0
- 1.28.0
- 1.27.1
- 1.27.0
- 1.26.0
- 1.25.0
- 1.24.0
- 1.23.0
- 1.22.0
- 1.21.0
- 1.20.0
- 1.19.0
- 1.18.0
- 1.17.0
- 1.16.0
- 1.15.0
- 1.14.0
- 1.13.1
- 1.13.0
- 1.12.0
- 1.11.0
- 1.10.8
- 1.10.7
- 1.10.6
- 1.10.5
- 1.10.4
- 1.10.3
- 1.10.2
- 1.10.1
- 1.10.0
- 1.9.1
- 1.9.0
- 1.8.0
- 1.7.0
- 1.6.0
- 1.5.0
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.0
- 1.1.0
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.1
- 1.0.0
- 0.1.9
- 0.1.8
- 0.1.7
- 0.1.6
- 0.1.5
- 0.1.4
- 0.1.3
- 0.1.2
- 0.1.1
- v0.1
- dev-docs-help-prose
- dev-parser-shared-setup
- dev-docs-nine-fixes
- dev-scc-hint-split-long
- dev-nullable-options-params
- dev-cli-side-files-after-checks
- dev-cli-help-80-columns
- dev-cli-argument-helpers
- dev-fix-empty-srt-sbv-read
- dev-fix-265-five-items
- dev-formatter-tag-split
- dev-cli-format-matrix
- dev-fix-mkv-duration-rounding
- dev-cli-side-file-errors
- dev-timecode-helper
- dev-microdvd-rate-and-enums
- dev-split-test-helpers
- dev-fix-wrong-docblocks
- dev-cli-options-copy
- dev-style-runs-helper
- dev-fix-flaky-test
- dev-cli-output-path-checks
- dev-options-finite-checks
- dev-share-markup-patterns
- dev-cli-warnings-to-stderr
- dev-ocr-language-enum
- dev-text-encoding-enum
- dev-translate-cli
- dev-fix-hls-segment-order
- dev-fix-cli-mkv-memory
- dev-fix-comment-after-last-cue
- dev-fix-ttml-duplicate-ids
- dev-ci-drop-2x
- dev-v2-281-cli-compat-blockers
- dev-v2-273-upgrade-guide
- dev-v2-270-readme
- dev-v2-267-cli-io
- dev-v2-r4-cli
- dev-v2-r4-lib
- dev-translate-2.1
- dev-markup-escape-text
- dev-ttml-format
- dev-ass-format
- dev-sami-format
- dev-fix-srt-timestamp-without-millis
- dev-fix-srt-text-escaping
- dev-fix-microdvd-invalid-utf8
- dev-fix-lrc-text-escaping
- dev-fix-vtt-class-spans
- dev-fix-sbv-text-escaping
- dev-fix-mpsub-text-escaping
- dev-sbv-real-files
- dev-pin-fixture-sources
- dev-require-ext-dom
- dev-readme-current-state
- dev-normalize-cr-cr-lf
- dev-vtt-feature-complete
- dev-lrc-feature-complete
- dev-fix-readme-format-table
- dev-srt-feature-complete
- dev-mpsub-feature-complete
- dev-microdvd-format
- dev-export-ignore-dev-files
- dev-drop-ignore-metadata-test
- dev-core-markup-alignment-format-data
- dev-core-metadata-comments-identifiers
- dev-sbv-format
- dev-retime-subtitles
- dev-modernize-php-82
- dev-fix-mpsub-repo-link
- dev-fix-cue-numbering-after-removal
- dev-fix-unknown-parser-class
- dev-fix-single-line-block-warning
- dev-fix-multiple-empty-lines
- dev-fix-lrc-centisecond-rounding
- dev-fix-lrc-end-time
- dev-fix-cue-sort-subsecond
- dev-fix-exception-message
This package is auto-updated.
Last update: 2026-10-06 17:06:34 UTC
README
A PHP library and command line tool that reads, edits and writes subtitles, transcripts and chapter lists in more than 30 formats.
Upgrading from 1.x? See the upgrade guide.
Install
composer require ymakhloufi/subtitle-toolbox # Optional, for OCR of PGS and VobSub image subtitles: composer require yama6a/php-glyph-ocr:^0.3 # php-glyph-ocr, the pure PHP OCR engine apt install tesseract-ocr # or Tesseract, for more than 100 languages, on Debian and Ubuntu
The core package needs neither OCR engine. See ocr.md for the install commands of other systems and languages.
The library needs PHP 8.2 or later with ext-dom and ext-iconv. Optional: ext-mbstring for Unicode upper and lower case, ext-zlib for PGS output and compressed MKV tracks and ext-curl for the DeepL and Google translation engines.
The command line tool also comes as a PHAR file and as two container images. The -tesseract image includes Tesseract for OCR.
curl -fsSLO https://github.com/yama6a/subtitle-toolbox/releases/latest/download/subtitle-toolbox.phar php subtitle-toolbox.phar formats docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/yama6a/subtitle-toolbox convert movie.srt --to vtt -o movie.vtt docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" ghcr.io/yama6a/subtitle-toolbox:tesseract convert movie.sup --to srt -o movie.srt --ocr
Supported formats
| Format | Case | Name | Extensions | Read | Write | Notes |
|---|---|---|---|---|---|---|
| ASS, SSA | Ass |
ass |
.ass, .ssa |
yes | yes | |
| CSV, TSV | Csv, Tsv |
csv, tsv |
.csv, .tsv |
yes | yes | not detected from the content |
| EBU STL | EbuStl |
stl |
.stl |
yes | yes | binary, 25 or 30 fps |
| iTunes Timed Text | Itt |
itt |
.itt |
yes | yes | needs a frame rate to write |
| LRC | Lyrics |
lrc |
.lrc |
yes | yes | with enhanced LRC word times |
| MicroDVD | MicroDvd |
microdvd |
.sub |
yes | yes | needs the frame rate of the video |
| MPL2 | Mpl2 |
mpl2 |
.txt |
yes | yes | |
| MPSub | MpSub |
mpsub |
.mpsub |
yes | yes | |
| SAMI | Sami |
sami |
.smi, .sami |
yes | yes | one language class per parse |
| SBV | Sbv |
sbv |
.sbv |
yes | yes | |
| SCC | Scc |
scc |
.scc |
yes | yes | CEA-608 closed captions |
| SubRip | SubRip |
srt |
.srt |
yes | yes | |
| SubViewer 1 and 2 | SubViewer |
subviewer |
.sub |
yes | yes | |
| TMPlayer | TmPlayer |
tmplayer |
.txt |
yes | yes | |
| TTML, IMSC, DFXP | Ttml |
ttml |
.ttml, .dfxp, .xml |
yes | yes | |
| WebVTT | WebVtt |
vtt |
.vtt |
yes | yes | also chapters |
| PGS | Pgs |
pgs |
.sup |
yes | yes | Blu-ray bitmaps as image cues |
| VobSub | VobSub |
vobsub |
.idx with .sub |
yes | no | DVD bitmaps as image cues |
| JSON of this library | Json |
json |
.json |
yes | yes | |
| Plain text | PlainText |
txt |
.txt |
no | yes | transcript |
| Whisper JSON | Whisper |
whisper |
.json |
yes | no | OpenAI API, openai-whisper, faster-whisper, WhisperX, whisper.cpp |
| Cloud speech-to-text JSON | AwsTranscribe, Deepgram, AssemblyAi, GoogleSpeech |
aws-transcribe, deepgram, assemblyai, google-speech |
.json |
yes | no | not detected from the content |
| YouTube timed text | YouTubeTimedText |
youtube |
.json3, .srv3, .srv1 |
yes | no | json3, srv1, srv2, srv3 and transcript XML |
| Podcasting 2.0 transcript JSON | PodcastTranscript |
podcast-transcript |
.json |
yes | yes | |
| HTML transcript | HtmlTranscript |
html |
.html, .htm |
yes | yes | the Podcasting 2.0 HTML format |
| YouTube chapters | YouTubeChapters |
youtube-chapters |
.txt |
yes | yes | not detected from the content |
| Podcasting 2.0 chapters | PodcastChapters |
podcast-chapters |
.json |
yes | yes | not detected from the content |
| FFmpeg metadata chapters | FfMetadataChapters |
ffmeta-chapters |
.ffmeta |
yes | yes | not detected from the content |
| OGM chapters | OgmChapters |
ogm-chapters |
.txt |
yes | yes | not detected from the content |
| MKV and WebM tracks | .mkv, .webm |
yes | no | text, ASS, SSA, WebVTT and PGS tracks |
Case is the case of the enum Format, for example Format::SubRip. Name is the format name for --from and --to.
Command line tool
Composer installs vendor/bin/subtitle-toolbox. Optional parts are in brackets.
subtitle-toolbox convert movie.srt --to vtt [--timing-fix-overlaps] [-o movie.vtt] subtitle-toolbox convert movie.mkv --to srt --track 3 [-o movie.srt] subtitle-toolbox convert movie.sup --to srt --ocr [--ocr-language deu] [-o movie.srt] subtitle-toolbox retime movie.sub --input-fps 25 --from-fps 25 --to-fps 23.976 [-o movie.fixed.sub] subtitle-toolbox info movie.srt [--json] subtitle-toolbox validate movie.srt --preset netflix-en [--video-fps 23.976] subtitle-toolbox sync movie.de.srt --reference movie.en.srt [-o movie.de.synced.srt] subtitle-toolbox diff movie.v1.srt movie.v2.srt [--text-only] subtitle-toolbox translate movie.de.srt --engine deepl --target-language en-US [-o movie.en.srt] subtitle-toolbox dual --primary movie.en.srt --secondary movie.de.srt --to ass [--mode stack] [-o movie.en-de.ass] subtitle-toolbox hls movie.vtt --output-dir hls/ [--segment 6] subtitle-toolbox formats
- Without
-o, the output goes to standard output. - Several inputs need
--output-dir, for exampleretime season1/ --shift 2 --output-dir fixed/. - No command overwrites a file.
- When detection fails, pass
--from.
subtitle-toolbox convert --help lists the option groups of convert. See cli.md for all commands and options.
Library
Load and write
use SubtitleToolbox\Format; use SubtitleToolbox\LineEnding; use SubtitleToolbox\Subtitle; use SubtitleToolbox\WriteOptions; $subtitle = Subtitle::load('movie.srt', Format::SubRip); $subtitle = Subtitle::loadAutoDetectFormat('movie.srt'); $subtitle->getFormat(); // Format::SubRip $subtitle->save('movie.vtt'); // the format comes from the extension $vtt = $subtitle->toString(Format::WebVtt, new WriteOptions(lineEnding: LineEnding::Crlf, stripTags: true));
Read options
use SubtitleToolbox\Format; use SubtitleToolbox\Parsers\Options\MicroDvdReadOptions; use SubtitleToolbox\ReadOptions; use SubtitleToolbox\Subtitle; $latin1 = Subtitle::load('latin1.srt', Format::SubRip, new ReadOptions(encoding: 'Windows-1252')); $microDvd = Subtitle::load('movie.sub', Format::MicroDvd, new ReadOptions(format: new MicroDvdReadOptions(frameRate: 23.976)));
See read-options.md for the options of each format.
Edit
use SubtitleToolbox\CaseMode; use SubtitleToolbox\Format; use SubtitleToolbox\HearingImpaired\HearingImpairedOptions; use SubtitleToolbox\HearingImpaired\HearingImpairedRemover; use SubtitleToolbox\Subtitle; $subtitle = Subtitle::load('movie.srt', Format::SubRip); $subtitle->shift(-2.5) // all cues 2.5 s earlier ->convertFrameRate(25, 23.976) ->fixOverlaps(0.083) // a gap of at least 0.083 s between cues ->wrapLines(42) // at most 42 characters per line, 2 lines ->changeCase(CaseMode::Sentence); $firstMinute = $subtitle->withSlice(0, 60); // a new Subtitle, $subtitle stays as it is $report = HearingImpairedRemover::apply($subtitle, new HearingImpairedOptions(parentheses: false)); echo "$report->removedLines lines removed\n";
A service such as HearingImpairedRemover changes the subtitle in place and returns a report.
Validate and count
use SubtitleToolbox\Format; use SubtitleToolbox\Subtitle; use SubtitleToolbox\SubtitleStatistics; use SubtitleToolbox\Validation\ValidationRules; $subtitle = Subtitle::load('movie.srt', Format::SubRip); foreach ($subtitle->validate(ValidationRules::netflixEnglish(23.976)) as $violation) { echo "cue index $violation->cueIndex: {$violation->rule->value} is $violation->value\n"; } $stats = SubtitleStatistics::of($subtitle); echo "$stats->cueCount cues, $stats->wordCount words\n";
MKV and WebM tracks
use SubtitleToolbox\Subtitle; foreach (Subtitle::tracks('movie.mkv') as $track) { echo "$track->number: $track->codecId, $track->language, $track->name\n"; // 3: S_TEXT/UTF8, de, Deutsch (Forced) } $subtitle = Subtitle::loadTrack('movie.mkv', 3);
OCR
use SubtitleToolbox\Format; use SubtitleToolbox\Ocr\OcrEngineChooser; use SubtitleToolbox\Ocr\OcrEngineName; use SubtitleToolbox\Subtitle; $subtitle = Subtitle::load('movie.sup', Format::Pgs); $subtitle->recognizeText(OcrEngineChooser::create()); // Tesseract if installed, otherwise php-glyph-ocr $subtitle->recognizeText(OcrEngineChooser::create(OcrEngineName::Glyph)); // always php-glyph-ocr $subtitle->save('movie.srt');
create() takes the Tesseract language as its second argument, for example 'deu+eng'. For engine settings, pass TesseractOcrOptions to new TesseractOcrEngine() or GlyphOcrOptions to new GlyphOcrEngine().
Translate
use SubtitleToolbox\Format; use SubtitleToolbox\Subtitle; use SubtitleToolbox\Translation\DeepLEngine; use SubtitleToolbox\Translation\DeepLOptions; use SubtitleToolbox\Translation\TranslationRunner; $subtitle = Subtitle::load('movie.de.srt', Format::SubRip); (new TranslationRunner(new DeepLEngine(new DeepLOptions(apiKey: $apiKey))))->translate($subtitle, 'de', 'en-US'); $subtitle->save('movie.en.srt');
GoogleTranslateEngine works the same way. See translation.md.
Sync to a reference
use SubtitleToolbox\Format; use SubtitleToolbox\Subtitle; use SubtitleToolbox\Sync\ReferenceSync; use SubtitleToolbox\Sync\ReferenceSyncOptions; $german = Subtitle::load('movie.de.srt', Format::SubRip); $english = Subtitle::load('movie.en.srt', Format::SubRip); $report = ReferenceSync::apply($german, new ReferenceSyncOptions($english)); echo "offset $report->offset s, scale $report->scale, score $report->score\n";
Every exception implements SubtitleToolboxException. See errors.md.
OCR
OCR turns the bitmaps of PGS and VobSub subtitles into text. The library uses Tesseract when it is installed. Otherwise it uses php-glyph-ocr. When neither engine is installed, the call throws InvalidArgumentException that names both engines. Tesseract reads more than 100 languages, php-glyph-ocr reads only Latin-script fonts. See ocr.md for the install commands and a comparison of the engines.
Compatibility
Semantic versioning covers the public PHP API and the CLI commands, options, exit codes and --json shapes. See compatibility.md for what a minor or patch release can change.
Documentation
docs/README.md lists every page. The most used pages:
- cli.md: all commands and options
- formats.md: what each parser reads and each formatter writes
- editing.md: retiming, cutting, joining and splitting cues
- text.md: text changes, hearing-impaired removal, common error fixes
- validation.md: rules and presets
- sync.md: sync to a reference or to the speech
- subtitle.md: metadata, comments, cue lookup, statistics
Contributing
Pull requests are welcome. Run the tests with composer test. Each pull request carries one label that sets the version bump: major, minor, patch or skip-release. Every merge to master publishes a release.
Licence
MIT, see LICENSE.