Skip to content

About

Toolbox for converting and manipulating subtitles

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

Subtitle Toolbox

Packagist version CI License

A PHP library and command line tool that reads, edits and writes subtitles, transcripts and chapter lists in more than 30 formats.

Upgrading from 2.x? The package has a new name. See the upgrade guide.

Upgrading from 3.x? See the 4.0 upgrade guide.

Install

composer require yama6a/subtitle-toolbox-php
# Optional, for OCR of PGS, VobSub and DVB image subtitles:
composer require yama6a/php-glyph-ocr:^0.3   # php-glyph-ocr, the pure PHP OCR engine
apt install tesseract-ocr                    # or Tesseract on Debian and Ubuntu, for more than 100 languages

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, ext-iconv and ext-mbstring. These extensions are optional:

  • ext-zlib for PGS output and compressed MKV tracks
  • ext-curl for the translation engines

The command line tool also comes as a PHAR file and as two container images. The -tesseract image includes Tesseract for OCR. See Install without Composer for the commands.

Supported formats

Case is the case of the enum Format, for example Format::SubRip. Name is the format name for --from and --to. An image cue holds a bitmap in place of text.

Text subtitles

Format Case Name Extensions Read Write Notes
ASS, SSA Ass ass .ass, .ssa ✅ ✅
Cavena 890 Cavena890 cavena890 .890 ✅ ✅ binary, no public spec, not detected from the content
CSV, TSV Csv, Tsv csv, tsv .csv, .tsv ✅ ✅ not detected from the content
DCP subtitles: SMPTE ST 428-7, Interop Dcp dcp .xml ✅ ✅ digital cinema text subtitles
EBU STL EbuStl stl .stl ✅ ✅ binary, 25 or 30 fps
iTunes Timed Text Itt itt .itt ✅ ✅ needs a frame rate to write
JSON of this library Json json .json ✅ ✅
LRC Lyrics lrc .lrc ✅ ✅ with enhanced LRC word times
MCC Mcc mcc .mcc ✅ ✅ CEA-608 and CEA-708 closed captions of MacCaption and CaptionMaker
MicroDVD MicroDvd microdvd .sub ✅ ✅ needs the frame rate of the video
MPL2 Mpl2 mpl2 .txt ✅ ✅
MPSub MpSub mpsub .mpsub ✅ ✅
PAC by Screen Electronics, PAC Unicode Pac pac .pac, .fpc ✅ ✅ binary, no public spec, code page detected
QuickTime text QuickTimeText qttext .txt ✅ ✅ {QTtext} text tracks of QuickTime
SAMI Sami sami .smi, .sami ✅ ✅ one language class per parse
SBV Sbv sbv .sbv ✅ ✅
SCC Scc scc .scc ✅ ✅ CEA-608 closed captions
Spruce STL SpruceStl spruce-stl .stl ✅ ✅ DVD Studio Pro text subtitles
SubRip SubRip srt .srt ✅ ✅
SubViewer 1 and 2 SubViewer subviewer .sub ✅ ✅
TMPlayer TmPlayer tmplayer .txt ✅ ✅
TTML, IMSC, DFXP Ttml ttml .ttml, .dfxp, .xml ✅ ✅
WebVTT WebVtt vtt .vtt ✅ ✅ also chapters
YouTube timed text YouTubeTimedText youtube .srv3, .json3, .srv1, .ytt ✅ ✅ json3, srv1, srv2, srv3 and transcript XML

Image subtitles

Format Case Name Extensions Read Write Notes
PGS Pgs pgs .sup ✅ ✅ Blu-ray bitmaps as image cues
VobSub VobSub vobsub .idx with .sub ✅ ❌ DVD bitmaps as image cues

Transcripts

