Skip to content

Instantly share code, notes, and snippets.

@jerrylususu
Created March 21, 2022 15:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jerrylususu/2442f9a58020c24af3f1461cf907f140 to your computer and use it in GitHub Desktop.
Save jerrylususu/2442f9a58020c24af3f1461cf907f140 to your computer and use it in GitHub Desktop.
Key-frame based FFmpeg Cut
# WARNING: NOT QUITE WORKING...
import sys
import subprocess
import bisect
from pathlib import Path
def run_command(command_list):
success = True
stdout = b""
print("executing: ", " ".join(command_list))
try:
stdout = subprocess.check_output(command_list)
except subprocess.CalledProcessError:
success = False
return stdout.decode(), success
# https://stackoverflow.com/questions/63548027/cut-a-video-in-between-key-frames-without-re-encoding-the-full-video-using-ffpme
def get_keyframe_list(input_file):
probe_command = ["ffprobe", "-v", "error", "-select_streams", "v:0", "-skip_frame", "nokey", "-show_entries", "frame=pkt_pts_time", "-of", "csv=p=0", input_file]
output, _ = run_command(probe_command)
keyframes = [float(line) for line in output.split("\n") if line]
return keyframes
def find_closest_range(keyframe_list, start_sec, end_sec):
if len(keyframe_list) < 2:
raise Exception("too few keyframe...")
# some binary search magic that seems to work?
start_index = bisect.bisect_left(keyframe_list, start_sec)
end_index = bisect.bisect(keyframe_list, end_sec)-1
return keyframe_list[start_index], keyframe_list[end_index]
def time_to_sec(time_str):
ms = 0
if ":" not in time_str:
raise Exception("invalid time format")
if "." in time_str:
time_str, ms = time_str.split(".")
ms = float("0." + ms)
times = time_str.split(":")
hour, min, sec = 0, int(times[-2]), int(times[-1])
if len(times) == 3:
hour = int(times[0])
precise_second = hour*60*60 + min*60 + sec + ms
return precise_second
def ffmpeg_encode_cut(in_file, start, end, out_file):
# EDIT ENCODE PARAMETER HERE!
cut_command = ["ffmpeg", "-i", in_file,"-ss", str(start), "-to", str(end), "-c:v", "libx264", "-crf", "18", "-y", out_file]
# print(" ".join(cut_command))
output, success = run_command(cut_command)
return success
def ffmpeg_stream_copy_cut(in_file, start, end, out_file):
cut_command = ["ffmpeg", "-ss", str(start), "-i", in_file, "-to", str(end), "-c:v", "copy", "-c:a", "copy", "-y", out_file]
output, success = run_command(cut_command)
return success
def ffmpeg_concat(segments, out_file):
segments = [segment for segment in segments if Path(segment).exists()]
with open("concat.txt", "w") as f:
for segment in segments:
f.write("file '{}'\n".format(segment))
concat_command = ["ffmpeg", "-f", "concat", "-safe", "0", "-i", "concat.txt", "-c", "copy", "-y", out_file]
output, success = run_command(concat_command)
return success
def do_cut(file, start, end):
keyframes = get_keyframe_list(file)
start_sec, end_sec = time_to_sec(start), time_to_sec(end)
start_frame, end_frame = find_closest_range(keyframes, start_sec, end_sec)
print(f"{start_sec=} {start_frame=} {end_frame=} {end_sec=}")
ffmpeg_encode_cut(file, start_sec, start_frame, "before.mp4")
ffmpeg_stream_copy_cut(file, start_frame, end_frame, "inside.mp4")
ffmpeg_encode_cut(file, end_frame, end_sec, "after.mp4")
ffmpeg_concat(["before.mp4", "inside.mp4", "after.mp4"], "output.mp4")
def main():
print("hello world")
if len(sys.argv) < 4:
print("usage: cut.py [file] [start] [end]")
print("example: cut.py input.mp4 00:01:02.02 00:01:04.88")
print("time format: (hh:)mm:ss(.ms)")
sys.exit(1)
do_cut(sys.argv[1], sys.argv[2], sys.argv[3])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment