You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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.mkv
  • E2 - Bem.mkv
  • E3 - The Practical Joker.mkv
  • E4 - Albatross.mkv
  • E5 - How Sharper Than a Serpent's Tooth.mkv
  • E6 - 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 05:14:54