Search code examples
pythonvideoffmpegcompressioncodec

Extracting all the group of pictures (GOPs) of a video generated by the codec


I am doing some remasterisation techniques on a video, and instead of using these techniques on the whole video at once I want to do them on the group of pictures(GOPs) of the video separately, so what I want to know is there any way to extract the group of pictures of a video which are generated by the codec.


Solution

  • Group of pictures (GOP) applies all video frames starting from a key frame, and ends one frame before the next key frame.
    (The above definition assumes "Close GOPs").

    The following post have examples for splitting a video into GOPs, but there is no Python code sample and I am not sure if this script is actually working.


    For splitting a video file into multiple files when each file is one GOP, we may use segment muxer with -segment_times argument.
    -segment_times expects a list of timestamps. We shell provide a list of timestamps of all the key frames in the input file.


    Let's start by building an input video file for testing (using FFmpeg CLI):

    ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=100 -vcodec libx264 -g 10 in.mp4

    The above command builds synthetic a video file with fixed GOP size of 10 frames (for testing).
    The frames are numbered, so it's easy to follow...


    Using FFprobe CLI for getting the timestamps of all the key-frames (for demonstrating the concept):

    ffprobe -skip_frame nokey -select_streams v:0 -show_frames -show_entries frame=pkt_pts_time -of json in.mp4 > tmp.txt

    (A similar command is going to be executed from Python).

    The above command creates a text file with timestamps of all key-frames in JSON format:

    {
        "frames": [
        ...    
        {
            "pkt_pts_time": "10.000000"
        },
        {
            "pkt_pts_time": "20.000000"
        },
        ...
    }
    

    The segment_times list is going to be: "10.000000,20.000000,30.000000...".


    Using a Python script for programmatically splitting video file to GOPs:

    • Use FFprobe for getting the PTS timestamps of all key frames (get it in JSON format):

       data = sp.run(['ffprobe', '-skip_frame', 'nokey', '-select_streams', 'v:0', '-show_frames', '-show_entries', 'frame=pkt_pts_time', '-of', 'json', in_file_name], stdout=sp.PIPE).stdout
      
    • Convert from JSON (string) to dictionary, and Get 'frames' out of the dictionary:

       dict = json.loads(data) 
       frames_dict = dict['frames']
      
    • Building a comma separated string of timestamps:

       pts_list = [item['pkt_pts_time'] for item in frames_dict]
       segment_times = ",".join(pts_list)
      
    • Use FFmpeg for splitting the input video by timestamps (files: out0000.mp4, out0001.mp4, out0002.mp4):

       sp.run(['ffmpeg', '-i', in_file_name, '-codec', 'copy', '-f', 'segment', '-reset_timestamps', '1', '-segment_times', segment_times, 'out%04d.mp4'])
      

    The above code uses subprocess module for executing FFmpeg and FFprobe within Python.
    Make sure ffmpeg and ffprobe are in the exaction path.


    Python code:

    import subprocess as sp
    import json
    
    # Preparation: build synthetic video file for testing
    # ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=100 -vcodec libx264 -g 10 in.mp4
    
    in_file_name = 'in.mp4'  # Input file name
    
    # Use FFprobe for getting the PTS timestamps of all key frames (get it in JSON format).
    data = sp.run(['ffprobe', '-skip_frame', 'nokey', '-select_streams', 'v:0', '-show_frames', '-show_entries', 'frame=pkt_pts_time', '-of', 'json', in_file_name], stdout=sp.PIPE).stdout
    dict = json.loads(data)  # Convert from JSON (string) to dictionary 
    frames_dict = dict['frames']  # Get 'frames' out of the dictionary
    
    pts_list = [item['pkt_pts_time'] for item in frames_dict]  # Convert to list: ['0.000000', '10.000000', '20.000000', ...]
    segment_times = ",".join(pts_list)  # Convert list to comma separated string: '0.000000,10.000000,20.000000,...'
    
    # Use FFmpeg for splitting the input video by timestamps (files: out0000.mp4, out0001.mp4, out0002.mp4)
    # Each segment file is going to be a GOP - start from key-frame, and end one frame before the next key-frame.
    sp.run(['ffmpeg', '-i', in_file_name, '-codec', 'copy', '-f', 'segment', '-reset_timestamps', '1', '-segment_times', segment_times, 'out%04d.mp4'])
    

    Note:

    • The above solution may not work for all codecs and all file formats.
      For example, H.265 codec has segmenting issues (due to FFmpeg limitations).