public/DV转HDR10.ps1

function DV转HDR10 {
    <#
    .SYNOPSIS
        将 Dolby Vision HEVC 视频转为带 HDR10 元数据的 HEVC 文件。
 
    .DESCRIPTION
        使用支持 libplacebo 滤镜的 ffmpeg 处理 Dolby Vision 元数据和色彩空间,并输出 BT.2020、PQ(SMPTE ST 2084)、TV range 的 HDR10 HEVC 视频。
 
        命令只转换第一个视频轨,并复制输入文件中的所有音频轨;字幕、附件和其它非音频轨不会映射到输出文件。
        如果检测到 hevc_nvenc,则使用 GPU 编码(preset p4、VBR、CQ 等于 -质量);否则回退到 libx265(medium,并把 -质量 写入 x265 CRF 参数)。
 
        未指定输出路径时,会在输入文件同目录生成“原文件名_HDR10.mkv”。启用 -测试秒数 时,输出名会变为“原文件名_HDR10_testNs.mkv”,并只转换开头 N 秒,便于快速验证色彩和编码参数。
        输出文件已存在时会被覆盖。
 
    .PARAMETER 输入文件
        输入 MKV/MP4 等媒体文件路径。文件必须存在。
 
    .PARAMETER 输出文件
        输出文件路径。省略时自动生成 MKV 输出文件名。
 
    .PARAMETER 质量
        视频编码质量值,范围 1 到 51。数值越小质量越高、文件通常越大。使用 hevc_nvenc 时作为 CQ;使用 libx265 时作为 CRF。
        默认值为 19。
 
    .PARAMETER 测试秒数
        只转换输入开头指定秒数。默认 0 表示转换完整文件。设置为正数时输出文件名会自动带 test 后缀。
 
    .EXAMPLE
        DV转HDR10 -输入文件 'D:\电影.DV.mkv'
 
        将完整影片转换为 D:\电影.DV_HDR10.mkv。
 
    .EXAMPLE
        DV转HDR10 -输入文件 'D:\电影.DV.mkv' -测试秒数 30 -质量 21
 
        只转换前 30 秒,用稍高 CRF/CQ 值快速测试输出效果。
 
    .EXAMPLE
        DV转HDR10 -输入文件 'D:\电影.DV.mkv' -输出文件 'D:\电影.HDR10.mkv' -质量 18
 
        使用指定输出路径和质量参数转换完整文件。
 
    .NOTES
        依赖支持 libplacebo 滤镜的 ffmpeg。命令会优先寻找 winget 安装的 Gyan.FFmpeg 或 PATH 中的 ffmpeg;找不到合适构建时会尝试安装。
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, HelpMessage = '输入 MKV/MP4 文件路径')]
        [string]$输入文件,

        [string]$输出文件 = '',

        [ValidateRange(1, 51)]
        [int]$质量 = 19,

        [int]$测试秒数 = 0
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'
    $script:输出外部进程信息 = $false

    if (-not (Test-Path -LiteralPath $输入文件)) {
        throw "文件不存在: $输入文件"
    }

    if (-not $输出文件) {
        $绝对路径 = (Resolve-Path -LiteralPath $输入文件).Path
        $基础名 = [System.IO.Path]::GetFileNameWithoutExtension($绝对路径)
        $目录 = [System.IO.Path]::GetDirectoryName($绝对路径)
        $后缀 = if ($测试秒数 -gt 0) { "_HDR10_test${测试秒数}s" } else { '_HDR10' }
        $输出文件 = Join-Path $目录 "${基础名}${后缀}.mkv"
    }

    $ffmpeg = 确保_ffmpeg支持过滤器 -过滤器名称 'libplacebo'
    $版本信息 = (& $ffmpeg -version 2>&1 | Select-Object -First 1) -replace 'ffmpeg version ', ''
    Write-Host "ffmpeg : $版本信息"
    Write-Host "路径 : $ffmpeg"

    $支持nvenc = 测试_ffmpeg视频编码器可用 -FfmpegPath $ffmpeg -编码器名称 'hevc_nvenc'
    $视频过滤器基础 = 'libplacebo=colorspace=bt2020nc:color_trc=smpte2084:color_primaries=bt2020:range=tv:apply_dolbyvision=true'

    if ($支持nvenc) {
        Write-Host '编码器 : hevc_nvenc(GPU)'
        $视频过滤器 = "$视频过滤器基础,format=p010le"
        $视频编码参数 = @(
            '-c:v', 'hevc_nvenc',
            '-preset', 'p4',
            '-rc', 'vbr',
            '-cq', "$质量"
        )
    } else {
        Write-Host '编码器 : libx265(CPU,NVENC 不可用)'
        $x265参数 = "colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:hdr10=1:crf=$质量"
        $视频过滤器 = "$视频过滤器基础,format=yuv420p10le"
        $视频编码参数 = @(
            '-c:v', 'libx265',
            '-preset', 'medium',
            '-x265-params', $x265参数
        )
    }

    $色彩元数据 = @(
        '-color_primaries', 'bt2020',
        '-color_trc', 'smpte2084',
        '-colorspace', 'bt2020nc',
        '-color_range', 'tv'
    )

    $时长参数 = if ($测试秒数 -gt 0) { @('-t', "$测试秒数") } else { @() }
    $参数列表 = @('-y') +
        $时长参数 +
        @('-i', $输入文件, '-vf', $视频过滤器) +
        $视频编码参数 +
        $色彩元数据 +
        @('-map', '0:v:0', '-map', '0:a', '-c:a', 'copy', $输出文件)

    Write-Host ''
    Write-Host "输入 : $输入文件"
    Write-Host "输出 : $输出文件"
    Write-Host "质量 : $质量"
    if ($测试秒数 -gt 0) { Write-Host "测试模式: 仅转换前 ${测试秒数} 秒" }
    Write-Host ''
    Write-Host '开始转换'

    $旧错误偏好 = $ErrorActionPreference
    $ErrorActionPreference = 'SilentlyContinue'
    & $ffmpeg @参数列表
    $退出码 = $LASTEXITCODE
    $ErrorActionPreference = $旧错误偏好

    if ($退出码 -eq 0) {
        $大小MB = [math]::Round((Get-Item -LiteralPath $输出文件).Length / 1MB, 1)
        Write-Host ''
        Write-Host '转换完成'
        Write-Host "输出文件:$输出文件(${大小MB} MB)"
    } else {
        throw "转换失败(退出码:$退出码)"
    }
}