我对 Linux 还很陌生,并且一直在努力解决这个问题。请帮助我解决这个问题。
我试图像批处理一样多次合并 2 个视频(每个文件夹 1 个),在下一个视频后自动合并 1 个视频。
我正在尝试使用 和ffmpeg
来for 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/a
and/tmp/b
代替/path/folder-1
and /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##*/}"
生成的输出文件将位于.mkv
matroska 容器中,而不是.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 }