Skip to content

Instantly share code, notes, and snippets.

@seriousm4x
Last active August 21, 2023 14:14
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seriousm4x/b5f795d7058f4e831270b047e25c3b9e to your computer and use it in GitHub Desktop.
Save seriousm4x/b5f795d7058f4e831270b047e25c3b9e to your computer and use it in GitHub Desktop.
Split a video into single parts from a premiere edl file
#!/usr/bin/env python3
"""
Cut Adobe Premiere .edl into single clips with ffmpeg.
Make cuts in the video track in Premiere, and export it as an edl file.
python ffmpeg-edl.py -i <edl> <edl2> -o output_folder
You can give this script multiple edl files to process.
ffmpeg and ffprobe are required.
"""
import argparse
import json
import os
import re
import subprocess
ffmpeg = "ffmpeg"
ffprobe = "ffprobe"
def tcToSec(timecode, fps):
h, m, s, f = timecode.split(':')
return float(h) * 3600 + float(m) * 60 + float(s) + float(f) / float(eval(fps))
def main(args):
# loop edl files from cli
for arg in args.i:
edl_ext = os.path.splitext(os.path.basename(arg))[1]
if edl_ext != ".edl":
print("File is not an .edl")
exit()
with open(arg, "r", encoding="utf-8") as f:
edl = f.read()
edl_path = os.path.dirname(os.path.realpath(arg))
# loop through cut blocks in edl
for block in edl.split("\n\n"):
clip = re.findall(r'\* FROM CLIP NAME: (.*)', block)
if not clip:
continue
clip = clip[0]
clip_name, clip_ext = os.path.splitext(os.path.basename(clip))
# loop through lines in a block
for line in block.split("\n"):
if not "V " in line:
continue
# collect clip and timecode infos
video_streams = subprocess.Popen([ffprobe, "-v", "warning", "-print_format",
"json", "-show_format", "-show_streams", clip],
stdout=subprocess.PIPE, shell=True)
data = json.loads(video_streams.stdout.read())
fps = data["streams"][0]["r_frame_rate"]
splitted_line = line.split(" ")[::-1]
inpoint = tcToSec(splitted_line[3], fps)
outpoint = tcToSec(splitted_line[2], fps)
duration = str(outpoint - inpoint)
cut = line.split(" ")[0]
# ffmpeg
ffin = os.path.join(edl_path, clip)
ffout = os.path.join(
args.o, f"{clip_name} # cut{cut}{clip_ext}")
cmd = [ffmpeg, "-hide_banner", "-v", "warning", "-ss", str(inpoint), "-i", ffin,
"-t", duration, "-c", "copy", "-y", ffout]
print("Writing", os.path.abspath(ffout))
subprocess.check_call(cmd)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-i', help='Premiere .edl file', nargs='*')
parser.add_argument('-o', help='Output folder', nargs="?",
default=os.path.dirname(os.path.abspath(__file__)))
args = parser.parse_args()
if not os.path.isdir(args.o):
os.mkdir(args.o)
main(args)
@HulkSmashBurgers
Copy link

HulkSmashBurgers commented Mar 5, 2021

Can't get this to work. I can run the script with no args and get the no arg usage tip, but I get no response when I give it an edl file. FFMpeg and ffprobe are in my system path, so those should be no problem.

python ffmpeg-edl.py "D:\Test video.edl"

Nothing happens. No results, no errors. Thought maybe I needed name my edl "D:\Test video.mov.edl" but that doesn't work either.

@seriousm4x
Copy link
Author

Wow, I didn't even remember this thing... Looking at old code is weird.
Anyways, I've updated it. Please try again.

@HulkSmashBurgers
Copy link

My apologies. It was an error on my part. I'm new to this and still learning. Both the old and new version of your script work.

Are you no longer using this? I think I will be using this a ton. Thank you for creating it.

My only request would be an option to choose an output folder. I'm an beginner editor and I don't always have the space on the source drive.

@HulkSmashBurgers
Copy link

I've gotten the script to run, but I'm having a few issues.

  1. The script grabs the master timecodes (right), not the source timecodes (left). So it's writing out the entire video in clips, rather than my Premiere timeline. I got around that by removing the master timecodes in a text editor.
001  AX       AA/V  C        00:06:53:04 00:08:30:01
002  AX       AA/V  C        00:14:06:21 00:16:25:07
003  AX       AA/V  C        00:23:37:15 00:25:54:00
004  AX       AA/V  C        00:32:58:18 00:35:20:23
005  AX       AA/V  C        00:35:27:22 00:36:15:04
006  AX       AA/V  C        00:42:02:05 00:44:53:15
007  AX       AA/V  C        00:49:58:07 00:51:29:01
008  AX       AA/V  C        01:47:30:16 01:49:51:06
009  AX       AA/V  C        01:58:14:15 01:59:51:01
010  AX       AA/V  C        01:59:57:02 02:00:37:02
011  AX       AA/V  C        02:03:08:19 02:05:11:18
012  AX       AA/V  C        02:18:40:15 02:19:03:11
013  AX       AA/V  C        02:30:59:08 02:32:02:07
014  AX       AA/V  C        02:37:28:13 02:39:05:03
  1. The first clip is 100% accurate. The second has 2 extra seconds at the start and 2 seconds missing at the end, and the clips gradually become more misaligned. Clip 12 is misaligned by 8 seconds. Clip 14 is off by 10.