Format Case Name Extensions Read Write Notes
Cloud speech-to-text JSON: AWS Transcribe, Deepgram, AssemblyAI, Google Speech-to-Text AwsTranscribe, Deepgram, AssemblyAi, GoogleSpeech aws-transcribe, deepgram, assemblyai, google-speech .json ✅ ✅ not detected from the content
HTML transcript HtmlTranscript html .html, .htm ✅ ✅ the Podcasting 2.0 HTML format
Plain text PlainText txt .txt ❌ ✅ transcript
Podcasting 2.0 transcript JSON PodcastTranscript podcast-transcript .json ✅ ✅
Whisper JSON Whisper whisper .json ✅ ✅ OpenAI API, openai-whisper, faster-whisper, WhisperX, whisper.cpp

Chapters

Format Case Name Extensions Read Write Notes
FFmpeg metadata chapters FfMetadataChapters ffmeta-chapters .ffmeta ✅ ✅ not detected from the content
OGM chapters OgmChapters ogm-chapters .txt ✅ ✅ not detected from the content
Podcasting 2.0 chapters PodcastChapters podcast-chapters .json ✅ ✅ not detected from the content
YouTube chapters YouTubeChapters youtube-chapters .txt ✅ ✅ not detected from the content

Containers

Format Case Name Extensions Read Write Notes
MKV and WebM tracks .mkv, .webm ✅ ❌ text, ASS, SSA, WebVTT, PGS, VobSub and DVB subtitle tracks
MP4 tracks .mp4, .m4v, .mov ✅ ❌ tx3g (mov_text) tracks, c608 and c708 closed caption tracks
MPEG-TS ARIB captions .ts, .m2ts ✅ ❌ Japanese ISDB and Brazilian ISDB-Tb captions with furigana and gaiji
MPEG-TS DVB subtitles .ts ✅ ❌ DVB bitmap subtitles as image cues
MPEG-TS teletext .ts ✅ ❌ DVB teletext subtitle pages, see ETS 300 706

Command line tool

Composer installs vendor/bin/stbox. Optional parts are in brackets.

stbox convert movie.srt --to vtt [--timing-fix-overlaps] [-o movie.vtt]
stbox convert movie.mkv --to srt --track 3 [-o movie.srt]
stbox convert movie.sup --to srt --ocr [--ocr-language deu] [-o movie.srt]
stbox convert movie.sub --input-fps 25 --from-fps 25 --to-fps 23.976 [-o movie.fixed.sub]
stbox info movie.srt [--json]
stbox validate movie.srt --preset netflix-en [--video-fps 23.976]
stbox sync movie.de.srt --reference movie.en.srt [-o movie.de.synced.srt]
stbox diff movie.v1.srt movie.v2.srt [--text-only]
stbox translate movie.de.srt --engine deepl --target-language en-US [-o movie.en.srt]
stbox dual --primary movie.en.srt --secondary movie.de.srt --to ass [--mode stack] [-o movie.en-de.ass]
stbox hls movie.vtt --output-dir hls/ [--segment 6]
stbox formats
  • Without -o, the output goes to standard output.
  • Several inputs need --output-dir, for example convert season1/ --shift 2 --output-dir fixed/.
  • No command overwrites a file.
  • When detection fails, pass --from.

stbox 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::load('movie.srt');
$subtitle->findFormat();                           // 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(formats: [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);
$subtitle = Subtitle::loadTrack('recording.ts', 777);                          // teletext page 777 of a DVB recording
$subtitle = Subtitle::loadTrack('recording.ts', 2050);                         // DVB bitmap subtitles with PID 2050, as image cues

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(reference: $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, VobSub and DVB 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

See compatibility.md for the parts that semantic versioning covers and 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. See CONTRIBUTING.md for the licence rules and the credit rule. 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.

License

MIT, see LICENSE. Material from third parties keeps its own licence, see THIRD_PARTY_NOTICES.md.

Credits

The PHP libraries mantas-done/subtitles and captioning/captioning shaped this library. Their formats, test files and user reports guided many decisions here. THIRD_PARTY_NOTICES.md lists every file copied from other projects.

About

Toolbox for converting and manipulating subtitles

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages