<# .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