PAYATHON 2026

如何在 PowerShell 中正确转义 FFmpeg ASS 字幕文件路径?

数据小许

字幕路径中盘符后的冒号需要转义,让 FFmpeg 收到 ass=filename='C\:/Users/X/AppData/Local/Temp/subtitles.ass'。只把反斜杠换成 / 不够;使用 Start-Process 时,还要处理命令行参数的双引号,避免带空格的路径被拆开。

为什么文件存在,FFmpeg 仍然报错?

关键日志是:

Unable to parse option value "/Users/X/AppData/Local/Temp/subtitles.ass" as image size
Error applying option 'original_size' to filter 'ass': Invalid argument

路径在滤镜参数解析时被拆开了。ass 的选项用冒号分隔,未转义的 C: 导致后半段路径被当成下一个位置参数 original_size,因此出现“无法解析为图像尺寸”的错误。

FFmpeg 的 filtergraph 和滤镜选项有不同的转义层级。这里的外层单引号保护路径中的反斜杠,让 \: 继续交给滤镜选项解析器处理。因此,ass='C:/...' 中的单引号不能代替冒号转义。相关规则见 FFmpeg 滤镜转义说明。

如何修改现有代码?

对于问题中这样的普通 Windows 绝对路径,可以替换路径处理、参数构造和进程启动部分。下面的示例支持路径中包含空格,但假定字幕路径不包含单引号:

# Convert path separators and escape the drive-letter colon for FFmpeg.
$assPathFixed = $assPath.Replace('\', '/').Replace(':', '\:')

# These quotes are literal characters needed by Start-Process.
$ffmpegArgs = @(
    '-i'
    ('"{0}"' -f $videoPath)
    '-vf'
    ('"ass=filename=''{0}''"' -f $assPathFixed)
    '-c:a'
    'copy'
    '-y'
    ('"{0}"' -f $outputPath)
)

$argumentLine = $ffmpegArgs -join ' '
Write-Host "Running: ffmpeg $argumentLine"

$errorLogPath = 'ffmpeg_sub_error.log'
$outputLogPath = 'ffmpeg_sub_output.log'

$processOptions = @{
    FilePath               = 'ffmpeg'
    ArgumentList           = $argumentLine
    NoNewWindow            = $true
    Wait                   = $true
    RedirectStandardError  = $errorLogPath
    RedirectStandardOutput = $outputLogPath
    PassThru               = $true
}

$process = Start-Process @processOptions

原路径:

C:\Users\X\AppData\Local\Temp\subtitles.ass

转换后的 $assPathFixed:

C\:/Users/X/AppData/Local/Temp/subtitles.ass

构造出的命令行片段:

-vf "ass=filename='C\:/Users/X/AppData/Local/Temp/subtitles.ass'"

外层双引号用于把整个滤镜表达式作为一个命令行参数传入;内部单引号和 \: 属于 FFmpeg 的滤镜语法。filename= 明确指定字幕文件选项,但盘符冒号仍需转义。

PowerShell 的转义字符是反引号,代码中的反斜杠会保留给 FFmpeg。上面的 .Replace() 使用字面字符串替换,避免将正则表达式转义和滤镜转义混在一起。

为什么 Start-Process 的数组还需要引号?

Start-Process -ArgumentList 会将字符串数组用空格拼成一个命令行字符串,数组元素的边界不会自动成为子进程参数的边界。因此,"-i", $videoPath 在路径包含空格时仍可能出问题。

示例显式添加了命令行需要的双引号,再通过单个字符串传给 ArgumentList。具体行为和用法见 Start-Process 的 ArgumentList 文档。这些额外的双引号用于 Start-Process 构造命令行;如果改成直接调用 & ffmpeg @ffmpegArgs,需要按直接调用的方式重新组织参数。

如何修复后续的空值异常和成功判断?

最后的错误:

You cannot call a method on a null-valued expression.

是 PowerShell 脚本自身的另一个问题。用 Get-Content -Raw 读取空日志可能得到 $null,接着调用 .Trim() 就会抛出异常。没有错误行号,无法仅凭现有日志确定具体是哪一次调用,但两处日志读取都应处理空值,例如:

if (Test-Path -LiteralPath $errorLogPath) {
    $errorContent = [string](Get-Content -LiteralPath $errorLogPath -Raw)
    if (-not [string]::IsNullOrWhiteSpace($errorContent)) {
        Write-Host 'FFmpeg STDERR:' -ForegroundColor Red
        Write-Host $errorContent -ForegroundColor Red
    }
}

对 $outputContent 使用相同的处理即可。

原来的成功条件也需要调整。如果只检查输出文件是否存在,即使退出码非零,也可能报告成功。输出文件可能是旧文件,也可能是本次失败留下的不完整文件。至少应同时检查退出码和文件是否存在:

if (($process.ExitCode -eq 0) -and
    (Test-Path -LiteralPath $outputPath -PathType Leaf)) {
    Write-Host "Subtitles added successfully: $outputPath" -ForegroundColor Green
    # Clean up the temporary subtitle file only after success.
    Remove-Item -LiteralPath $assPath -ErrorAction SilentlyContinue
    return $true
}

排查期间应保留失败时的字幕文件和日志,方便重试。如果字幕路径包含单引号等滤镜语法特殊字符,上面的替换并不能通用地处理转义;可以先将字幕复制到名称简单的临时路径再调用。

另外,ass 会将字幕烧录到视频画面中,需要重新编码视频。现有的 -c:a copy 仅复制音频,可以保留;不能为了避免视频重编码而添加 -c:v copy。

备注:内容仅供参考。