Files
Video-Processing-Scripts/Get-VideoSubtitles.ps1
T

105 lines
4.8 KiB
PowerShell

<#
.SYNOPSIS
Extracts the embedded subtitle tracks of a video file into the current directory.
.PARAMETER Video
Path to the input video file. Can be absolute or relative to current directory.
.PARAMETER List
When present, lists the subtitle tracks instead of extracting them. Creates no files.
.DESCRIPTION
This script uses ffprobe to list the subtitle streams of a video file and ffmpeg to copy each one out:
- Text subtitles (SubRip, WebVTT and mov_text, found in MKV, WebM, MP4, MOV and M4V) are written as .srt.
- ASS/SSA subtitles (MKV) are written as .ass, keeping their styling.
- Blu-ray PGS subtitles (hdmv_pgs_subtitle, found in MKV, M2TS and TS) are copied unchanged to .sup.
- Bitmap DVD subtitles (dvd_subtitle, found in MKV and VOB) and DVB subtitles (dvb_subtitle, found in TS) are listed but not extracted, because ffmpeg cannot write them as text or as a standalone file.
- AVI files have no standard subtitle track, so nothing is extracted from them.
Bitmap subtitles can only be turned into text with OCR, which this script does not do.
Each file is named "<video name>.<language>.<extension>" in the current directory. The language is the 2-letter ISO 639-1 code when one exists, otherwise the 3-letter code from the file, or "und" when the track has no language tag. A repeated name gets a counter: "<video name>.<language>.2.<extension>".
An existing file with the same name is overwritten.
.NOTES
Requires ffmpeg and ffprobe to be installed and available in the system PATH.
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[string]$Video,
[Parameter(Mandatory = $false)]
[switch]$List
)
function Get-LanguageCode([string]$Tag) {
if (-not $Tag -or $Tag -eq 'und') { return 'und' }
$culture = [System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::NeutralCultures) | Where-Object { $_.ThreeLetterISOLanguageName -eq $Tag } | Select-Object -First 1
if ($culture) { return $culture.TwoLetterISOLanguageName }
return $Tag
}
try {
if (-not (Test-Path -LiteralPath $Video)) {
throw "Video file not found: $Video"
}
$videoPath = (Resolve-Path -LiteralPath $Video).Path
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($videoPath)
$outputDir = (Get-Location).ProviderPath
$ffprobeJson = & ffprobe -v error -select_streams s -show_entries 'stream=index,codec_name:stream_tags=language' -of json -- $videoPath
if ($LASTEXITCODE -ne 0) {
throw "ffprobe failed to read video file: $videoPath"
}
$streams = @((ConvertFrom-Json ($ffprobeJson -join "`n")).streams | Where-Object { $_ })
$plan = @()
$used = @{}
foreach ($s in $streams) {
$ext = switch ($s.codec_name) {
{ $_ -in 'subrip', 'mov_text', 'webvtt' } { '.srt' }
{ $_ -in 'ass', 'ssa' } { '.ass' }
'hdmv_pgs_subtitle' { '.sup' }
default { $null }
}
$code = Get-LanguageCode $s.tags.language
$fileName = "$baseName.$code$ext"
$counter = 1
while ($used.ContainsKey($fileName.ToLowerInvariant())) {
$counter++
$fileName = "$baseName.$code.$counter$ext"
}
$used[$fileName.ToLowerInvariant()] = $true
$plan += [pscustomobject]@{ Index = [int]$s.index; Codec = $s.codec_name; Ext = $ext; File = Join-Path $outputDir $fileName }
}
Write-Host "Subtitle tracks in: $videoPath"
if ($streams.Count -eq 0) {
Write-Host "No subtitle stream found in video file." -ForegroundColor Yellow
exit 0
}
if ($List) {
$plan | Format-Table -Property @{ Label = 'Index'; Expression = { $_.Index } }, @{ Label = 'Codec'; Expression = { $_.Codec } }, @{ Label = 'Output'; Expression = { if ($_.Ext) { Split-Path -Leaf $_.File } else { 'not supported' } } } -AutoSize | Out-String | Write-Host
exit 0
}
foreach ($p in $plan) {
if (-not $p.Ext) {
Write-Warning "Subtitle stream $($p.Index) ($($p.Codec)) is not supported and was skipped."
continue
}
if ($PSCmdlet.ShouldProcess($p.File, "Extract subtitle stream $($p.Index) ($($p.Codec))")) {
$codecArgs = if ($p.Ext -eq '.srt') { @('-c:s', 'srt') } else { @('-c:s', 'copy') }
if ($p.Ext -eq '.sup') { $codecArgs += @('-f', 'sup') }
& ffmpeg -v error -y -i $videoPath -map "0:$($p.Index)" @codecArgs $p.File
if ($LASTEXITCODE -ne 0) {
throw "ffmpeg failed to extract subtitle stream $($p.Index) with exit code $LASTEXITCODE"
}
if (-not (Test-Path -LiteralPath $p.File)) {
throw "Subtitle file was not created: $($p.File)"
}
Write-Host "Subtitle extracted: $($p.File)" -ForegroundColor Green
}
}
}
catch {
Write-Error "Error: $_"
exit 1
}