How to split a video using FFMPEG so that each chunk starts with a key frame?

后端 未结 6 454
我寻月下人不归
我寻月下人不归 2020-11-28 02:54

We need to split a large live WMV video feed in small chunks all of the same size. We made a script that works fine doing this, except for one thing: the video chunks don\'t

相关标签:
6条回答
  • 2020-11-28 03:02

    The latest builds of FFMPEG include a new option "segment" which does exactly what I think you need.

    ffmpeg -i INPUT.mp4 -acodec copy -f segment -vcodec copy -reset_timestamps 1 -map 0 OUTPUT%d.mp4

    This produces a series of numbered output files which are split into segments based on Key Frames. In my own testing, it's worked well, although I haven't used it on anything longer than a few minutes and only in MP4 format.

    0 讨论(0)
  • 2020-11-28 03:10

    Use ffprobe -show_frames -pretty <stream> to identify the key frames.

    0 讨论(0)
  • 2020-11-28 03:13

    Here is the solution that I could get to work:

    As suggested by av501 and d33pika, I used ffprobe to find where the key frames are. Because ffprobe is very verbose and can take several seconds or even minutes to output all key frames and there is no way to scope the range of frames we want from a lengthy video, I proceed into 5 steps:

    1. Export a video chunk from the original file, around the double of the desired chunk size.

      ffmpeg -i source.wmv -ss 00:00:00 -t 00:00:06 -acodec copy -vcodec copy -async 1 -y  0001.wmv
      
    2. Use ffprobe to find where the keyframes are. Choose closest keyframe after desired chunk size.

      ffprobe -show_frames -select_streams v -print_format json=c=1 0001.wmv
      
    3. From the output of ffprobe get the pkt_dts_time of the frame just before that key frame.

    4. ffmpeg on the exported chunk of step 1, specifying the same input and output file, and specifying -ss 00:00:00 and -t [value found in step 3].

      ffmpeg -i 0001.wmv -ss 00:00:00 -t 00:00:03.1350000 -acodec copy -vcodec copy -async 1 -y 0001.wmv
      
    5. Restart at step 1, using -ss [cumulated sum of values found in step 3 over iterations].

    Proceeding this way, I was able to have an efficient and robust way to split the video at key frames.

    0 讨论(0)
  • 2020-11-28 03:24

    If you are willing to do some scripting and want I frames at a particular interval the one way to do it is

    1. Run ffprobe and collect the locations of the I frames from the output

      ffprobe -show_streams

    2. Run a series of -ss -t commands using the same script to get the chunks you desire.

    You can then have your script decide minimum number of frames [say there are two I pictures within 10 frames of each other, you really don't want to be chunking it there].

    The other way to do it is to use gstreamer's multisfilesink and set the mode to key frame [however that will chunk at every key frame so that may not be ideal]

    0 讨论(0)
  • 2020-11-28 03:29

    As stated on the official FFMPEG Docs, it has worked better for me to specify -ss timestart before -i input_file.ext, because it sets (or so I understand) the beginning of the generated video to the nearest keyframe found before your specified timestamp.

    Change your example to:

    ffmpeg.exe -ss 00:00:00 -i "C:\test.wmv" -t 00:00:05 -acodec copy -vcodec copy -async 1 -y  "0000.wmv"
    

    I have tested this method working on .flv and .mp4 files.

    0 讨论(0)
  • 2020-11-28 03:29

    Using a newer build of ffmpeg, can achieve this by using ffprobe and the ffmpeg segment muxer.

    1. Use ffprobe and awk to identify the keyframes as close as possible to your desired chunk length.
    ffprobe -show_frames -select_streams v:0 \
            -print_format csv [SOURCE_VIDEO] 2>&1 |
    grep -n frame,video,1 |
    awk 'BEGIN { FS="," } { print $1 " " $5 }' |
    sed 's/:frame//g' |
    awk 'BEGIN { previous=0; frameIdx=0; size=0; } 
    {
      split($2,time,".");
      current=time[1];
      if (current-previous >= [DURATION_IN_SECONDS]){
        a[frameIdx]=$1; frameIdx++; size++; previous=current;
      }
    }
    END {
      str=a[0];
      for(i=1;i<size;i++) { str = str "," a[i]; } print str;
    }'
    

    Where

    • [SOURCE_VIDEO] = path to video you want to segment
    • [DURATION_IN_SECONDS] = desired segment length in seconds

    The output is comma-delimited string of keyframes.

    1. Use the keyframes output above as input to ffmpeg.
    ffmpeg -i [SOURCE_VIDEO] -codec copy -map 0 -f segment \
           -segment_frames [OUTPUT_OF_STEP_1] [SEGMENT_PREFIX] \
           _%03d.[SOURCE_VIDEO_EXTENSION]
    

    Where

    • [SOURCE_VIDEO] = path to video you want to segment
    • [OUTPUT_OF_STEP_1] = comma-delimited string of keyframes
    • [SEGMENT_PREFIX] = name of segment output
    • [SOURCE_VIDEO_EXTENSION] = extension of source video (e.g., mp4, mkv)
    0 讨论(0)
提交回复
热议问题