I did come across a tip that will make the script much faster. I noticed that each clip took longer and longer for ffmpeg to start processing it. Putting the -ss before -i makes ffmpeg seek instantly.

See here: https://stackoverflow.com/questions/18444194/cutting-the-videos-based-on-start-and-end-time-using-ffmpeg/42827058#42827058

@seriousm4x
Copy link
Author

seriousm4x commented Mar 7, 2021

No, I don't use this script any longer.

  • I've added an option to ask for a destination folder.
  • For your first question, you are right. It always worked out for me so far, but I've changed it to use the other 2 timecodes.
  • For your second question, I don't know where the offset comes from as my script takes the times from the edl. I've tried some things but couldn't figure it out.
  • ffmpeg: I added the start timecode before the input and made ffmpeg less verbose
  • I've also changed the way the clips are named. They get their name from the edl cut number at the beginning of the line.

Tell me what you think. Maybe we can figure out the offset issue.

@HulkSmashBurgers
Copy link

The changes are nice. I do like to see the progress of each clip, so I've switched that back. It works with unedited EDLs, and it spits out cuts quickly now.

I'm using a batch file to run the script for multiple folders. I'd love to be able to pass a destination variable to the script. Something like this:

ffmpeg-edl.py "%%f" "%destination%

Maybe the script could check for that first?

The offset problem turns out to be a problem with my Adobe Premiere project. According to ffmpeg and various video players my video length is 02:49:31.06, but Premiere shows it as 02:49:20:20. So, I'll have to figure that out.

@HulkSmashBurgers
Copy link

Over in a Premiere thread, it is thought that this script may not be handling the framerate of my video correctly. They think it's reading '24/1.001' from the FFprobe and rather than calculating the result it's just dumping everything after and including the '/' to end up with 24fps rather than 23.976.

They suggested something like this may work, but it produces errors for me.
fps = data["streams"][0]["r_frame_rate"].split("/")[0] / data["streams"][0]["r_frame_rate"].split("/")[1]

@seriousm4x
Copy link
Author

seriousm4x commented Mar 8, 2021

I've changed the script to use argparse instead of a popup window for the destination.
Run it like this: python ffmpeg-edl.py -i file1.edl file2.edl file3.edl -o output_path.
If you don't set an output folder, it falls back to the same folder as the ffmpeg-edl.py file.

The fps problem made sense so I changed it as well. It just reads the output from ffprobe now and calculates the correct fps.

@HulkSmashBurgers
Copy link

HulkSmashBurgers commented Mar 10, 2021

Thank so much for all your work.

I've just tried this on a EDL that refers to multiple videos, and it does not handle them. It creates all of the cuts from the first clip.

Results in:
upscale 01 # cut001.mov
all the way up to
upscale 01 # cut009.mov

001  AX       AA/V  C        00:06:53:16 00:08:30:10 00:00:00:00 00:01:36:22
* FROM CLIP NAME: upscale 01.mov

002  AX       AA/V  C        00:14:07:20 00:16:25:25 00:01:36:22 00:03:54:27
* FROM CLIP NAME: upscale 01.mov

003  AX       AA/V  C        00:23:38:24 00:25:55:09 00:03:54:27 00:06:11:13
* FROM CLIP NAME: upscale 01.mov

004  AX       AA/V  C        00:04:59:05 00:07:21:14 00:06:11:13 00:08:33:20
* FROM CLIP NAME: upscale 02.mov

005  AX       AA/V  C        00:07:28:15 00:08:15:24 00:08:33:20 00:09:21:00
* FROM CLIP NAME: upscale 02.mov

006  AX       AA/V  C        00:14:03:05 00:16:56:11 00:09:21:00 00:12:14:05
* FROM CLIP NAME: upscale 02.mov

007  AX       AA/V  C        00:21:59:16 00:23:30:19 00:12:14:05 00:13:45:07
* FROM CLIP NAME: upscale 02.mov

008  AX       AA/V  C        00:22:35:02 00:25:05:09 00:13:45:07 00:16:15:15
* FROM CLIP NAME: upscale 04.mov

009  AX       AA/V  C        00:04:55:12 00:06:31:28 00:16:15:15 00:17:51:29
* FROM CLIP NAME: upscale 05.mov

@seriousm4x
Copy link
Author

It was written to only use a single clip. I've changed some stuff to hopefully make it work with multiple clips. Try again.

@HulkSmashBurgers
Copy link

That's working great. Thank you.

@maddydevs
Copy link

python3 /home/maddy/Desktop/edl-editing/second.py -i madesh.edl
same location of my media files also:

Error log:
Simple multimedia streams analyzer
usage: ffprobe [OPTIONS] [INPUT_FILE]

You have to specify one input file.
Use -h to get full help or, even better, run 'man ffprobe'.
Traceback (most recent call last):
File "/home/maddy/Desktop/edl-editing/second.py", line 76, in
main(args)
File "/home/maddy/Desktop/edl-editing/second.py", line 51, in main
data = json.loads(video_streams.stdout.read())
File "/usr/lib/python3.8/json/init.py", line 357, in loads
return _default_decoder.decode(s)
File "/usr/lib/python3.8/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.8/json/decoder.py", line 355, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment