调用FFmpeg的Bash自动化脚本运行失败,但复制命令可正常执行
问题描述
我写了一个Bash脚本,用FFmpeg自动化处理视频转码,集成了音轨选择、字幕源设置、视频/音频转码控制、音量调整、立体声降级、字幕语言设置、标题元数据修改等功能。但运行脚本时FFmpeg总是报错:
Unable to find a suitable output format for 'Trek:' Trek:: Invalid argument
奇怪的是,把脚本生成的命令复制到终端手动执行却完全正常。报错里的Trek:来自titles.txt文件里的标题内容,我搞不懂为什么它会被当成输出格式参数,而不是标题元数据。
相关文件内容
完整脚本
#!/bin/bash audioTrack=1 subSource=1 subTrack=0 transcodeVideo=1 transcodeAudio=1 volumeMultiplier=1 degradeToStereo=0 subLanguage="Japanese" while getopts "t:ns:vam:dl:h" opt; do case "$opt" in t) audioTrack=${OPTARG};; n) subSource=0;; s) subTrack=${OPTARG};; v) transcodeVideo=0;; a) transcodeAudio=0;; m) volumeMultiplier=${OPTARG};; d) degradeToStereo=1;; l) subLanguage=${OPTARG};; h) echo "Options:" echo "-t [integer]: Audio track number. Default: 1." echo "-n: If included, subtitles will be taken from internal source." echo "-s [integer]: Subtitles track number. Default: 0." echo "-v: If included, video source will be copied without transcoding." echo "-a: If included, audio source will be copied without transcoding." echo "-m [number]: Volume multiplier. If 1, volume is unaffected. Default: 1" echo "-d: If included, audio will be degraded to stereo." echo "-l [language]: Subtitles language. Only used for external subtitles source. Default: Japanese." exit 0 ;; esac done echo "Audio track: $audioTrack." echo "Subtitles track: $subTrack." params="-map 0:0 -map 0:$audioTrack -map $subSource:$subTrack -c:v" if [[ $transcodeVideo -eq 1 ]]; then echo "Video will be transcoded." params="$params hevc" elif [[ $transcodeVideo -eq 0 ]]; then echo "Video will be copied without transcoding." params="$params copy" fi params="$params -c:a" if [[ $transcodeAudio -eq 1 ]]; then echo "Audio will be transcoded." params="$params libopus" elif [[ $transcodeAudio -eq 0 ]]; then echo "Audio will be copied without transcoding." params="$params copy" fi if [[ $volumeMultiplier -ne 1 ]]; then echo "Volume will be multiplied by a factor of $volumeMultiplier." params="$params -filter:a 'volume=$volumeMultiplier'" else echo "Volume will be unaffected." fi if [[ $degradeToStereo -eq 1 ]]; then echo "Audio will be degraded to stereo." params="$params -ac 2" elif [[ $degradeToStereo -eq 0 ]]; then echo "Audio will not be degraded to stereo." fi params="$params -c:s copy" if [[ $subSource -eq 1 ]]; then echo "Subtitles source is external." echo "Subtitles language is $subLanguage." params="$params -metadata:s:s:0 title='' -metadata:s:s:0 language='$subLanguage'" else echo "Subtitles source is internal." fi if [[ -f titles.txt ]]; then echo "A titles.txt file was found. Titles will be changed according to it." echo "Please check titles.txt to make sure the titles are correct." changeTitles=1 counter=0 else echo "A titles.txt file was not found. Titles will not be changed." changeTitles=0 fi read -p "Are these options correct? (y/n) " choice case "$choice" in y|Y) echo "Initiating conversion sequence. This may take a while..." mkdir output currentParams="" shopt -s nullglob for i in *.mp4 *.mkv; do currentParams=$params fileNameNoExtension=$(echo $i | rev | cut -f 2- -d '.' | rev) if [[ $subSource -eq 1 ]]; then currentParams="-f srt -i $fileNameNoExtension.srt $currentParams" fi if [[ $changeTitles -eq 1 ]]; then ((counter++)) currentParams="$currentParams -metadata title='$(awk "NR==$counter" titles.txt)'" fi ffmpeg -i "$i" $currentParams "output/$fileNameNoExtension.mkv" done echo "Conversion finished!" ;; n|N) echo "Operation canceled. Exiting.";; *) echo "Invalid input. Try again.";; esac
运行目录下的视频文件
E1 - The Pirates of Orion.mkvE2 - Bem.mkvE3 - The Practical Joker.mkvE4 - Albatross.mkvE5 - How Sharper Than a Serpent's Tooth.mkvE6 - The Counter-Clock Incident.mkv
titles.txt内容
Star Trek: The Animated Series - Season 2, Episode 1 - The Pirates of Orion Star Trek: The Animated Series - Season 2, Episode 2 - Bem Star Trek: The Animated Series - Season 2, Episode 3 - The Practical Joker Star Trek: The Animated Series - Season 2, Episode 4 - Albatross Star Trek: The Animated Series - Season 2, Episode 5 - How Sharper Than a Serpent's Tooth Star Trek: The Animated Series - Season 2, Episode 6 - The Counter-Clock Incident
问题原因与解决方案
原因
脚本里用字符串拼接的方式组装FFmpeg参数,当标题里包含空格、冒号这类特殊字符时,Bash会自动做单词拆分,把标题拆成多个独立参数。比如Star Trek: ...会被拆成Star、Trek:、...,FFmpeg就会把Trek:误认为是输出格式/文件,导致报错。而手动执行时你用引号包裹了整个标题,所以没问题,但脚本里的变量展开时没有正确保留参数边界。
解决方案
改用数组存储FFmpeg参数,数组能完美保留每个参数的边界,不会因特殊字符拆分参数。下面是修改后的完整脚本:
#!/bin/bash audioTrack=1 subSource=1 subTrack=0 transcodeVideo=1 transcodeAudio=1 volumeMultiplier=1 degradeToStereo=0 subLanguage="Japanese" while getopts "t:ns:vam:dl:h" opt; do case "$opt" in t) audioTrack=${OPTARG};; n) subSource=0;; s) subTrack=${OPTARG};; v) transcodeVideo=0;; a) transcodeAudio=0;; m) volumeMultiplier=${OPTARG};; d) degradeToStereo=1;; l) subLanguage=${OPTARG};; h) echo "Options:" echo "-t [integer]: Audio track number. Default: 1." echo "-n: If included, subtitles will be taken from internal source." echo "-s [integer]: Subtitles track number. Default: 0." echo "-v: If included, video source will be copied without transcoding." echo "-a: If included, audio source will be copied without transcoding." echo "-m [number]: Volume multiplier. If 1, volume is unaffected. Default: 1" echo "-d: If included, audio will be degraded to stereo." echo "-l [language]: Subtitles language. Only used for external subtitles source. Default: Japanese." exit 0 ;; esac done echo "Audio track: $audioTrack." echo "Subtitles track: $subTrack." # 用数组存储参数,每个元素是一个独立参数 params=(-map 0:0 -map 0:"$audioTrack" -map "$subSource":"$subTrack" -c:v) if [[ $transcodeVideo -eq 1 ]]; then echo "Video will be transcoded." params+=("hevc") elif [[ $transcodeVideo -eq 0 ]]; then echo "Video will be copied without transcoding." params+=("copy") fi params+=("-c:a") if [[ $transcodeAudio -eq 1 ]]; then echo "Audio will be transcoded." params+=("libopus") elif [[ $transcodeAudio -eq 0 ]]; then echo "Audio will be copied without transcoding." params+=("copy") fi if [[ $volumeMultiplier -ne 1 ]]; then echo "Volume will be multiplied by a factor of $volumeMultiplier." params+=(-filter:a "volume=$volumeMultiplier") else echo "Volume will be unaffected." fi if [[ $degradeToStereo -eq 1 ]]; then echo "Audio will be degraded to stereo." params+=("-ac" "2") elif [[ $degradeToStereo -eq 0 ]]; then echo "Audio will not be degraded to stereo." fi params+=("-c:s" "copy") if [[ $subSource -eq 1 ]]; then echo "Subtitles source is external." echo "Subtitles language is $subLanguage." params+=(-metadata:s:s:0 title="" -metadata:s:s:0 language="$subLanguage") else echo "Subtitles source is internal." fi if [[ -f titles.txt ]]; then echo "A titles.txt file was found. Titles will be changed according to it." echo "Please check titles.txt to make sure the titles are correct." changeTitles=1 counter=0 else echo "A titles.txt file was not found. Titles will not be changed." changeTitles=0 fi read -p "Are these options correct? (y/n) " choice case "$choice" in y|Y) echo "Initiating conversion sequence. This may take a while..." mkdir -p output shopt -s nullglob for i in *.mp4 *.mkv; do # 复制参数数组 currentParams=("${params[@]}") fileNameNoExtension=$(echo "$i" | rev | cut -f 2- -d '.' | rev) if [[ $subSource -eq 1 ]]; then # 把外部字幕参数加到数组开头 currentParams=(-f srt -i "$fileNameNoExtension.srt" "${currentParams[@]}") fi if [[ $changeTitles -eq 1 ]]; then ((counter++)) title=$(awk "NR==$counter" titles.txt) currentParams+=(-metadata "title=$title") fi # 用数组展开方式传递参数,确保每个参数边界正确 ffmpeg -i "$i" "${currentParams[@]}" "output/$fileNameNoExtension.mkv" done echo "Conversion finished!" ;; n|N) echo "Operation canceled. Exiting.";; *) echo "Invalid input. Try again.";; esac
内容的提问来源于stack exchange,提问作者GaboScharff99
相关产品推荐
相关产品推荐

