Public/Repair/Repair-SubtitleOverlap.ps1

function Repair-SubtitleOverlap {
    <#
    .SYNOPSIS
        Resolves timestamp overlaps between subtitle entries.
    .DESCRIPTION
        Three strategies:
        - Trim (default): shorten the earlier entry's end time to the start of the next.
        - Shift: push the later entry forward to start after the previous ends.
        - Drop: remove the entry that starts before the previous one ends.
    .PARAMETER InputObject
        A SubtitleFile object.
    .PARAMETER Strategy
        Overlap resolution strategy: Trim, Shift, or Drop.
    .PARAMETER Gap
        Minimum gap in milliseconds to insert between entries when using Trim or Shift. Default: 0.
    .EXAMPLE
        Import-SubtitleFile 'movie.srt' | Repair-SubtitleOverlap -Strategy Trim
    .EXAMPLE
        Import-SubtitleFile 'movie.srt' | Repair-SubtitleOverlap -Strategy Shift -Gap 50
    #>

    [CmdletBinding()]
    [OutputType('SubtitleFile')]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [SubtitleFile] $InputObject,

        [ValidateSet('Trim', 'Shift', 'Drop')]
        [string] $Strategy = 'Trim',

        [int] $Gap = 0
    )

    process {
        $gapSpan  = [TimeSpan]::FromMilliseconds($Gap)
        $sorted   = [System.Collections.Generic.List[SubtitleEntry]]::new()
        $sorted.AddRange([SubtitleEntry[]]($InputObject.Entries | Sort-Object Start))

        $repaired = [System.Collections.Generic.List[SubtitleEntry]]::new()
        $prevEnd  = [TimeSpan]::Zero

        foreach ($entry in $sorted) {
            if ($entry.Start -lt $prevEnd) {
                switch ($Strategy) {
                    'Trim' {
                        # Shorten the previous entry
                        if ($repaired.Count -gt 0) {
                            $lastRepaired = $repaired[$repaired.Count - 1]
                            $newEnd = $entry.Start - $gapSpan
                            if ($newEnd -gt $lastRepaired.Start) {
                                $lastRepaired.End = $newEnd
                            }
                        }
                        $repaired.Add($entry)
                        $prevEnd = $entry.End
                    }
                    'Shift' {
                        # Push this entry forward
                        $shift       = $prevEnd + $gapSpan - $entry.Start
                        $duration    = $entry.End - $entry.Start
                        $entry.Start = $prevEnd + $gapSpan
                        $entry.End   = $entry.Start + $duration
                        $repaired.Add($entry)
                        $prevEnd = $entry.End
                    }
                    'Drop' {
                        # Skip this entry entirely
                        Write-SubtitleLog -Message "Dropped overlapping entry $($entry.Index)." -Level Warning
                        continue
                    }
                }
            } else {
                $repaired.Add($entry)
                $prevEnd = $entry.End
            }
        }

        $InputObject.Entries = $repaired.ToArray()

        # Re-index
        $i = 1
        foreach ($e in $InputObject.Entries) { $e.Index = $i++ }

        return $InputObject
    }
}