private/Matroska过程.ps1
|
function 取_EBML变长整数长度 { param([byte]$FirstByte) for ($len = 1; $len -le 8; $len++) { $mask = [byte](0x80 -shr ($len - 1)) if (($FirstByte -band $mask) -ne 0) { return $len } } return $null } function 读_EBML_ID { param([byte[]]$Bytes, [int]$Offset) $len = 取_EBML变长整数长度 -FirstByte $Bytes[$Offset] if (-not $len) { return $null } $id = 0 for ($i = 0; $i -lt $len; $i++) { $id = ($id -shl 8) -bor $Bytes[$Offset + $i] } return [pscustomobject]@{ Length = $len; Id = $id } } function 读_EBML_Size { param([byte[]]$Bytes, [int]$Offset) $len = 取_EBML变长整数长度 -FirstByte $Bytes[$Offset] if (-not $len) { return $null } $first = $Bytes[$Offset] $mask = [byte](0x80 -shr ($len - 1)) $value = ($first -bxor $mask) for ($i = 1; $i -lt $len; $i++) { $value = ($value -shl 8) -bor $Bytes[$Offset + $i] } $maxVal = ([int64]1 -shl (7 * $len)) - 1 $unknown = ([int64]$value -eq $maxVal) return [pscustomobject]@{ Length = $len; Size = [int64]$value; Unknown = $unknown } } function 读_EBML_UInt { param([byte[]]$Bytes, [int]$Offset, [int]$Length) $v = [int64]0 for ($i = 0; $i -lt $Length; $i++) { $v = ($v -shl 8) -bor $Bytes[$Offset + $i] } return $v } function 写_EBML_UInt_原地 { param([byte[]]$Bytes, [int]$Offset, [int]$Length, [int64]$Value) for ($i = 0; $i -lt $Length; $i++) { $shift = 8 * ($Length - 1 - $i) $Bytes[$Offset + $i] = [byte](($Value -shr $shift) -band 0xFF) } } function 打开_内存映射访问器_可写 { param([Parameter(Mandatory)] [string]$Path) $len = [int64](Get-Item -LiteralPath $Path).Length if ($len -le 0) { throw "文件长度无效:$Path" } # .NET Framework 下 mapName 不能为 $null/空字符串;这里用一个临时唯一名称。 $mapName = ("mkvfix_{0}" -f ([Guid]::NewGuid().ToString('N'))) $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile( $Path, [System.IO.FileMode]::Open, $mapName, $len, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::ReadWrite ) $acc = $mmf.CreateViewAccessor(0, $len, [System.IO.MemoryMappedFiles.MemoryMappedFileAccess]::ReadWrite) return [pscustomobject]@{ Mmf = $mmf; Acc = $acc; Length = $len } } function 读_EBML_ID_映射 { param( [Parameter(Mandatory)] [System.IO.MemoryMappedFiles.MemoryMappedViewAccessor]$Acc, [Parameter(Mandatory)] [int64]$Offset, [Parameter(Mandatory)] [int64]$TotalLength ) if ($Offset -lt 0 -or $Offset -ge $TotalLength) { return $null } $first = $Acc.ReadByte($Offset) $len = 取_EBML变长整数长度 -FirstByte $first if (-not $len) { return $null } if (($Offset + $len) -gt $TotalLength) { return $null } $buf = New-Object byte[] $len [void]$Acc.ReadArray($Offset, $buf, 0, $len) $id = 0 for ($i = 0; $i -lt $len; $i++) { $id = ($id -shl 8) -bor $buf[$i] } return [pscustomobject]@{ Length = $len; Id = $id } } function 读_EBML_Size_映射 { param( [Parameter(Mandatory)] [System.IO.MemoryMappedFiles.MemoryMappedViewAccessor]$Acc, [Parameter(Mandatory)] [int64]$Offset, [Parameter(Mandatory)] [int64]$TotalLength ) if ($Offset -lt 0 -or $Offset -ge $TotalLength) { return $null } $first = $Acc.ReadByte($Offset) $len = 取_EBML变长整数长度 -FirstByte $first if (-not $len) { return $null } if (($Offset + $len) -gt $TotalLength) { return $null } $buf = New-Object byte[] $len [void]$Acc.ReadArray($Offset, $buf, 0, $len) $mask = [byte](0x80 -shr ($len - 1)) $value = [int64]($buf[0] -bxor $mask) for ($i = 1; $i -lt $len; $i++) { $value = ($value -shl 8) -bor $buf[$i] } $maxVal = ([int64]1 -shl (7 * $len)) - 1 $unknown = ([int64]$value -eq $maxVal) return [pscustomobject]@{ Length = $len; Size = [int64]$value; Unknown = $unknown } } function 读_EBML_UInt_映射 { param( [Parameter(Mandatory)] [System.IO.MemoryMappedFiles.MemoryMappedViewAccessor]$Acc, [Parameter(Mandatory)] [int64]$Offset, [Parameter(Mandatory)] [int]$Length, [Parameter(Mandatory)] [int64]$TotalLength ) if ($Length -le 0) { return [int64]0 } if (($Offset + $Length) -gt $TotalLength) { return $null } $buf = New-Object byte[] $Length [void]$Acc.ReadArray($Offset, $buf, 0, $Length) $v = [int64]0 for ($i = 0; $i -lt $Length; $i++) { $v = ($v -shl 8) -bor $buf[$i] } return $v } function 写_EBML_UInt_原地_映射 { param( [Parameter(Mandatory)] [System.IO.MemoryMappedFiles.MemoryMappedViewAccessor]$Acc, [Parameter(Mandatory)] [int64]$Offset, [Parameter(Mandatory)] [int]$Length, [Parameter(Mandatory)] [int64]$Value, [Parameter(Mandatory)] [int64]$TotalLength ) if ($Length -le 0) { return } if (($Offset + $Length) -gt $TotalLength) { return } $buf = New-Object byte[] $Length for ($i = 0; $i -lt $Length; $i++) { $shift = 8 * ($Length - 1 - $i) $buf[$i] = [byte](($Value -shr $shift) -band 0xFF) } [void]$Acc.WriteArray($Offset, $buf, 0, $Length) } function 尝试_修正Matroska_DefaultDuration { param([Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [string]$Fps) if (-not (Test-Path -LiteralPath $Path)) { return $false } if ([System.IO.Path]::GetExtension($Path).ToLowerInvariant() -ne '.mkv') { return $false } $targetNs = 转换_fps到DefaultDuration_ns -Fps $Fps if (-not $targetNs -or $targetNs -le 0) { return $false } $map = $null try { $map = 打开_内存映射访问器_可写 -Path $Path $acc = $map.Acc $lenTotal = $map.Length $SEGMENT_ID = 0x18538067 $TRACKS_ID = 0x1654AE6B $TRACKENTRY_ID = 0xAE $TRACKTYPE_ID = 0x83 $DEFAULTDURATION_ID = 0x23E383 $pos = [int64]0 $segmentStart = $null $segmentEnd = $lenTotal while ($pos -lt $lenTotal - 12) { $id = 读_EBML_ID_映射 -Acc $acc -Offset $pos -TotalLength $lenTotal if (-not $id) { break } $size = 读_EBML_Size_映射 -Acc $acc -Offset ($pos + $id.Length) -TotalLength $lenTotal if (-not $size) { break } $dataStart = $pos + $id.Length + $size.Length if ($id.Id -eq $SEGMENT_ID) { $segmentStart = $dataStart if ($size.Unknown) { $segmentEnd = $lenTotal } else { $segmentEnd = [math]::Min($lenTotal, $dataStart + [int64]$size.Size) } break } if ($size.Unknown) { break } $pos = $dataStart + [int64]$size.Size } if ($null -eq $segmentStart) { return $false } $pos = [int64]$segmentStart $tracksStart = $null $tracksEnd = $null while ($pos -lt $segmentEnd - 12) { $id = 读_EBML_ID_映射 -Acc $acc -Offset $pos -TotalLength $lenTotal if (-not $id) { break } $size = 读_EBML_Size_映射 -Acc $acc -Offset ($pos + $id.Length) -TotalLength $lenTotal if (-not $size) { break } $dataStart = $pos + $id.Length + $size.Length $dataEnd = if ($size.Unknown) { $segmentEnd } else { [math]::Min($segmentEnd, $dataStart + [int64]$size.Size) } if ($id.Id -eq $TRACKS_ID) { $tracksStart = $dataStart $tracksEnd = $dataEnd break } $pos = $dataEnd } if ($null -eq $tracksStart) { return $false } $pos = [int64]$tracksStart while ($pos -lt $tracksEnd - 8) { $id = 读_EBML_ID_映射 -Acc $acc -Offset $pos -TotalLength $lenTotal if (-not $id) { break } $size = 读_EBML_Size_映射 -Acc $acc -Offset ($pos + $id.Length) -TotalLength $lenTotal if (-not $size) { break } $dataStart = $pos + $id.Length + $size.Length $dataEnd = [math]::Min($tracksEnd, $dataStart + [int64]$size.Size) if ($id.Id -eq $TRACKENTRY_ID) { $trackType = $null $defaultDurOffset = $null $defaultDurLen = $null $p2 = [int64]$dataStart while ($p2 -lt $dataEnd - 4) { $cid = 读_EBML_ID_映射 -Acc $acc -Offset $p2 -TotalLength $lenTotal if (-not $cid) { break } $csz = 读_EBML_Size_映射 -Acc $acc -Offset ($p2 + $cid.Length) -TotalLength $lenTotal if (-not $csz) { break } $cDataStart = $p2 + $cid.Length + $csz.Length $cDataEnd = [math]::Min($dataEnd, $cDataStart + [int64]$csz.Size) if ($cid.Id -eq $TRACKTYPE_ID -and $csz.Size -ge 1 -and $csz.Size -le 8) { $trackType = 读_EBML_UInt_映射 -Acc $acc -Offset $cDataStart -Length ([int]$csz.Size) -TotalLength $lenTotal } if ($cid.Id -eq $DEFAULTDURATION_ID -and $csz.Size -ge 1 -and $csz.Size -le 8) { $defaultDurOffset = $cDataStart $defaultDurLen = [int]$csz.Size } $p2 = $cDataEnd } if ($trackType -eq 1 -and $null -ne $defaultDurOffset -and $null -ne $defaultDurLen) { $old = 读_EBML_UInt_映射 -Acc $acc -Offset $defaultDurOffset -Length $defaultDurLen -TotalLength $lenTotal if ([math]::Abs([double]$old - [double]$targetNs) -gt 1000) { $max = ([int64]1 -shl (8 * $defaultDurLen)) - 1 if ($targetNs -le $max) { 写_EBML_UInt_原地_映射 -Acc $acc -Offset $defaultDurOffset -Length $defaultDurLen -Value $targetNs -TotalLength $lenTotal $acc.Flush() Write-Host ("已修正 Matroska DefaultDuration: {0} -> {1} (ns)" -f $old, $targetNs) -ForegroundColor Yellow return $true } } return $false } } $pos = $dataEnd } return $false } finally { if ($map -and $map.Acc) { $map.Acc.Dispose() } if ($map -and $map.Mmf) { $map.Mmf.Dispose() } } } function 尝试_修正Matroska_FrameRate元素 { param([Parameter(Mandatory)] [string]$Path, [Parameter(Mandatory)] [string]$Fps) if (-not (Test-Path -LiteralPath $Path)) { return $false } if ([System.IO.Path]::GetExtension($Path).ToLowerInvariant() -ne '.mkv') { return $false } $fpsValue = 解析_有理数 $Fps if (-not $fpsValue -or $fpsValue -le 0) { return $false } $map = $null try { $map = 打开_内存映射访问器_可写 -Path $Path $acc = $map.Acc $lenTotal = $map.Length $pat0 = [byte]0x23 $pat1 = [byte]0x83 $pat2 = [byte]0xE3 # FrameRate 元素通常在头部 Tracks 附近;只扫描前 64MiB 以提升速度 $scanLen = [int64]([math]::Min($lenTotal, 64MB)) $chunkSize = 4MB $overlap = 2 $offset = [int64]0 $changed = $false while ($offset -lt $scanLen) { $remain = [int64]($scanLen - $offset) $readLen = [int]([math]::Min([int64]$chunkSize, $remain)) if ($readLen -le 0) { break } $buf = New-Object byte[] $readLen [void]$acc.ReadArray($offset, $buf, 0, $readLen) for ($i = 0; $i -lt ($readLen - 16); $i++) { if ($buf[$i] -ne $pat0 -or $buf[$i+1] -ne $pat1 -or $buf[$i+2] -ne $pat2) { continue } $abs = $offset + $i $sizeOff = $abs + 3 if ($sizeOff -ge $lenTotal) { continue } $first = $acc.ReadByte($sizeOff) $len = 取_EBML变长整数长度 -FirstByte $first if (-not $len) { continue } if (($sizeOff + $len) -gt $lenTotal) { continue } $szBuf = New-Object byte[] $len [void]$acc.ReadArray($sizeOff, $szBuf, 0, $len) $mask = [byte](0x80 -shr ($len - 1)) $sz = [int64]($szBuf[0] -bxor $mask) for ($k = 1; $k -lt $len; $k++) { $sz = ($sz -shl 8) -bor $szBuf[$k] } $dataOff = $sizeOff + $len if (($dataOff + $sz) -gt $lenTotal) { continue } if ($sz -eq 4) { $val = New-Object byte[] 4 [void]$acc.ReadArray($dataOff, $val, 0, 4) $cur = [BitConverter]::ToSingle([byte[]]($val[3],$val[2],$val[1],$val[0]), 0) if ($cur -gt 100 -or $cur -lt 1) { $target = [single]$fpsValue $le = [BitConverter]::GetBytes($target) $be = [byte[]]@($le[3],$le[2],$le[1],$le[0]) [void]$acc.WriteArray($dataOff, $be, 0, 4) $changed = $true } } elseif ($sz -eq 8) { $val = New-Object byte[] 8 [void]$acc.ReadArray($dataOff, $val, 0, 8) $cur = [BitConverter]::ToDouble([byte[]]($val[7],$val[6],$val[5],$val[4],$val[3],$val[2],$val[1],$val[0]), 0) if ($cur -gt 100 -or $cur -lt 1) { $target = [double]$fpsValue $le = [BitConverter]::GetBytes($target) $be = New-Object byte[] 8 for ($k = 0; $k -lt 8; $k++) { $be[$k] = $le[7-$k] } [void]$acc.WriteArray($dataOff, $be, 0, 8) $changed = $true } } } if ($offset -eq 0) { $offset += ([int64]$chunkSize - $overlap) } else { $offset += ([int64]$chunkSize - $overlap) } } if ($changed) { $acc.Flush() Write-Host "已修正 Matroska Video->FrameRate 字段(用于 MediaInfo 显示)" -ForegroundColor Yellow } return $changed } finally { if ($map -and $map.Acc) { $map.Acc.Dispose() } if ($map -and $map.Mmf) { $map.Mmf.Dispose() } } } |