【问题标题】:How to concatenate two MP4 files using FFmpeg?如何使用 FFmpeg 连接两个 MP4 文件?
【发布时间】:2011-11-12 02:11:31
【问题描述】:

我正在尝试使用 ffmpeg 连接两个 mp4 文件。我需要这是一个自动过程,因此我选择了 ffmpeg。我将这两个文件转换为.ts 文件,然后将它们连接起来,然后尝试对连接后的.ts 文件进行编码。这些文件是h264aac 编码的,我希望保持质量相同或尽可能接近原始文件。

ffmpeg -i part1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.ts
ffmpeg -i part2.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part2.ts
cat part1.ts part2.ts > parts.ts
ffmpeg -y -i parts.ts -acodec copy -ar 44100 -ab 96k -coder ac -vbsf h264_mp4toannexb parts.mp4

不幸的是,我在编码期间从 ffmpeg 返回以下错误消息:

[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[h264 @ 0x1012600]sps_id out of range
[h264 @ 0x1012600]non-existing SPS 0 referenced in buffering period
[NULL @ 0x101d600]error, non monotone timestamps 13779431 >= 13779431kbits/s    
av_interleaved_write_frame(): Error while opening file

这发生在编码的一半左右,这让我觉得你不能将两个 .ts 文件连接在一起并让它工作。

【问题讨论】:

    标签: ffmpeg h.264 mp4


    【解决方案1】:

    对于那些需要连接多个用 H.264 编码的 MP4 视频的人,我建议使用 Python 脚本 mp4concat.py 自动执行 ffmpeg @987654322 中的 Concat 协议/使用中间文件 段落@。

    从上面的链接下载脚本或使用

    wget https://gist.githubusercontent.com/mwouts/115956d3fc7ece55cbce483a7a5148bd/raw/4bc5e03952f1b981dac3f97f4daf82c907774b17/mp4concat.py
    

    然后把它当作

    python3 mp4concat.py input1.mp4 ... inputN.mp4 --output output.mp4
    

    【讨论】:

      【解决方案2】:

      如果您更喜欢method #2 from rogerdpack's answer 但不想使用管道(例如,您只想在C 中使用execv)或者不想创建额外的文件(list.txt),那么只需组合@ 987654323@ 带有datafile 协议的解复用器,即FFmpeg 允许您几乎像在HTML 中一样内联输入文件:

      <img src="data:image/png;base64,..." alt="" />
      
      ffmpeg -i 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB4AAAAQ4AQAAAADAqPzuAAABEklEQVR4Ae3BAQ0AAADCIPunfg8HDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4FT45QABPFL5RwAAAABJRU5ErkJggg==' /tmp/blackbg.mp4
      

      下面是我的程序(放在/usr/bin/ffconcat),它自动内联“包含文件路径列表的文件”。此外,与所有其他答案不同,您可以使用任何 FFmpeg 选项。

      如果您使用的不是 bash 语言(C、Node.js),那么只需查看usage () 和最后一行。

      #!/bin/bash
      # ffconcat v0.3
      # @author Arzet Ro, 2021 <arzeth0@gmail.com>
      # @license CC-0 (Public Domain)
      
      function usage ()
      {
          echo "\
      ffconcat's usage:
          ffconcat (anyFfmpegInputOptions) -i /tmp/a.mp4 -i ... -i ... /tmp/out.mp4 (anyFfmpegOutputOptions)
          ffconcat -vn /tmp/a.mp4 /tmp/b.opus /tmp/out.mp4 -y
          ffconcat -http -i https://a/h264@720p@25fps+opus.mp4 -i ftp://127.0.0.1/h264@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mkv
          ffconcat -http -i https://a/vp9@1080p@30fps+opus.mp4 -i ftp://127.0.0.1/vp9@720p@30fps+opus.mp4 -i /tmp/c.opus /tmp/out.mp4
          WARNING: ffconcat uses `concat` demuxer; when
          using both this demuxer AND -y, FFmpeg doesn't check if
          an input file and output file
          are the same file, so your 100 GB input file
          could immediately become 10 KB.
          ffconcat checks that only when neither -i
          nor new FFmpeg release's boolean args (see valuelessFfmpegArgs in the code)
          are specified.
      
          ffmpeg has no -http.
          ffconcat has -http because ffmpeg automatically
          sets allowed protocols depending on -f and -i.
          But when -f concat, ffmpeg doesn't know what to do with -i.
      
          ffmpeg and mpv support VP9+Opus in .mp4
          Only one video codec is possible in an output file.
          You can't have both AAC and Opus in one .mp4 (not sure about other containers).
          If you combine VP9 videos, then make sure they have the same FPS.
          If you combine H264 videos of different resolutions,
          then you get A/V desync
          and also either
          1) the start of video streams after the first video stream are cut
          2) or video player freezes for 5 seconds when switching between video streams.
          Also it seems if DAR (display aspect ratio) differs (at least in H.264)
          then incorrect (5x longer) duration is estimated
          and mpv displays the second video with 1 FPS.
          You can see the info about an input file
          with
          mediainfo file.mp4
          or
          ffprobe -hide_banner -protocol_whitelist file,rtp,udp -show_streams file.mp4"
      }
      
      # Configuration [begin]
      forceRequireIArgumentForInputFiles=0
      # Configuration [end]
      
      
      
      
      in_array ()
      {
          local e match="$1"
          shift
          for e; do [[ "$e" == "$match" ]] && return 0; done
          return 1
      }
      
      if [[ "$#" == 0 ]]
      then
          usage
          exit
      fi
      
      requireIArgumentForInputFiles=0
      if in_array "--help" "$@"
      then
          usage
          exit
      elif in_array "-help" "$@"
      then
          usage
          exit
      elif in_array "-i" "$@"
      then
          requireIArgumentForInputFiles=1
      elif [[ "$forceRequireIArgumentForInputFiles" == "1" ]]
      then
          >&2 echo "forceRequireIArgumentForInputFiles=1, so you need -i"
          usage
          exit 1
      fi
      
      
      
      
      NL=$'\n'
      inputOptions=()
      outputOptions=()
      inputFilesRawArray=()
      outputFile=""
      
      declare -a valuelessFfmpegArgs=("-http"     "-hide_banner" "-dn" "-n" "-y" "-vn" "-an" "-autorotate" "-noautorotate" "-autoscale" "-noautoscale" "-stats" "-nostats" "-stdin" "-nostdin" "-ilme" "-vstats" "-psnr" "-qphist" "-hwaccels" "-sn" "-fix_sub_duration" "-ignore_unknown" "-copy_unknown" "-benchmark" "-benchmark_all" "-dump" "-hex" "-re" "-copyts" "-start_at_zero" "-shortest" "-accurate_seek" "-noaccurate_seek" "-seek_timestamp"     "write_id3v2" "write_apetag" "write_mpeg2" "ignore_loop" "skip_rate_check" "no_resync_search" "export_xmp")
      #^ used when requireIArgumentForInputFiles=0
      # TODO: fill all the args
      # grep -C 3 AV_OPT_TYPE_BOOL libavformat/ libavcodec/
      # grep -C 3 OPT_BOOL fftools/
      # Unfortunately, unlike MPV, FFmpeg neither
      # requires nor supports `=`, i.e. `--y --i=file.mp4'
      # instead of `-y -i file.mp4`.
      # Which means it's unclear whether an argument
      # is a value of an argument or an input/output file.
      
      areFfmpegArgsAllowed=1
      isHttpMode=0
      
      if in_array "-http" "$@"
      then
          isHttpMode=1
      fi
      
      
      # if an argument is not a boolean argument, then what key needs a value
      secondArgumentIsWantedByThisFirstArgument=""
      # if requireIArgumentForInputFiles=1
      # then secondArgumentIsWantedByThisFirstArgument must be either "" or "-i"
      isCurrentArgumentI=0
      localRawFilesArray=()
      outputFile=""
      for arg in "$@"
      do
          if [[
              "$secondArgumentIsWantedByThisFirstArgument" == ""
              &&
              "$arg" == "-http"
          ]]
          then
              continue
          fi
          if [[ "$arg" == "--" ]]
          then
              areFfmpegArgsAllowed=0
              continue
          fi
          if [[
              (
                  "$areFfmpegArgsAllowed" == "1"
                  ||
                  "$secondArgumentIsWantedByThisFirstArgument" != ""
              )
              && !(
                  "$requireIArgumentForInputFiles" == "1"
                  &&
                  "$secondArgumentIsWantedByThisFirstArgument" == "-i"
              )
              &&
              (
                  "$secondArgumentIsWantedByThisFirstArgument" != ""
                  ||
                  (
                      "$requireIArgumentForInputFiles" == "0"
                      &&
                      "$arg" = -*
                  )
                  ||
                  (
                      "$requireIArgumentForInputFiles" == "1"
                  )
              )
          ]]
          then
              if [[ !(
                  "$requireIArgumentForInputFiles" == "1"
                  &&
                  "$arg" == "-i"
              ) ]]
              then
                  if (( ${#inputFilesRawArray[@]} == 0 ))
                  then
                      inputOptions+=("$arg")
                  else
                      outputOptions+=("$arg")
                  fi
              fi
          elif [[
              "$requireIArgumentForInputFiles" == "0"
              ||
              "$secondArgumentIsWantedByThisFirstArgument" == "-i"
          ]]
          then
              if echo -n "$arg" | egrep '^(https?|ftp)://'
              then
                  inputFilesRawArray+=("$arg")
                  localRawFilesArray+=("$arg")
              else
                  tmp=`echo -n "$arg" | sed 's@^file:@@'`
                  localRawFilesArray+=("$tmp")
                  if [[ "$secondArgumentIsWantedByThisFirstArgument" == "-i" ]]
                  then
                      if ! ls -1d -- "$tmp" >/dev/null 2>/dev/null
                      then
                          >&2 echo "Input file '$tmp' not found"
                          exit 1
                      fi
                  fi
                  tmp=`echo -n "$tmp" | sed -E 's@(\s|\\\\)@\\\\\1@g' | sed "s@'@\\\\\'@g"`
                  # ^ FIXME: does it work for all filenames?
                  inputFilesRawArray+=("file:$tmp")
              fi
          elif [[
              "$requireIArgumentForInputFiles" == "1"
              &&
              "$secondArgumentIsWantedByThisFirstArgument" != "-i"
          ]]
          then
              if echo -n "$arg" | egrep '^(https?|ftp)://'
              then
                  outputFile="$arg"
              else
                  outputFile=`echo -n "$arg" | sed 's@^file:@@'`
                  outputFile="file:$outputFile"
              fi
          else
              usage
              exit 1
          fi
          if [[
              "$secondArgumentIsWantedByThisFirstArgument" != ""
              ||
              "$areFfmpegArgsAllowed" == "0"
          ]]
          then
              secondArgumentIsWantedByThisFirstArgument=""
          else
              if [[ "$requireIArgumentForInputFiles" == "1" && "$arg" == "-i" ]]
              then
                  secondArgumentIsWantedByThisFirstArgument="$arg"
              elif [[ "$requireIArgumentForInputFiles" == "0" && "$arg" = -* ]]
              then
                  if ! in_array "$arg" ${valuelessFfmpegArgs[@]}
                  then
                      secondArgumentIsWantedByThisFirstArgument="$arg"
                  fi
              fi
          fi
      done
      if [[
          "$requireIArgumentForInputFiles" == "0"
          &&
          "$outputFile" == ""
      ]]
      then
          outputFile="${localRawFilesArray[((${#localRawFilesArray[@]}-1))]}"
      fi
      actualOutputFile="$outputFile"
      if [[ "$requireIArgumentForInputFiles" == "0" || "file:" =~ ^"$outputFile"* ]]
      then
          actualOutputFile=`echo -n "$actualOutputFile" | sed 's@^file:@@'`
          actualOutputFile=`readlink -nf -- "$actualOutputFile"`
      fi
      
      if [[ "$requireIArgumentForInputFiles" == "0" ]]
      then
          unset 'inputFilesRawArray[((${#inputFilesRawArray[@]}-1))]'
          unset 'localRawFilesArray[((${#localRawFilesArray[@]}-1))]'
          outputOptions+=("$outputFile")
      fi
      
      #>&2 echo Input: ${inputFilesRawArray[@]}
      #if [[ "$requireIArgumentForInputFiles" == "0" ]]
      #then
      #   >&2 echo Output: $outputFile
      #fi
      
      
      if (( ${#inputFilesRawArray[@]} < 2 ))
      then
          >&2 echo "Error: Minimum 2 input files required, ${#inputFilesRawArray[@]} given."
          >&2 echo Input: ${inputFilesRawArray[@]}
          if [[ "$requireIArgumentForInputFiles" == "0" ]]
          then
              >&2 echo Output: $outputFile
          fi
          usage
          #outputFile=/dev/null
          exit 1
      fi
      if [[
          "$requireIArgumentForInputFiles" == "0"
          &&
          "$outputFile" == ""
      ]]
      then
          >&2 echo "Error: No output file specified."
          usage
          exit 1
      fi
      
      
      ffmpegInputList=""
      firstFileDone=0
      inputFilesRawArrayLength=${#inputFilesRawArray[@]}
      
      for (( i = 0; i < inputFilesRawArrayLength; i++ ))
      do
          lf="${localRawFilesArray[$i]}"
          f="${inputFilesRawArray[$i]}"
          if [[ "${inputFilesRawArray[$i]}" =~ ^file: ]]
          then
              actualF=`readlink -nf -- "$lf"`
              if [[ "$actualF" == "$actualOutputFile" ]]
              then
                  >&2 echo "Error: The same file '$actualF' is used both as an input file and an output file"
                  exit 1
              fi
          fi
          if [[ "$firstFileDone" == "1" ]]
          then
              ffmpegInputList+="$NL"
          fi
          ffmpegInputList+="file $f"
          firstFileDone=1
      done
      
      protocol_whitelist_appendage=""
      if [[ "$isHttpMode" == "1" ]]
      then
          protocol_whitelist_appendage=",ftp,http,https"
      fi
      
      
      # Also print the final line:
      set -x
      
      ffmpeg \
      -safe 0 \
      -f concat \
      -protocol_whitelist data,file"$protocol_whitelist_appendage" \
      "${inputOptions[@]}" \
      -i "data:text/plain;charset=UTF-8,${ffmpegInputList}" \
      -c copy \
      "${outputOptions[@]}"
      # $ffmpegInputList is
      # file file:./test.mp4\nfile file:/home/aaa.mp4\nfile http://a/b.aac
      # All whitespace and ' in ffmpegInputList are escaped with `\`.
      

      与 HTML 不同,-i 不需要百分比编码(JavaScript 的 encodeURI/encodeURIComponent)(%20 等)。

      【讨论】:

        【解决方案3】:

        对于 Windows Powershell

        创建文件列表

        PS > ls file* | % { $n = $_.name; "file '\$n'" } | out-file mylist.txt
        

        检查创建的文件

        PS > cat mylist.txt
        file '/path/to/file1.mkv'
        file '/path/to/file2.mkv'
        file '/path/to/file3.mkv'
        

        运行 ffmpeg(不要忘记为列表文件附加./

        PS > ffmpeg -safe 0 -f concat -i ./mylist.txt -c copy file.mkv
        ffmpeg version git-2020-05-15-b18fd2b Copyright (c) 2000-2020 the FFmpeg developers
        ...
        
        

        【讨论】:

          【解决方案4】:

          我有不同编码器 libx264H.264 的剪辑

          我将所有剪辑转换为libx264,并使用了投票最多的答案中的解复用器方法。

          ffmpeg -i input.flv -vcodec libx264 output.mp4
          

          【讨论】:

            【解决方案5】:

            迟到的答案,但这是唯一对我有用的选项:

            (echo file '1.mp4' &amp; echo file '2.mp4' &amp; echo file '3.mp4' &amp; echo file '4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "1234.mp4"

            【讨论】:

            • 这对我不起作用了。当我尝试管道时,我得到moov atom not found;Impossible to open 'pipe:file_01.mp4' pipe:: Invalid data found when processing input。将所有内容保存到 input.text 并使用 -i input.text 运行。
            • @emk2203 file_01.mp4damaged
            • 我可以尝试任何具有相同结果的 mp4 文件,但是当我使用 ffmpeg -f concat -safe 0 -i input.text -c copy output.mp4 并且它们的名称在“file 'file_01.mp4'` 语法中的 input.text 中时它们可以完美运行 - 的和我使用管道输出时一样。
            • 不,绝对不是。同时,我在 bug tracker 上找到了答案。长答案在askubuntu.com/a/1368547/332437。简短回答:当您在 ca 之后的 ffmpeg 版本中使用管道协议时,您需要使用 (echo file file:'1.mp4' &amp; echo file file:'2.mp4' &amp; echo file file:'3.mp4' &amp; echo file file:'4.mp4') | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy "1234.mp4"。 4.2.2.
            【解决方案6】:

            当我在接受的答案中使用选项 3 在 Mac 上连接多个 MP4 时,我发现管道运算符对我不起作用。

            以下单行在 bash(Mac、Linux)中工作,不需要中间文件:

            ffmpeg -f concat -safe 0 -i &lt;(for f in ./*.mp4; do echo "file '$PWD/$f'"; done) -c copy output.mp4

            这里,

            【讨论】:

              【解决方案7】:

              FFmpeg 有三种连接方式:

              1。 concat video filter

              如果您的输入没有相同的参数(宽度、高度等),或者格式/编解码器不同,或者您想要执行任何过滤,请使用此方法。

              请注意,此方法对所有输入进行重新编码。如果您想避免重新编码,您可以只重新编码不匹配的输入,以便它们共享相同的编解码器和其他参数,然后使用 concat demuxer 避免重新编码所有内容。

              ffmpeg -i opening.mkv -i episode.mkv -i ending.mkv \
              -filter_complex "[0:v] [0:a] [1:v] [1:a] [2:v] [2:a] \
              concat=n=3:v=1:a=1 [v] [a]" \
              -map "[v]" -map "[a]" output.mkv
              

              2。 concat demuxer

              当您想避免重新编码并且您的格式不支持文件级串联(一般用户使用的大多数文件不支持文件级串联)时,请使用此方法。

              $ cat mylist.txt
              file '/path/to/file1'
              file '/path/to/file2'
              file '/path/to/file3'
                  
              $ ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4
              

              对于 Windows

              (echo file 'first file.mp4' & echo file 'second file.mp4' )>list.txt
              ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
              

              3。 concat protocol

              将此方法用于支持文件级连接的格式 (MPEG-1、MPEG-2 PS、DV)。 不要与 MP4 一起使用。

              ffmpeg -i "concat:input1|input2" -codec copy output.mkv
              

              由于这些格式的性质以及此方法执行的简单连接,此方法不适用于包括 MP4 在内的许多格式。


              如果对使用哪种方法有疑问,请尝试 concat demuxer。

              另见

              【讨论】:

              • Windows 命令提示符转义字符为 ^;我也遇到了引号问题,所以它变成了: ffmpeg -i concat:input1^|input2 -codec copy output
              • 有用的答案。当我读到这个变化时,它对我来说更有意义:superuser.com/a/607384/74576
              • concat demuxer 适用于视频,但我正在丢失音频
              • 单线使用:ffmpeg -safe 0 -f concat -i &lt;(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv(mkv 接受的编解码器比 mp4 多,但您也可以尝试使用 mp4)。 -safe 0 用于抱怨 不安全文件名 的最新 ffmpeg 版本,-type f 仅用于列出文件。我添加了| sort 按字母顺序对文件进行排序;因为find 按保存在文件系统中的顺序读取它们。也适用于带有空格的文件。
              • 对我来说,在 Windows 中,双引号有效(而不是您的示例中的单引号):ffmpeg -i "concat:input1|input2" -codec copy output。无需转义管道字符。
              【解决方案8】:

              这是一个脚本(适用于任意数量的指定文件(不只是工作目录中的所有文件),无需额外文件,也适用于 .mov;在 macOS 上测试):

              #!/bin/bash
              
              if [ $# -lt 1 ]; then
                  echo "Usage: `basename $0` input_1.mp4 input_2.mp4 ... output.mp4"
                  exit 0
              fi
              
              ARGS=("$@") # determine all arguments
              output=${ARGS[${#ARGS[@]}-1]} # get the last argument (output file)
              unset ARGS[${#ARGS[@]}-1] # drop it from the array
              (for f in "${ARGS[@]}"; do echo "file '$f'"; done) | ffmpeg -protocol_whitelist file,pipe -f concat -safe 0 -i pipe: -vcodec copy -acodec copy $output
              

              【讨论】:

              • 对于更新的ffmpeg 版本比ca。 4.2.2,您需要将do echo "file '$f'" 更改为do echo "file file:'$f'" 才能使脚本正常工作。此处解释:askubuntu.com/a/1368547/332437.
              【解决方案9】:

              我试图将三个 .mp3 音频文件连接到一个 .m4a 文件中,这个 ffmpeg 命令有效。

              输入指令:

              ffmpeg -i input1.mp3 -i input2.mp3 -i input3.mp3 \
                     -filter_complex "concat=n=3:v=0:a=1" -f MOV -vn -y input.m4a
              

              的含义: -filter_complex "concat=n=3:v=0:a=1" :

              concat 表示使用媒体连接(加入)功能。
              n 表示确认输入文件的总数。
              v 表示有视频?使用 0 = 无视频,1 = 包含视频。
              a 表示有音频?使用 0 = 无音频,1 = 包含音频。
              -f 表示强制设置文件格式(要查看所有支持的格式,请使用 ffmpeg -formats
              -vn 表示禁用视频(如果不需要,-an 也会禁用音频)
              -y 表示覆盖输出文件(如果输出文件已经存在)。

              更多信息:使用ffmpeg -h full 打印所有选项(包括所有格式和编解码器特定选项,很长)

              【讨论】:

              • 如果我使用这个-f concat -safe 0 -i "file.txt" -c copy -y "OutPut.mp4",它会连接,但是当一些视频有音频而一些没有音频时,它不起作用我该如何解决这个问题。谢谢——
              • 像魅力一样工作,只需将“v=0”更改为“v=1”,其余的都解决了!
              • 你让它听起来像 va 是布尔值;输出是否应包含视频/音频的“是”或“否”。不是这种情况。它们实际上是整数。 concat-filter 上的 FFmpeg 文档:“设置输出视频/音频流的数量,也就是每个段中的视频/音频流的数量”。即使在example 中,您也可以看到例如 a 设置为 2
              【解决方案10】:

              这是一种快速(不到 1 分钟)且无损的方法,无需中间文件:

              ls Movie_Part_1.mp4 Movie_Part_2.mp4 | \
              perl -ne 'print "file $_"' | \
              ffmpeg -f concat -i - -c copy Movie_Joined.mp4
              

              “ls”包含要加入的文件 “perl”将连接文件即时创建到管道中 “-i -”部分告诉 ffmpeg 从管道中读取

              (注意——我的文件中没有空格或奇怪的东西——如果你想用“硬”文件做这个想法,你需要适当的外壳转义)。

              【讨论】:

              • 对包含空格的文件进行适当的转义:ls Movie_Part_1.mp4 Movie_Part_2.mp4 | perl -ne '$_ =~ s/\n$//; print "file '"'"'$_'"'"'\n"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4
              • 如果您的文件命名良好且井井有条,您也可以使用ls * | perl -ne 'print "file $_"' | ffmpeg -f concat -i - -c copy Movie_Joined.mp4。在 OS X 上非常适合我,正是我想要的。
              • 在 Linux 上,我使用了这个 oneliner:ffmpeg -safe 0 -f concat -i &lt;(find . -type f -name '*' -printf "file '$PWD/%p'\n" | sort) -c copy output.mkv(mkv 接受的编解码器比 mp4 多,但您也可以尝试使用 mp4)。 -safe 0 用于抱怨 不安全文件名 的最新 ffmpeg 版本,-type f 仅用于列出文件。我添加了| sort 以按字母顺序对文件进行排序;因为find 按保存在文件系统中的顺序读取它们。也适用于带有空格的文件。
              • 在 Windows CMD 中我使用了这个:ls *mp4 | sed "s/\(*mp4\)/file \1/" | ffmpeg -f concat -i - -c:v copy output.mp4
              • 我必须将此参数添加到 ffmpeg 以使其工作:-protocol_whitelist file,tcp,http,pipe
              【解决方案11】:

              这里有 2 个纯 bash 解决方案,仅使用 ffmpeg 而未使用

              • 中间文件
              • perl、python 和 javascript

              使用ls的单线解决方案

              ls video1.mp4 video2.mp4 | while read line; do echo file \'$line\'; done | ffmpeg -protocol_whitelist file,pipe -f concat -i - -c copy output.mp4
              

              接受 0 或 2+ 个参数的函数

              #######################################
              # Merge mp4 files into one output mp4 file
              # usage:
              #   mergemp4 #merges all mp4 in current directory
              #   mergemp4 video1.mp4 video2.mp4
              #   mergemp4 video1.mp4 video2.mp4 [ video3.mp4 ...] output.mp4 
              #######################################
              function mergemp4() {
                if [ $# = 1 ]; then return; fi
              
                outputfile="output.mp4"
              
                #if no arguments we take all mp4 in current directory as array
                if [ $# = 0 ]; then inputfiles=($(ls -1v *.mp4)); fi
                if [ $# = 2 ]; then inputfiles=($1 $2); fi  
                if [ $# -ge 3 ]; then
                  outputfile=${@: -1} # Get the last argument
                  inputfiles=(${@:1:$# - 1}) # Get all arguments besides last one as array
                fi
                
                # -y: automatically overwrite output file if exists
                # -loglevel quiet: disable ffmpeg logs
                ffmpeg -y \
                -loglevel quiet \
                -f concat \
                -safe 0 \
                -i <(for f in $inputfiles; do echo "file '$PWD/$f'"; done) \
                -c copy $outputfile
              
                if test -f "$outputfile"; then echo "$outputfile created"; fi
              }
              

              注意:在这个帖子中尝试了一些解决方案,但没有一个让我满意

              【讨论】:

                【解决方案12】:

                以可重复使用的 PowerShell 脚本形式接受的答案

                Param(
                [string]$WildcardFilePath,
                [string]$OutFilePath
                )
                try
                {
                    $tempFile = [System.IO.Path]::GetTempFileName()
                    Get-ChildItem -path $wildcardFilePath | foreach  { "file '$_'" } | Out-File -FilePath $tempFile -Encoding ascii
                    ffmpeg.exe -safe 0 -f concat -i $tempFile -c copy $outFilePath
                }
                finally
                {
                    Remove-Item $tempFile
                }
                

                【讨论】:

                  【解决方案13】:

                  这对我有用(在 Windows 上)

                  ffmpeg -i "concat:input1|input2" -codec copy output
                  

                  一个例子...

                  ffmpeg -i "concat:01.mp4|02.mp4" -codec copy output.mp4
                  

                  Python

                  使用一些 python 代码来处理一个文件夹中的多个 mp4(从 python.org 安装 python,复制并粘贴并将此代码保存到一个名为 mp4.py 的文件中,然后从在带有 python mp4.py 的文件夹,并且文件夹中的所有 mp4 将被连接)

                  import glob
                  import os
                  
                  stringa = ""
                  for f in glob.glob("*.mp4"):
                      stringa += f + "|"
                  os.system("ffmpeg -i \"concat:" + stringa + "\" -codec copy output.mp4")
                  

                  Python 版本 2

                  取自我的post on my blog,这就是我在 python 中的做法:

                  import os
                  import glob
                  
                  def concatenate():
                      stringa = "ffmpeg -i \"concat:"
                      elenco_video = glob.glob("*.mp4")
                      elenco_file_temp = []
                      for f in elenco_video:
                          file = "temp" + str(elenco_video.index(f) + 1) + ".ts"
                          os.system("ffmpeg -i " + f + " -c copy -bsf:v h264_mp4toannexb -f mpegts " + file)
                          elenco_file_temp.append(file)
                      print(elenco_file_temp)
                      for f in elenco_file_temp:
                          stringa += f
                          if elenco_file_temp.index(f) != len(elenco_file_temp)-1:
                              stringa += "|"
                          else:
                              stringa += "\" -c copy  -bsf:a aac_adtstoasc output.mp4"
                      print(stringa)
                      os.system(stringa)
                  
                  concatenate()
                  

                  【讨论】:

                  • concat 协议不应该与 MP4 一起使用,并且不会像 accepted answer 中提到的那样工作。
                  • 不仅(正如 Neckbeard 勋爵指出的那样)有必要将 .mp4 文件转换为中间 .TS 格式才能使连接成功,而且可能更糟糕的是,这个答案甚至没有开始解决输入视频文件具有匹配比特率、帧率、采样率、时基和持续时间的需求。如果没有这 5 个共同因素,文件无法连接(即使用流复制连接),而是必须重新编码。
                  【解决方案14】:

                  这里描述的 concat 协议; https://trac.ffmpeg.org/wiki/Concatenate#protocol

                  当使用命名管道来避免中间文件时

                  速度非常快(阅读:即时),没有丢帧,并且运行良好。

                  记得删除命名管道文件,并记得检查视频是否为 H264 和 AAC,您可以使用 ffmpeg -i filename.mp4 进行操作(检查是否提及 h264 和 aac)

                  【讨论】:

                    【解决方案15】:

                    这是我使用命令替换和 concat 视频过滤器(这将重新编码)加入一个充满 MP4 文件的目录的方法 - 认为其他人会从这个单行中获得一些用途,特别是如果你有很多文件(我一口气加入了17个文件):

                    ffmpeg $(for f in *.mp4 ; do echo -n "-i $f "; done) -filter_complex \
                    "$(i=0 ; for f in *.mp4 ; do echo -n "[$i:v] [$i:a] " ; i=$((i+1)) ; done \
                    && echo "concat=n=$i:v=1:a=1 [v] [a]")" -map "[v]" -map "[a]" output.mp4
                    

                    注意此命令按照它们的命名顺序加入您的文件(即,如果您运行ls *.mp4,则与它们呈现的顺序相同) - 在我的例子中,它们每个都有一个轨道号,所以效果很好。

                    【讨论】:

                    • 这将是一个脚本而不是一个命令。
                    【解决方案16】:

                    以下脚本在 Windows 10 powershell 上为我工作后的各种尝试。

                        $files=Get-ChildItem -path e:\ -Filter *.mp4
                    
                    
                        $files| ForEach-Object  {"file '$($_.FullName)'"}| Out-File -FilePath e:\temp.txt -Encoding ASCII
                    
                    
                        if (-not (test-path "e:\ffmpeg\bin\ffmpeg.exe")) {throw "e:\ffmpeg\bin\ffmpeg.exe needed"}
                    
                        E:\ffmpeg\bin\ffmpeg.exe -safe 0 -f concat -i "e:\temp.txt" -c copy -bsf:v hevc_mp4toannexb -an e:\joined.mp4
                    
                        # Conversion Cleanup
                        Remove-Item e:\temp.txt
                    

                    这里的前两行创建了一个文本文件 temp.txt,其中包含以下内容

                    file 'e:\first.mp4'
                    file 'e:\second.mp4'
                    

                    第 3、4 行检查 ffmpeg 在路径中是否可用并创建“joined.mp4”

                    与其他答案的主要区别如下

                    usage  of -bsf:v hevc_mp4toannexb -an
                    

                    对于我上面的 mp4 文件,您可能需要根据您的视频编码使用下面的其他替代方法。

                    h264_mp4toannexb
                    

                    所有可能的比特流过滤器都可以在https://ffmpeg.org/ffmpeg-bitstream-filters.html找到

                    【讨论】:

                      【解决方案17】:

                      对于.mp4 文件,我发现使用开源命令行工具:mp4box 效果更好更快。那么你可以这样使用它:

                      mp4box.exe -add video1.mp4 -cat video2.mp4 destvideo.mp4
                      

                      在此处下载适用于大多数平台:https://gpac.wp.imt.fr/mp4box/

                      【讨论】:

                      • 虽然这可能对某人有用,但要求是:“我正在尝试使用 ffmpeg 连接两个 mp4 文件。我需要这是一个自动过程”。请阅读完整的问题并尝试提供正确的答案。
                      【解决方案18】:

                      用于 MP4 文件

                      对于 .mp4 文件(我从 DailyMotion.com 获得:一个 50 分钟的电视剧集,只能分三部分下载,作为三个 .mp4 视频文件)以下是一个有效的解决方案: strong>Windows 7,并且涉及重新编码文件。

                      我重命名了文件(如 file1.mp4file2.mp4file3.mp4),以使这些部分正确观看完整电视剧集的顺序。

                      然后我创建了一个简单的批处理文件(concat.bat),内容如下:

                      :: Create File List
                      echo file file1.mp4 >  mylist.txt 
                      echo file file2.mp4 >> mylist.txt
                      echo file file3.mp4 >> mylist.txt
                      
                      :: Concatenate Files
                      ffmpeg -f concat -i mylist.txt -c copy output.mp4
                      

                      批处理文件ffmpeg.exe必须与要加入的.mp4文件放在同一个文件夹中。然后运行批处理文件。它通常需要不到十秒的时间来运行。
                      .

                      附录(2018/10/21)-

                      如果您正在寻找一种无需大量重新键入即可指定当前文件夹中所有 mp4 文件的方法,请在您的 Windows 批处理文件中尝试此方法(必须 strong> 包含选项 -safe 0):

                      :: Create File List
                      for %%i in (*.mp4) do echo file '%%i'>> mylist.txt
                      
                      :: Concatenate Files
                      ffmpeg -f concat -safe 0 -i mylist.txt -c copy output.mp4
                      

                      这适用于 Windows 7,在批处理文件中。 不要尝试在命令行中使用它,因为它只能在批处理文件中使用!

                      【讨论】:

                      • 这是一个很好的解决方案,但是谈论批处理文件会使这听起来很原始(就像它是在批处理中完成的),并且比它更复杂(因为批处理文件完全没有必要)。如果您只显示C:\blah&gt;type mylist.txt&lt;ENTER&gt;(因此他们会看到该文件的内容),那么会更清楚ffmpeg -f concat -i mylist.txt -c copy output.mp4&lt;ENTER&gt; 您还应该包含参考trac.ffmpeg.org/wiki/Concatenate(该参考甚至使用相同的文件名,mylist.txt)
                      • 这是一个非常好的解决方案,因为它使您不必为-i 参数输入冗长的文件列表。在 Linux 上,我这样做了:ls -1 P1041*.mp4 | sed s/"P"/" file P"/g &gt; mylist.txt,这比编写批处理脚本还要酷。无论如何,-c copy 执行得非常快(我不知道),这就是这个答案的真正魔力。
                      • 您的最后一个代码不起作用。我必须改用这个:(for %i in (*.mp4) do @echo file '%i') &gt; mylist.txt.
                      • 对于我的 mp4 文件,在 Mac 上使用 ffmpeg v3.2.2 运行 concat 代码后,输出文件中根本没有音频。
                      • del mylist.txt 应该加在顶部
                      【解决方案19】:

                      对于 MP4 文件:

                      如果它们不完全相同(100% 相同的编解码器、相同的分辨率、相同的类型)MP4 文件,那么您必须首先将它们转码为中间流:

                      ffmpeg -i myfile1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp1.ts
                      ffmpeg -i myfile2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts temp2.ts
                      // now join
                      ffmpeg -i "concat:temp1.ts|temp2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
                      

                      注意!: 输出将像第一个文件(而不是第二个文件)

                      【讨论】:

                      • 正如我在下面的回答中所示,不需要对文件进行转码。 FFmpeg 可以将多个 mp4 文件连接成一个 mp4 文件——无需任何中间步骤,也无需任何重新编码。唯一需要重新编码的情况是原始文件不是 mp4 文件,但目的是创建一个 mp4 文件作为输出文件。
                      • @T. Todua:我有兴趣了解您的两个 mp4 源文件之间究竟有什么区别。他们是否使用不同的采样率(例如 44,100 Hz 和 48,000 Hz)?还是他们使用不同的比特率(例如 128 kbps 和 64 kbps)?在这两种情况下,我不太明白命令 -c copy(流复制指令)如何修改源文件以使其兼容。
                      • @T.Todua 嗨,当视频没有音轨时,该命令效果很好。但是,当 myfile1.mp4 没有音频而 myfile2.mp4 有音频时,结果 output.mp4 没有音频。预期的 output.mp4 应该包含音频。我该如何解决?
                      • @T.Todua er....太糟糕了。 file1|file2 与 file2|file1 生成不同的视频。顺序很重要。
                      • 我在 concat 2 mp4 文件上尝试了你的命令。有用。但是,第二个文件的音频在生成的新串联中变得像 Ah-oh-Ee 声音一样断断续续。你有解决办法吗?
                      【解决方案20】:

                      来自此处的文档:https://trac.ffmpeg.org/wiki/Concatenate

                      如果您有 MP4 文件,可以通过首先将它们转码为 MPEG-2 传输流来无损连接这些文件。对于 H.264 视频和 AAC 音频,可以使用以下内容:

                      ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
                      ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
                      ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
                      

                      这种方法适用于所有平台。

                      我需要能够将其封装在跨平台脚本中,因此我使用了fluent-ffmpeg 并提出了以下解决方案:

                      const unlink = path =>
                        new Promise((resolve, reject) =>
                          fs.unlink(path, err => (err ? reject(err) : resolve()))
                        )
                      
                      const createIntermediate = file =>
                        new Promise((resolve, reject) => {
                          const out = `${Math.random()
                            .toString(13)
                            .slice(2)}.ts`
                      
                          ffmpeg(file)
                            .outputOptions('-c', 'copy', '-bsf:v', 'h264_mp4toannexb', '-f', 'mpegts')
                            .output(out)
                            .on('end', () => resolve(out))
                            .on('error', reject)
                            .run()
                        })
                      
                      const concat = async (files, output) => {
                        const names = await Promise.all(files.map(createIntermediate))
                        const namesString = names.join('|')
                      
                        await new Promise((resolve, reject) =>
                          ffmpeg(`concat:${namesString}`)
                            .outputOptions('-c', 'copy', '-bsf:a', 'aac_adtstoasc')
                            .output(output)
                            .on('end', resolve)
                            .on('error', reject)
                            .run()
                        )
                      
                        names.map(unlink)
                      }
                      
                      concat(['file1.mp4', 'file2.mp4', 'file3.mp4'], 'output.mp4').then(() =>
                        console.log('done!')
                      )
                      

                      【讨论】:

                        【解决方案21】:

                        合并当前目录下的所有 mp4 文件

                        我个人不喜欢创建之后必须删除的外部文件,所以我的解决方案如下,其中包括文件编号列表(如file_1_name, file_2_name, file_10_name, file_20_name, file_100_name,...)

                        #!/bin/bash
                        filesList=""
                        for file in $(ls -1v *.mp4);do #lists even numbered file
                            filesList="${filesList}${file}|"
                        done
                        filesList=${filesList%?} # removes trailing pipe
                        ffmpeg -i "concat:$filesList" -c copy $(date +%Y%m%d_%H%M%S)_merged.mp4
                        

                        【讨论】:

                        • 我将此代码复制到记事本中,但没有任何反应。
                        • @7vujy0f0hy 这适用于带有 bash shell 的 linux,也许它可以与 cygwin 一起使用
                        【解决方案22】:

                        ffmpeg中各种拼接方式的详细文档可以found here

                        您可以使用'Concat filter' 进行快速连接。

                        它执行重新编码。当输入具有不同的视频/音频格式时,此选项是最佳选择。

                        对于连接 2 个文件:

                        ffmpeg -i input1.mp4 -i input2.webm \
                        -filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] concat=n=2:v=1:a=1 [v] [a]" \
                        -map "[v]" -map "[a]" output.mp4
                        

                        对于连接 3 个文件:

                        ffmpeg -i input1.mp4 -i input2.webm -i input3.mp4 \
                        -filter_complex "[0:v:0] [0:a:0] [1:v:0] [1:a:0] [2:v:0] [2:a:0] concat=n=3:v=1:a=1 [v] [a]" \
                        -map "[v]" -map "[a]" output.mp4
                        

                        这适用于相同以及多种输入文件类型。

                        【讨论】:

                        • @Developer: 选项说明-> Concat filter wiki
                        • 当连接 3 个文件时,你应该使用 concat=n=3:v=1:a=1 而不是 n=2
                        • 我没有得到无损连接我的ouput.mp4 视频比特率明显低于输入视频的视频比特率
                        • @jasan, concat filter 对输出进行重新编码。对于无损输出,请选择 concat protocolconcat demuxer,但这两个选项都需要您具体说明输入格式。
                        • 请不要发布仅连接 2 个文件的“解决方案”。许多用户不会意识到只有 2 个文件的连接是一种特殊情况,当他们尝试对 2 个文件建议的方法时,它可能会很有效(例如,两个 .m4a 文件),但是当他们使用将它与 3 个相同类型的文件一起使用,它会失败。几乎任何两个 mp4 / m4a / m4b 文件都会连接,如果它们具有相同的比特率和采样率,即使它们具有不同的持续时间,但如果添加第三个这样的文件,它们将无法连接 因为它们有不同的持续时间。
                        【解决方案23】:
                        ffmpeg \
                          -i input_1.mp4 \
                          -i input_2.mp4 \
                          -filter_complex '[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]' \
                          -map [vid] \
                          -c:v libx264 \
                          -crf 23 \
                          -preset veryfast \
                          output.mp4
                        

                        【讨论】:

                        • 不仅仅是发布答案,您还可以添加一些解释,以便更好地理解 OP 和未来的读者的解决方案。
                        • 这创建了一个新的无声视频,两个输入同时播放。我预计连接会创建一个播放 input_1.mp4 然后 input_2.mp4 的视频
                        【解决方案24】:

                        根据 rogerdpack 和 Ed999 的回复,我创建了我的 .sh 版本

                        #!/bin/bash
                        
                        [ -e list.txt ] && rm list.txt
                        for f in *.mp4
                        do
                           echo "file $f" >> list.txt
                        done
                        
                        ffmpeg -f concat -i list.txt -c copy joined-out.mp4 && rm list.txt
                        

                        它将当前文件夹中的所有*.mp4文件加入joined-out.mp4

                        在 mac 上测试。

                        生成的文件大小是我的 60 个测试文件的确切总和。应该不会有任何损失。正是我需要的

                        【讨论】:

                          【解决方案25】:

                          我最终使用 mpg 作为中间格式并且它起作用了(注意这是一个危险的例子,-qscale 0 将重新编码视频...)

                          ffmpeg -i 1.mp4 -qscale 0 1.mpg
                          ffmpeg -i 2.mp4 -qscale 0 2.mpg
                          cat 1.mpg 2.mpg | ffmpeg -f mpeg -i - -qscale 0 -vcodec mpeg4 output.mp4
                          

                          【讨论】:

                          • 截至此评论,-sameq 已被删除。请改用-qscale 0
                          • 使用 =-sameq= 为我工作。我使用 =output.avi= 而不是 =output.mp4= 因为后者的纵横比不好。
                          • -sameq does not mean "same quality" 并已从 @XavierHo 提到的 ffmpeg 中删除。
                          • 为了连接两个文件而不得不通过中间格式对视频进行转码似乎很可惜。你会得到一代损失。最好不要比容器格式更深入,并在 ffmpeg 中使用 concat 命令,就像上面的 @rogerdpack 所做的那样。
                          • @Mark L : 支持文件级连接的音频格式包括 MPG、MPEG-1、MPEG-2 PS、DV、VOB 和 MPEG-TS。
                          【解决方案26】:

                          这是我为将多个 GoPro mp4 连接成 720p mp4 而编写的脚本。希望对您有所帮助。

                          #!/bin/sh
                          cmd="( "
                          for i; do
                              cmd="${cmd}ffmpeg -i $i -ab 256000 -vb 10000000 -mbd rd -trellis 2 -cmp 2 -subcmp 2 -g 100 -f mpeg -; "
                          done
                          cmd="${cmd} ) | ffmpeg -i - -vb 10000000 -ab 256000 -s 1280x720 -y out-`date +%F-%H%M.%S`.mp4"
                          echo "${cmd}"
                          eval ${cmd}
                          

                          【讨论】:

                          • 这不是串联的。此代码不会连接输入,而是重新编码它们。这将获取任意视频文件并将它们更改为通用的音频和视频比特率(无论源文件的原始比特率或原始帧大小)。 o/p 想使用流复制连接两个文件,而不是重新编码它们,所以这不能解决所提出的问题。
                          猜你喜欢
                          • 2013-04-13
                          • 2019-12-30
                          • 2020-07-15
                          • 2019-08-15
                          • 2014-06-01
                          相关资源
                          最近更新 更多