前幾天提到我要下載 Podcast 音檔存入 MP3 運動耳機跑步時聽。平常用手機聽中文 Podcast 我會加快 20% ~ 50% 省點時間,但 MP3 耳機不能調播放速度,所以我想跑批次作業將 MP3 加速後另存新檔。要達成此任務,強大的開源自由軟體多媒體轉檔工具 - FFmpeg 是首選。

https://github.com/BtbN/FFmpeg-Builds 找到編譯好的 Windows 版 (註:下載版本很多,N-xxxxxx 是 Nightly-Build 即時新版,-n4.4 是較穩定的發行版 參考,選 win64-gpl 版) 取得神奇的 ffmpeg.exe,執行 ffmpeg -i input.mp3 -filter:a "atempo=1.5" -vn output.mp3 可將 input.mp3 加速 50% 另存成 output.mp3。參考

我打算寫一小段 PowerShell 列舉原始 MP3,加速另存新檔再取代原檔。本以為是個簡單任務,想說寫一下放著跑去洗澡,沒想到一不小心踩坑,搞到凌晨澡都還沒洗。

PowerShell 要呼叫外部 EXE 程式並動態決定參數,使用呼叫運算子 & 是最簡單做法:

不過,這次的檔名參數有空白還需要雙引號包夾,我怎麼都無法用變數組裝出可執行的 ffmpeg.exe -i ".\EP121 我不是登徒子啦.mp3" -filter:a "atempo=1.5" -vn ".\EP121 我不是登徒子啦.x15.mp3"

# 沒用變數時,寫死檔名沒問題
& '.\ffmpeg.exe' -i ".\EP121 我不是登徒子啦.mp3" -filter:a "atempo=1.5" -vn ".\EP121 我不是登徒子啦.x15.mp3"

$fn = "EP121 我不是登徒子啦"

# 轉成內容一模一樣的字串變數卻不行
$argStr = "-i `".\$fn`" -filter:a `"atempo=1.5`" -vn `".\$fn.x15.mp3`""
& '.\ffmpeg.exe' $argStr
# 錯誤:Unrecognized option 'i .\EP121'.
# Error splitting the argument list: Option not found

# 拆成三個參數不行,雙引號似乎沒作用,EP121 空格後方被當成下一個參數
# 註:雙引號包含內容要 Escape 雙引號,寫 `" 或 "" 都行
$i = "-i `".\$fn.mp3`""
$vn = "-vn `".\$fn.x15.mp3`""
$filter = '-filter:a "atempo=1.5"'
& '.\ffmpeg.exe' $i $filter $vn
# 錯誤:Unrecognized option 'i .\EP121'.
# Error splitting the argument list: Option not found

# 寫成 \",ffmpeg 會看到 ",含空白的路徑被視為一個字串,但仍無法執行
$i = "-i \`".\$fn.mp3`""
$vn = "-vn `".\$fn.x15.mp3`""
$filter = '-filter:a "atempo=1.5"'
& '.\ffmpeg.exe' $i $filter $vn
# 錯誤:Unrecognized option 'i ".\EP121 我不是登徒子啦.mp3"'.
# Error splitting the argument list: Option not found

搞了大半天沒結果,上網查資料,發現這是一個很容易踩到的坑。PowerShell 解析參數時,對於單引號、雙引號一個或兩個、前面有沒有加 \,以及中間是否包含空白的解析規則遠比想像複雜,例如以下這張表:


參考來源

【延伸閱讀】

這個解析規則複雜度之高,高到開發社群甚至寫了個小工具 EchoArgs.exe 協助除錯,它被包含在 Pscx (PowerShell Community Extension) 模組,理論上可以用 Install-Module 安裝,但 Pscx 有點年久失修,安裝時會遇到 此系統上已有下列命令: 'gcb,Expand-Archive,Forma t-Hex,Get-Hash,help,Invoke-NullCoalescing,prompt,Dismount-VHD,Get-Clipboard,Get-Hel p,Mount-VHD,Set-Clipboard'。 錯誤,我選擇從 Manual Download 下載 .nupkg 檔解壓縮取檔。

將 ffmpeg.exe 換成 EchoArgs.exe,很快就知道我們傳入的參數是如何被曲解了::D

用對工具,很快找出正確的寫法 & '.\ffmpeg.exe' -i ".\$fn.mp3" -filter:a "atempo=1.5" -vn ".\$fn.x15.mp3",答案出奇簡單,"$fn.mp3" 本身已算一個獨立參數,不需特別再加雙引號。

掌握這個技巧,未來在 PowerShell 傳遞內含空白參數給外部 EXE 時,就不會像無頭蒼蠅到處亂轉了。

Tips of passing arguments with spaces and quotation marks to external programs and how to use EchoArgs.exe to debug.


Comments

# by 湯包

""".\EP121 我不是登徒子啦.x15.mp3"""

# by Jeffrey

to 湯包,跟其他參數混用時較易有問題,例如: λ .\EchoArgs.exe "-i "".\EP121 我不是登徒子啦.x15.mp3""" Arg 0 is <-i .\EP121> Arg 1 is <我不是登徒子啦.x15.mp3>

# by goto3375

$fn = '11 222.mp3' $argStr = "-i `".\$fn`" -filter:a `"atempo=1.5`" -vn `".\$fn.x15.mp3`"" $cmdLine = '.\ffmpeg.exe' + ' ' + $argStr cmd /s /c $cmdLine ####################### $fn = '11 222.mp3' #字串陣列 $arr = '-i', $fn, '-filter:a', 'atempo=1.5', '-vn', ".\$fn.x15.mp3" & '.\ffmpeg.exe' $arr

Post a comment