A LONG time ago, somebody on here created an amazing bat file for me to extract subs from .mov files. I used the power of ChatGPT and augmented it.

The reason I did this was because I needed .srt files created from a bunch of .mov files, and then take all of those and combine them into a "master.mkv" file.

Your original files won't be harmed during this entire process. I found this very useful and am sharing it with the class in case anyone else is ever looking for the same thing.

Here's a crude breakdown of what it does.

1) It finds all the .mov files in your folder and goes through them and creates corresponding srt files with Y-M-D hh:mm format that displays every second

2) It creates new mov files (*.subtitled.mov) by embedding their srt track from step 1 into each, individual mov file

3) The last step, it takes all the newly created *.subtitled.mov files and combines them into a single, master.mkv

I intentionally decided to not use .mov in the "master" file and make it output a mkv file instead.

You MUST have ffmpeg and ffprobe in your path (example; c:\ffmpeg\bin)

Code:
# combo mkv
# 1) Extract SRT files with local 12-hour timestamps (no seconds in text) from all .mov files
# 2) Mux each .mov + .srt into *_subtitled.mkv (with embedded subrip subtitles)
# 3) Concatenate all *_subtitled.mkv into master.mkv
#
# Requires ffprobe and ffmpeg in PATH, or adjust $ffprobe and $ffmpeg below.

$ffprobe = "ffprobe"
$ffmpeg  = "ffmpeg"

function Get-VideoInfo {
    param(
        [string]$Path
    )

    $args = @(
        "-v", "error",
        "-select_streams", "v:0",
        "-show_entries", "format=duration:format_tags=creation_time",
        "-of", "default=noprint_wrappers=1:nokey=1",
        "$Path"
    )

    $output = & $ffprobe @args 2>$null

    if (-not $output) {
        return $null
    }

    $lines = $output -split "`n"
    # Expecting: duration on first line, creation_time (if present) on second
    $duration = [double]$lines[0]

    $creationTime = $null

    if ($lines.Count -gt 1 -and $lines[1] -ne "") {
        try {
            # Parse as DateTimeOffset so we respect UTC/offset
            $dto = [DateTimeOffset]::Parse($lines[1])
            # Convert to local computer timezone
            $creationTime = $dto.ToLocalTime().DateTime
        } catch {
            # If parsing fails, fall back to file system timestamp
        }
    }

    if (-not $creationTime) {
        # Fallback to file system timestamp (already local)
        $fi = Get-Item -LiteralPath $Path
        $creationTime = $fi.CreationTime
    }

    return [PSCustomObject]@{
        Duration     = $duration
        CreationTime = $creationTime
    }
}

function Format-SrtTime {
    param(
        [double]$Seconds
    )

    if ($Seconds -lt 0) { $Seconds = 0 }

    $ts = [TimeSpan]::FromSeconds($Seconds)
    return ("{0:00}:{1:00}:{2:00},{3:000}" -f `
        [int]$ts.Hours, `
        [int]$ts.Minutes, `
        [int]$ts.Seconds, `
        [int]($ts.Milliseconds))
}

Write-Host "Processing .mov files in: $(Get-Location)`n"

$movFiles = Get-ChildItem -LiteralPath . -Filter "*.mov" | Sort-Object Name

if (-not $movFiles) {
    Write-Host "No .mov files found."
    return
}

# -------------------------------------------------
# PHASE 1: Generate SRT files for each .mov
# -------------------------------------------------
Write-Host "=== PHASE 1: Extracting SRT files from MOV metadata ===`n"

$step = 1   # seconds between subtitles

