ffmpeg在for循环中合并多组2个视频

ffmpeg在for循环中合并多组2个视频

我对 Linux 还很陌生,并且一直在努力解决这个问题。请帮助我解决这个问题。

我试图像批处理一样多次合并 2 个视频(每个文件夹 1 个),在下一个视频后自动合并 1 个视频。

我正在尝试使用 和ffmpegfor loop执行此操作,以便从文件夹 1 的列表顶部获取一个文件,并将其与文件夹 2 的列表顶部的一个文件合并,然后在文件夹列表中一直重复该过程,直到所有视频均已相互配对。

想象一下,将 2 个文件夹并排放置在每个文件夹中,然后将文件从左侧的文件夹排列到右侧的文件夹。我想多次将 2 个视频合并为 1 个。我本来打算画一个图表,但我想它可以理解,希望如此。

这是我的代码,我已经更改了很多次,但我最新的代码是:(我尝试在folder-1的目录中运行它,希望它能读取文件并从folder-2加入1到1,但是遗憾的是没有运气。

for filename in *.mp4; do
    folder2="/path/to/folder2"
    xout="/output/file/as/$(date +'%Y-%m-%d_%H:%M:%S').mp4"
    ffmpeg -f concat -i "${filename%}" -i "$vid2/${filename%}" -acodec copy -vcodec copy "$xout"
done

这是另一个给我带来同样错误的尝试。 No such file or directory

for filename in *.mp4; do
    vid1="/path/folder-1/${filename%.*}"
    vid2="path/folder-2/${filename%.*}"
    out1="/path/output/$(date +'%Y-%m-%d_%H:%M:%S').mp4"
    ffmpeg -f concat -i "$vid1" -i "$vid2" -acodec copy -vcodec copy "$out1"
done

任何人都可以请告诉我我做错了什么,我无法做到这一点,已经过去了大约 4 个小时,我已经尝试了很多事情并阅读了很多有关 for 循环的文章,而循环、ffmpeg 命令等。非常感谢您花费宝贵的时间,非常感谢!

答案1

在您的第一个示例中,${filename%}根本不更改 $filename ,并且当您告诉 ffmpeg 使用 concat demuxer 打开 .mp4 文件时-f concat,错误消息应该是<actual name of $filename>: Invalid data found when processing input,但您收到了No such file or directory,所以我怀疑 glob 不起作用 -也许你的工作目录实际上并不是folder-1,在这种情况下,来自 ffmpeg 的完整“文件未找到”错误消息将是*.mp4: No such file or directory- glob 与任何文件都不匹配,因此参数filename设置为<literal asterisk><dot>mp4.

在您的第二个示例中,参数替换${filename%.*}从您提供给 ffmpeg 的名称末尾删除了 .mp4 - 这可能是您收到的原因No such file or directory

另外,在你的两个例子中 ffmpeg 的使用连接解复用器是不正确的。 concat 解复用器需要一个文本文件作为其输入(或使用适当的 shell 替换,如下面的示例所示<())。在您的示例中,直接指定输入文件,如果您想将所有流混合到单个容器中,您可能会这样做 - 流将处于“并行”状态(例如,添加字幕流或辅助音轨) 。连接将按顺序连接文件。


如果您想要的是将文件夹 1 中的 .mp4 文件与文件夹 2 中的另一个 .mp4 文件连接,其中文件名匹配...这是我已经测试过的示例,使用绝对路径 - 使用/tmp/aand/tmp/b代替/path/folder-1and /path/folder-2/tmp作为/path/output

seconddirectory="/tmp/b"

for i in /tmp/a/*.mp4
do
    if ! [[ -e "$seconddirectory/${i##*/}" ]]
    then
        >&2 echo "no matching file in $seconddirectory for $i"
        continue
    fi
    out="${i##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '$i'" "inpoint 0" "file '$seconddirectory/${i##*/}'" "inpoint 0") -c copy "$out.mp4"
