ffmpeg在for循環中合併多組2個影片
我對 linux 還是很陌生,並且一直在努力做到這一點。請幫助我解決這個問題。
我正在嘗試像批處理一樣多次合併 2 個影片(每個文件夾中的 1 個),下一個之後自動設置 1 個。
我正在嘗試這樣做
ffmpeg
,for loop
以便從文件夾 1 列表的頂部獲取一個文件並將其與文件夾 2 列表頂部的一個文件合併,然後在文件夾列表中一直重複該過程,直到所有影片都已相互配對。想像一下,圖片 2 個文件夾與每個文件夾並排放置,現在將左側文件夾中的文件排列到右側文件夾。我想多次將 2 個影片合併為 1 個。我打算畫一張圖,但我認為它可以理解,希望如此。
這是我的程式碼,我已經更改了很多次,但我最新的一個是:(我試圖在文件夾 1 的目錄中執行它,希望它會讀取文件並從文件夾 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 命令等。非常感謝您寶貴的時間,非常感謝!
在您的第一個範例中,
${filename%}
根本不更改 $filename ,並且當您告訴 ffmpeg 使用 concat demuxer 打開 .mp4 文件時-f concat
,錯誤消息應該是<actual name of $filename>: Invalid data found when processing input
,但是您收到No such file or directory
了,所以我懷疑 glob 沒有工作 -也許您的工作目錄實際上不是文件夾 1,在這種情況下,來自 ffmpeg 的完整“找不到文件”錯誤消息將是*.mp4: No such file or directory
- glob 與任何文件都不匹配,因此參數filename
設置為<literal asterisk><dot>mp4
.在您的第二個範例中,參數替換
${filename%.*}
是從您提供給 ffmpeg 的名稱末尾刪除 .mp4 - 可能是您收到 .mp4 的原因No such file or directory
。此外,在您的兩個範例中,ffmpeg 的concat demuxer的使用都是不正確的。concat demuxer 需要一個文本文件作為其輸入(或以下範例中使用的適當的 shell 替換
<()
)。在您的範例中,輸入文件是直接指定的,如果您想將所有流混合到單個容器中,您可能會這樣做 - 流將是“並行”的(例如,添加字幕流或輔助音軌) . 連接將按順序加入文件。如果您想要將文件夾 1 中的 .mp4 文件與文件夾 2 中的另一個 .mp4 文件連接起來,其中文件名匹配…這是我測試過的範例,使用絕對路徑 -
/tmp/a
並/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/- date .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 }