Inital push
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
<#
|
||||
.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
|
||||
Reference in New Issue
Block a user