done

这会将 /tmp/a 中/tmp/b 中存在匹配文件名的所有 .mp4 文件输出到 /tmp/*-日期.mp4,连接匹配的文件。 (注意:不要仅使用输出的日期,因为它会导致文件名冲突 - 使用唯一的名称来避免这种情况 - 在本例中使用输入文件的基本名称)。 ${i##*/} 替换是从绝对路径中删除路径组件,只留下文件名组件 - 使用绝对路径意味着当前工作目录不会干扰*全局匹配。

如果您想加入文件名不匹配的文件,则需要采取不同的措施。例如。匹配每个文件夹中的第一个文件,然后是第二个文件,依此类推(按照 bash glob 对它们进行排序的顺序):

a=(/tmp/a/*.mp4)
b=(/tmp/b/*.mp4)
a=("${a[@]:0:${#b[@]}}")
b=("${b[@]:0:${#a[@]}}")
for (( i=0; i<${#a[@]}; i++ ))
do
    out="${a[i]##*/}"
    out="${out%.*}-${b[i]##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '${a[i]}'" "inpoint 0" "file '${b[i]}'" "inpoint 0") -c copy "$out.mp4"
done

这使用数组变量,结合 globbing 在每个目录中构建 .mp4 文件列表,并将连接一对(每个列表中的一个),直到不再存在对。

<()您可以匹配输入文件对并将它们以 ffmpeg 所需的格式写入文本文件,然后处理该文件,而不是过程替换。例如。对于第一个例子,它看起来像:

seconddirectory="/tmp/b"

for i in /tmp/a/*.mp4
do
    if ! [[ -e "$seconddirectory/${i##*/}" ]]
    then
        >&2 echo "no matching file in $seconddirectory for $i"
        continue
    fi
    out="${i##*/}"
    out="/tmp/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
    printf '%s\n' "file '$i'" "inpoint 0" "file '$seconddirectory/${i##*/}'" "inpoint 0" > "$out.ffcat"
done

for i in /tmp/*.ffcat
do
    ffmpeg -f concat -safe 0 -i "$i" -c copy "${i/%.ffcat/.mp4}"
done

完成这项工作的 ffmpeg 的替代方案是mkvmerge(来自 mkvtoolnix)。它提供了一种无需文本文件作为输入即可连接文件的方法。在上面的第一个示例中,整ffmpeg行可以替换为:

mkvmerge -o "$out.mkv" "$i" + "$seconddirectory/${i##*/}"

生成的输出文件将位于.mkvmatroska 容器中,而不是.mp4上面 ffmpeg 示例中使用的容器。


将所有这些放在一个可重用的函数中:

function concatenation_example() {
    local a b c i out mf
    if type mkvmerge >/dev/null
    then
        mf=m
    elif type ffmpeg >/dev/null
    then
        mf=f
    else
        >&2 echo "This function won't work without either mkvmerge or ffmpeg installed."
        return 1
    fi
    if [[ ! -d "$1" || ! -d "$2" || ! -d "$3" ]]
    then
        >&2 printf '%s\n' "concatenation_example FIRSTDIR SECONDDIR OUTDIR" "all arguments must be directories"
        return 1
    fi
    for i in "$1"/*.mp4
    do
        if ! [[ -e "$2/${i##*/}" ]]
        then
            >&2 echo "no matching file in $2 for $i"
            continue
        fi
        out="${i##*/}"
        out="$3/${out%.*}-$(date +'%Y-%m-%d_%H:%M:%S')"
        case "$mf" in
            (m) mkvmerge -o "$out.mkv" "$i" + "$2/${i##*/}" ;;
            (f) ffmpeg -f concat -safe 0 -i <(printf '%s\n' "file '$i'" "inpoint 0" "file '$2/${i##*/}'" "inpoint 0") -c copy "$out.mp4" ;;
        esac
    done
}

相关内容