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.
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: