105 lines
4.8 KiB
PowerShell
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
|
|
}
|