foreach ($mov in $movFiles) {
    Write-Host "Processing $($mov.Name)..."

    $info = Get-VideoInfo -Path $mov.FullName
    if (-not $info) {
        Write-Warning "  Could not read info for $($mov.Name). Skipping."
        continue
    }

    $durationSeconds = [double]$info.Duration
    $creationTime    = [DateTime]$info.CreationTime   # already local time

    $srtPath = [System.IO.Path]::ChangeExtension($mov.FullName, ".srt")

    $lines = New-Object System.Collections.Generic.List[string]

    $index = 1

    for ($t = 0.0; $t -lt $durationSeconds; $t += $step) {

        $start = $t
        $end   = [Math]::Min($t + $step, $durationSeconds - 0.001)

        $startSrt = Format-SrtTime -Seconds $start
        $endSrt   = Format-SrtTime -Seconds $end

        # Text: local creation time + offset t, 12h AM/PM format, NO seconds
        $currentTime = $creationTime.AddSeconds([int][Math]::Round($t))
        $text = $currentTime.ToString("yyyy-MM-dd hh:mm tt")

        $lines.Add("$index")
        $lines.Add("$startSrt --> $endSrt")
        $lines.Add($text)
        $lines.Add("")   # blank line between entries

        $index++
    }

    Set-Content -LiteralPath $srtPath -Value $lines -Encoding UTF8
    Write-Host "  Created SRT: $([System.IO.Path]::GetFileName($srtPath))"
}

Write-Host "`n=== PHASE 1 complete ===`n"

# -------------------------------------------------
# PHASE 2: Create *_subtitled.mkv for each .mov+.srt pair
# -------------------------------------------------
Write-Host "=== PHASE 2: Creating *_subtitled.mkv files ===`n"

foreach ($mov in $movFiles) {
    $baseName = [System.IO.Path]::GetFileNameWithoutExtension($mov.Name)
    $srtPath  = [System.IO.Path]::ChangeExtension($mov.FullName, ".srt")
    $outPath  = Join-Path $mov.DirectoryName ($baseName + "_subtitled.mkv")

    if (-not (Test-Path -LiteralPath $srtPath)) {
        Write-Warning "  No matching SRT for $($mov.Name). Skipping mux."
        continue
    }

    Write-Host "Muxing $($mov.Name) + $(Split-Path $srtPath -Leaf) -> $(Split-Path $outPath -Leaf)"

    $ffmpegArgs = @(
        "-y",
        "-i", $mov.FullName,
        "-i", $srtPath,
        "-c:v", "copy",
        "-c:a", "copy",
        "-c:s", "srt",   # embed subtitles as subrip, ideal for MKV
        $outPath
    )

    & $ffmpeg @ffmpegArgs

    if ($LASTEXITCODE -ne 0) {
        Write-Warning "  ffmpeg failed for $($mov.Name)."
    } else {
        Write-Host "  Created: $(Split-Path $outPath -Leaf)"
    }
}

Write-Host "`n=== PHASE 2 complete ===`n"

# -------------------------------------------------
# PHASE 3: Concatenate *_subtitled.mkv into master.mkv
# -------------------------------------------------
Write-Host "=== PHASE 3: Building master.mkv from *_subtitled.mkv ===`n"

$subtitledFiles = Get-ChildItem -LiteralPath . -Filter "*_subtitled.mkv" | Sort-Object Name

if (-not $subtitledFiles) {
    Write-Warning "No *_subtitled.mkv files found. master.mkv will not be created."
    Write-Host "`nAll done."
    return
}

$listPath  = Join-Path (Get-Location) "list.txt"
$listLines = @()

foreach ($f in $subtitledFiles) {
    # Use relative names; escape single quotes for ffmpeg concat
    $escapedName = $f.Name.Replace("'", "''")
    $listLines += "file '$escapedName'"
}

# Write list.txt without BOM so ffmpeg concat doesn't choke
Set-Content -LiteralPath $listPath -Value $listLines -Encoding ASCII
Write-Host "Created list.txt with $($subtitledFiles.Count) entries."

$concatArgs = @(
    "-y",
    "-f", "concat",
    "-safe", "0",
    "-i", $listPath,
    "-map", "0",
    "-c", "copy",
    "master.mkv"
)

& $ffmpeg @concatArgs

if ($LASTEXITCODE -ne 0) {
    Write-Warning "ffmpeg concat failed. master.mkv was not created."
} else {
    Write-Host "Created master.mkv"
}

Write-Host "`nAll done."
Save this as.... combo.ps1 (or whatever name you want) and place it in your folder with the mov files and execute it from that folder within Powershell.