246 lines
7.2 KiB
PowerShell
246 lines
7.2 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Creates a full subtitle file from a video file from three sources: a reference video and two SRT subtitle files.
|
|
This script will extract the video time length and the last subtitle number used in the first subtitle file,
|
|
then will apply them to the second subtitle file for proper timing and numbering.
|
|
The output file will contain all subtitles from the first and second subtitle files with proper timing and numbering.
|
|
.DESCRIPTION
|
|
This script creates a full subtitle file from a video file from three sources: a reference video and two SRT subtitle files.
|
|
The output file contains all subtitles from the first and second subtitle files with proper timing and numbering.
|
|
.PARAMETER Video
|
|
Path to the video file.
|
|
.PARAMETER Subtitle1
|
|
Path to the first subtitle file.
|
|
.PARAMETER Subtitle2
|
|
Path to the second subtitle file.
|
|
.PARAMETER Output
|
|
Path to the output subtitle file.
|
|
#>
|
|
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Video,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Subtitle1,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Subtitle2,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Output
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
#region Helper Functions
|
|
|
|
function Get-VideoDuration {
|
|
param([string]$VideoPath)
|
|
|
|
if (-not (Test-Path -LiteralPath $VideoPath -PathType Leaf)) {
|
|
throw "Video file not found: $VideoPath"
|
|
}
|
|
|
|
$frameCountOutput = ffprobe -v error -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of csv=p=0 "$VideoPath" 2>&1
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "ffprobe failed to get video frame count"
|
|
}
|
|
|
|
$fpsOutput = ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of csv=p=0 "$VideoPath" 2>&1
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "ffprobe failed to get video fps"
|
|
}
|
|
|
|
$frameCount = [int]$frameCountOutput.Trim()
|
|
|
|
# r_frame_rate is returned as a fraction e.g. "24/1" or "30000/1001"
|
|
$fpsParts = $fpsOutput.Trim() -split '/'
|
|
$fps = [double]$fpsParts[0] / [double]$fpsParts[1]
|
|
|
|
return $frameCount / $fps
|
|
}
|
|
|
|
function ConvertTo-TimeSpan {
|
|
param([string]$SrtTime)
|
|
|
|
# SRT format: HH:MM:SS,mmm
|
|
$pattern = '^(\d{2}):(\d{2}):(\d{2}),(\d{3})$'
|
|
if (-not ($SrtTime -match $pattern)) {
|
|
throw "Invalid SRT time format: $SrtTime"
|
|
}
|
|
|
|
$hours = [int]$matches[1]
|
|
$minutes = [int]$matches[2]
|
|
$seconds = [int]$matches[3]
|
|
$milliseconds = [int]$matches[4]
|
|
|
|
return New-TimeSpan -Hours $hours -Minutes $minutes -Seconds $seconds -Milliseconds $milliseconds
|
|
}
|
|
|
|
function ConvertFrom-TimeSpan {
|
|
param([TimeSpan]$TimeSpan)
|
|
|
|
$hours = [int][Math]::Floor($TimeSpan.TotalHours)
|
|
$minutes = $TimeSpan.Minutes
|
|
$seconds = $TimeSpan.Seconds
|
|
$milliseconds = $TimeSpan.Milliseconds
|
|
|
|
return '{0:D2}:{1:D2}:{2:D2},{3:D3}' -f $hours, $minutes, $seconds, $milliseconds
|
|
}
|
|
|
|
function Add-TimeToSrt {
|
|
param(
|
|
[string]$SrtTime,
|
|
[double]$OffsetSeconds
|
|
)
|
|
|
|
$timeSpan = ConvertTo-TimeSpan -SrtTime $SrtTime
|
|
$offsetSpan = [TimeSpan]::FromSeconds($OffsetSeconds)
|
|
$newTimeSpan = $timeSpan.Add($offsetSpan)
|
|
|
|
return ConvertFrom-TimeSpan -TimeSpan $newTimeSpan
|
|
}
|
|
|
|
function Get-SrtContent {
|
|
param([string]$SrtPath)
|
|
|
|
if (-not (Test-Path -LiteralPath $SrtPath -PathType Leaf)) {
|
|
throw "Subtitle file not found: $SrtPath"
|
|
}
|
|
|
|
$content = Get-Content -LiteralPath $SrtPath -Raw
|
|
$entries = @()
|
|
|
|
# Split by double newline to separate subtitle entries
|
|
$rawEntries = $content -split "`r?`n`r?`n"
|
|
|
|
foreach ($rawEntry in $rawEntries) {
|
|
$lines = $rawEntry.Trim() -split "`r?`n"
|
|
|
|
if ($lines.Count -lt 2) {
|
|
continue
|
|
}
|
|
|
|
# First line should be the subtitle number
|
|
$number = 0
|
|
if (-not ([int]::TryParse($lines[0].Trim(), [ref]$number))) {
|
|
continue
|
|
}
|
|
|
|
# Second line should be the timing
|
|
$timingLine = $lines[1].Trim()
|
|
$timingPattern = '^(.+?) --> (.+?)$'
|
|
if (-not ($timingLine -match $timingPattern)) {
|
|
continue
|
|
}
|
|
|
|
$startTime = $matches[1]
|
|
$endTime = $matches[2]
|
|
|
|
# Remaining lines are the text
|
|
$textLines = $lines[2..($lines.Count - 1)]
|
|
$text = ($textLines -join "`n").Trim()
|
|
|
|
$entries += [PSCustomObject]@{
|
|
Number = $number
|
|
StartTime = $startTime
|
|
EndTime = $endTime
|
|
Text = $text
|
|
}
|
|
}
|
|
|
|
return $entries
|
|
}
|
|
|
|
function Export-SrtFile {
|
|
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[array]$Entries,
|
|
[string]$OutputPath
|
|
)
|
|
|
|
$outputDir = Split-Path -Parent $OutputPath
|
|
if ($outputDir -and -not (Test-Path -LiteralPath $outputDir)) {
|
|
if ($PSCmdlet.ShouldProcess($outputDir, 'Create output directory')) {
|
|
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
|
|
}
|
|
}
|
|
|
|
$srtContent = @()
|
|
|
|
for ($i = 0; $i -lt $Entries.Count; $i++) {
|
|
$entry = $Entries[$i]
|
|
$srtContent += $entry.Number
|
|
$srtContent += "$($entry.StartTime) --> $($entry.EndTime)"
|
|
$srtContent += $entry.Text
|
|
|
|
if ($i -lt $Entries.Count - 1) {
|
|
$srtContent += ''
|
|
}
|
|
}
|
|
|
|
if ($PSCmdlet.ShouldProcess($OutputPath, 'Write combined SRT file')) {
|
|
$srtContent -join "`r`n" | Out-File -LiteralPath $OutputPath -Encoding UTF8 -NoNewline
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Main Script
|
|
|
|
Write-Host "Parsing first subtitle file: $Subtitle1"
|
|
$subtitle1Entries = Get-SrtContent -SrtPath $Subtitle1
|
|
Write-Host " Found $($subtitle1Entries.Count) entries"
|
|
|
|
if ($subtitle1Entries.Count -eq 0) {
|
|
throw "No valid subtitle entries found in: $Subtitle1"
|
|
}
|
|
|
|
$lastNumber = $subtitle1Entries[-1].Number
|
|
Write-Host " Last subtitle number: $lastNumber"
|
|
|
|
Write-Host "Extracting video duration from: $Video"
|
|
$videoDuration = Get-VideoDuration -VideoPath $Video
|
|
$videoDurationFormatted = ConvertFrom-TimeSpan -TimeSpan ([TimeSpan]::FromSeconds($videoDuration))
|
|
Write-Host " Video duration: $videoDurationFormatted (${videoDuration}s)"
|
|
|
|
Write-Host "Parsing second subtitle file: $Subtitle2"
|
|
$subtitle2Entries = Get-SrtContent -SrtPath $Subtitle2
|
|
Write-Host " Found $($subtitle2Entries.Count) entries"
|
|
|
|
if ($subtitle2Entries.Count -eq 0) {
|
|
throw "No valid subtitle entries found in: $Subtitle2"
|
|
}
|
|
|
|
Write-Host "Adjusting second subtitle timing and numbering..."
|
|
$adjustedSubtitle2Entries = @()
|
|
|
|
for ($i = 0; $i -lt $subtitle2Entries.Count; $i++) {
|
|
$entry = $subtitle2Entries[$i]
|
|
$newNumber = $lastNumber + $i + 1
|
|
|
|
$newStartTime = Add-TimeToSrt -SrtTime $entry.StartTime -OffsetSeconds $videoDuration
|
|
$newEndTime = Add-TimeToSrt -SrtTime $entry.EndTime -OffsetSeconds $videoDuration
|
|
|
|
$adjustedSubtitle2Entries += [PSCustomObject]@{
|
|
Number = $newNumber
|
|
StartTime = $newStartTime
|
|
EndTime = $newEndTime
|
|
Text = $entry.Text
|
|
}
|
|
}
|
|
|
|
Write-Host " Adjusted $($adjustedSubtitle2Entries.Count) entries"
|
|
|
|
$combinedEntries = $subtitle1Entries + $adjustedSubtitle2Entries
|
|
Write-Host "Combined total: $($combinedEntries.Count) entries"
|
|
|
|
Write-Host "Writing output file: $Output"
|
|
Export-SrtFile -Entries $combinedEntries -OutputPath $Output
|
|
Write-Host "Output file created successfully: $Output"
|
|
|
|
#endregion |