NateMac00

mkv-reorder ps1

May 12th, 2026
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Batch 7.41 KB | None | 0 0
  1. [CmdletBinding()]
  2. param(
  3.     [Parameter(Mandatory = $true, Position = 0)]
  4.     [string]$FilePath,
  5.  
  6.     [switch]$NoBackup,
  7.     [switch]$DryRun
  8. )
  9.  
  10. $ErrorActionPreference = 'Stop'
  11.  
  12. function Find-Mkvmerge {
  13.     $candidates = @(
  14.         'C:\Program Files\MKVToolNix\mkvmerge.exe',
  15.         'C:\Program Files (x86)\MKVToolNix\mkvmerge.exe'
  16.     )
  17.     foreach ($p in $candidates) {
  18.         if (Test-Path -LiteralPath $p) { return $p }
  19.     }
  20.     $cmd = Get-Command mkvmerge.exe -ErrorAction SilentlyContinue
  21.     if ($cmd) { return $cmd.Source }
  22.     return $null
  23. }
  24.  
  25. function Format-Channels($n) {
  26.     switch ([int]$n) {
  27.         1 { '1.0' ; break }
  28.         2 { '2.0' ; break }
  29.         3 { '2.1' ; break }
  30.         6 { '5.1' ; break }
  31.         7 { '6.1' ; break }
  32.         8 { '7.1' ; break }
  33.         default { "${n}ch" }
  34.     }
  35. }
  36.  
  37. function Write-Err($msg) {
  38.     Write-Host "ERROR: $msg" -ForegroundColor Red
  39. }
  40.  
  41. # --- Locate mkvmerge ---
  42. $mkvmerge = Find-Mkvmerge
  43. if (-not $mkvmerge) {
  44.     Write-Err "mkvmerge.exe not found."
  45.     Write-Host "Install MKVToolNix from: https://mkvtoolnix.download/"
  46.     exit 1
  47. }
  48.  
  49. # --- Validate file ---
  50. if (-not (Test-Path -LiteralPath $FilePath)) {
  51.     Write-Err "File not found: $FilePath"
  52.     exit 1
  53. }
  54.  
  55. $file = Get-Item -LiteralPath $FilePath
  56. if ($file.Extension -inotmatch '^\.mkv$') {
  57.     Write-Err "Not an MKV file: $($file.Name)"
  58.     Write-Host "This tool only handles .mkv files."
  59.     exit 1
  60. }
  61.  
  62. # --- Identify tracks ---
  63. Write-Host ""
  64. Write-Host "File: $($file.Name)" -ForegroundColor Cyan
  65. Write-Host ""
  66.  
  67. $jsonRaw = & $mkvmerge --identify --identification-format json $file.FullName 2>&1
  68. if ($LASTEXITCODE -ne 0) {
  69.     Write-Err "mkvmerge failed to identify the file:"
  70.     Write-Host ($jsonRaw -join "`n")
  71.     exit 1
  72. }
  73.  
  74. try {
  75.     $info = ($jsonRaw -join "`n") | ConvertFrom-Json
  76. } catch {
  77.     Write-Err "Could not parse mkvmerge output: $_"
  78.     exit 1
  79. }
  80.  
  81. $audioTracks    = @($info.tracks | Where-Object { $_.type -eq 'audio' })
  82. $videoTracks    = @($info.tracks | Where-Object { $_.type -eq 'video' })
  83. $subtitleTracks = @($info.tracks | Where-Object { $_.type -eq 'subtitles' })
  84.  
  85. if ($audioTracks.Count -eq 0) {
  86.     Write-Err "No audio tracks in this file. Nothing to do."
  87.     exit 1
  88. }
  89.  
  90. # --- Display tracks ---
  91. Write-Host "Audio tracks:"
  92. foreach ($a in $audioTracks) {
  93.     $codec    = $a.codec
  94.     $ch       = Format-Channels $a.properties.audio_channels
  95.     $lang     = if ($a.properties.language) { $a.properties.language } else { 'und' }
  96.     $title    = if ($a.properties.track_name) { '"' + $a.properties.track_name + '"' } else { '' }
  97.     $default  = if ($a.properties.default_track) { '[DEFAULT]' } else { '' }
  98.     $line = "  [{0,2}] {1,-24} {2,-5} {3,-4} {4,-30} {5}" -f $a.id, $codec, $ch, $lang, $title, $default
  99.     Write-Host $line
  100. }
  101. Write-Host ""
  102.  
  103. if ($audioTracks.Count -eq 1) {
  104.     Write-Host "Only one audio track present - nothing to reorder."
  105.     exit 0
  106. }
  107.  
  108. # --- Prompt for selection ---
  109. $validIds = @($audioTracks | ForEach-Object { [int]$_.id })
  110. $chosenId = $null
  111. $attempts = 0
  112. while ($attempts -lt 3) {
  113.     $userInput = Read-Host "Which audio track should be the new default? Enter track ID"
  114.     $parsed = 0
  115.     if ([int]::TryParse($userInput, [ref]$parsed) -and ($validIds -contains $parsed)) {
  116.         $chosenId = $parsed
  117.         break
  118.     }
  119.     Write-Host "  Invalid track ID. Valid IDs: $($validIds -join ', ')" -ForegroundColor Yellow
  120.     $attempts++
  121. }
  122.  
  123. if ($null -eq $chosenId) {
  124.     Write-Err "Too many invalid attempts. Exiting."
  125.     exit 1
  126. }
  127.  
  128. $chosen = $audioTracks | Where-Object { [int]$_.id -eq $chosenId } | Select-Object -First 1
  129. $chosenCodec = $chosen.codec
  130. $chosenCh    = Format-Channels $chosen.properties.audio_channels
  131.  
  132. # --- Summary + confirm ---
  133. Write-Host ""
  134. Write-Host "Will move track [$chosenId] $chosenCodec $chosenCh to be the first audio track and set it as default."
  135. if ($NoBackup) {
  136.     Write-Host "Original will be replaced (no backup)."
  137. } else {
  138.     Write-Host "Original will be backed up to: $($file.Name).bak"
  139. }
  140. Write-Host "This will take 1-3 minutes depending on file size. No re-encoding."
  141. if ($DryRun) { Write-Host "[DRY RUN] No changes will be made." -ForegroundColor Yellow }
  142. Write-Host ""
  143.  
  144. $response = Read-Host "Proceed? [Y/N]"
  145. if ($response -notmatch '^[Yy]') {
  146.     Write-Host "Cancelled. No changes made."
  147.     exit 0
  148. }
  149.  
  150. # --- Build track order ---
  151. $orderParts = @()
  152. foreach ($v in $videoTracks)    { $orderParts += "0:$($v.id)" }
  153. $orderParts += "0:$chosenId"
  154. foreach ($a in $audioTracks)    { if ([int]$a.id -ne $chosenId) { $orderParts += "0:$($a.id)" } }
  155. foreach ($s in $subtitleTracks) { $orderParts += "0:$($s.id)" }
  156.  
  157. $trackOrderArg = $orderParts -join ','
  158.  
  159. # --- Build default-flag args ---
  160. $defaultArgs = @()
  161. foreach ($a in $audioTracks) {
  162.     $flag = if ([int]$a.id -eq $chosenId) { '1' } else { '0' }
  163.     $defaultArgs += '--default-track-flag'
  164.     $defaultArgs += "$($a.id):$flag"
  165. }
  166.  
  167. $tempFile   = "$($file.FullName).tmp"
  168. $backupFile = "$($file.FullName).bak"
  169.  
  170. $mkvmergeArgs = @(
  171.     '--output', $tempFile,
  172.     '--track-order', $trackOrderArg
  173. ) + $defaultArgs + @($file.FullName)
  174.  
  175. if ($DryRun) {
  176.     Write-Host ""
  177.     Write-Host "[DRY RUN] Would run:"
  178.     Write-Host "  `"$mkvmerge`""
  179.     foreach ($arg in $mkvmergeArgs) {
  180.         Write-Host "    $arg"
  181.     }
  182.     exit 0
  183. }
  184.  
  185. # --- Pre-flight: temp file collision ---
  186. if (Test-Path -LiteralPath $tempFile) {
  187.     Write-Host "Removing stale temp file: $tempFile" -ForegroundColor Yellow
  188.     Remove-Item -LiteralPath $tempFile -Force
  189. }
  190.  
  191. # --- Remux ---
  192. Write-Host ""
  193. Write-Host "Remuxing..." -ForegroundColor Cyan
  194. $start = Get-Date
  195.  
  196. & $mkvmerge @mkvmergeArgs
  197. $exitCode = $LASTEXITCODE
  198.  
  199. if ($exitCode -ne 0) {
  200.     Write-Err "mkvmerge failed with exit code $exitCode"
  201.     if (Test-Path -LiteralPath $tempFile) {
  202.         Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue
  203.     }
  204.     exit $exitCode
  205. }
  206.  
  207. if (-not (Test-Path -LiteralPath $tempFile)) {
  208.     Write-Err "mkvmerge reported success but the output file is missing: $tempFile"
  209.     exit 1
  210. }
  211.  
  212. # --- Swap files ---
  213. try {
  214.     if ($NoBackup) {
  215.         Remove-Item -LiteralPath $file.FullName -Force
  216.     } else {
  217.         if (Test-Path -LiteralPath $backupFile) {
  218.             Remove-Item -LiteralPath $backupFile -Force
  219.         }
  220.         Rename-Item -LiteralPath $file.FullName -NewName ($file.Name + '.bak')
  221.     }
  222.     Rename-Item -LiteralPath $tempFile -NewName $file.Name
  223. } catch {
  224.     Write-Err "Failed to swap files: $_"
  225.     Write-Host "Temp file preserved at: $tempFile"
  226.     exit 1
  227. }
  228.  
  229. $elapsed = (Get-Date) - $start
  230. $elapsedStr = '{0:D2}:{1:D2}' -f [int]$elapsed.TotalMinutes, $elapsed.Seconds
  231.  
  232. Write-Host ""
  233. Write-Host "Done in $elapsedStr" -ForegroundColor Green
  234. Write-Host ""
  235. Write-Host "New audio track order:"
  236. $newOrder = @($chosen) + @($audioTracks | Where-Object { [int]$_.id -ne $chosenId })
  237. $pos = 1
  238. foreach ($a in $newOrder) {
  239.     $codec   = $a.codec
  240.     $ch      = Format-Channels $a.properties.audio_channels
  241.     $lang    = if ($a.properties.language) { $a.properties.language } else { 'und' }
  242.     $title   = if ($a.properties.track_name) { '"' + $a.properties.track_name + '"' } else { '' }
  243.     $default = if ([int]$a.id -eq $chosenId) { '[DEFAULT]' } else { '' }
  244.     $line = "  {0}. {1,-24} {2,-5} {3,-4} {4,-30} {5}" -f $pos, $codec, $ch, $lang, $title, $default
  245.     Write-Host $line
  246.     $pos++
  247. }
  248. Write-Host ""
  249.  
  250. exit 0
  251.  
Advertisement
Add Comment
Please, Sign In to add comment