如何在 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。
备注:内容仅供参考。
