Bash

ffmpeg在for循環中合併多組2個影片

  • January 26, 2020

我對 linux 還是很陌生,並且一直在努力做到這一點。請幫助我解決這個問題。

我正在嘗試像批處理一樣多次合併 2 個影片(每個文件夾中的 1 個),下一個之後自動設置 1 個。

我正在嘗試這樣做ffmpegfor 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-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/- 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##*/}"

生成的輸出文件將位於.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
}

引用自:https://unix.stackexchange.com/questions/564114