Files
pycmx/bin/edl2scenelist.py
T
2026-09-17 15:10:47 -07:00

134 lines
3.3 KiB
Python

import argparse
import logging
import re
import sys
import pycmx
FORMAT = "%(asctime)-15s %(message)s"
logging.basicConfig(format=FORMAT)
log = logging.getLogger(__name__)
def all_video_edits(edl):
for event in edl.events:
for edit in event.edits:
if edit.channels.video:
yield edit
def get_scene_name(edit, pattern):
scene_extractor = re.compile(pattern, re.IGNORECASE)
if edit.clip_name is None:
return None
else:
match_data = re.match(scene_extractor, edit.clip_name)
if match_data:
return match_data[1]
else:
return edit.clip_name
def output_cmx(outfile, out_list):
outfile.write("TITLE: SCENE LIST\r\n")
outfile.write("FCM: NON-DROP FRAME\r\n")
for i, o in enumerate(out_list):
line = "{i: 03d} AX V C "
line += f"00:00:00:00 00:00:00:00 {o['start']} {o['end']}\r\n"
outfile.write(line)
outfile.write(f"* FROM CLIP NAME: {o['scene']}\r\n")
def output_cols(outfile, out_list):
for o in out_list:
outfile.write(f"{o['start']:>12}\t{o['end']:>12}\t{o['scene']}")
def scene_list(infile, outfile, out_format, pattern):
edl = pycmx.parse_cmx3600(infile)
current_scene_name = None
grouped_edits = []
for edit in all_video_edits(edl):
this_scene_name = get_scene_name(edit, pattern)
if this_scene_name is not None:
if current_scene_name != this_scene_name:
grouped_edits.append([])
current_scene_name = this_scene_name
grouped_edits[-1].append(edit)
out_list = []
for group in grouped_edits:
out_list.append(
{
"start": group[0].record_in,
"end": group[-1].record_out,
"scene": get_scene_name(group[0], pattern),
}
)
if out_format == "cmx":
output_cmx(outfile, out_list)
if out_format == "cols":
output_cols(outfile, out_list)
else:
log.warning(f"Format {out_format} unrecognized. Will use cmx.\n")
output_cmx(outfile, out_list)
def scene_list_cli():
parser = argparse.ArgumentParser(
description="Read video events from an input CMX EDL and output "
"events merged into scenes."
)
parser.add_argument(
"-o",
"--outfile",
default=sys.stdout,
type=argparse.FileType("w"),
help="Output file. Default is stdout.",
)
parser.add_argument(
"-f",
"--format",
default="cmx",
type=str,
help="Output format. Options are cols and cmx, cmx is the default.",
)
parser.add_argument(
"-p",
"--pattern",
default="V?([A-Z]*[0-9]+)",
help="RE pattern for extracting scene name from clip "
'name. The default is "V?([A-Z]*[0-9]+)". '
+ "This pattern will be matched case-insensitively.",
)
parser.add_argument(
"input_edl",
default=sys.stdin,
type=argparse.FileType("r"),
nargs="?",
help="Input file. Default is stdin.",
)
args = parser.parse_args()
infile = args.input_edl
scene_list(
infile=infile,
outfile=args.outfile,
out_format=args.format,
pattern=args.pattern,
)
if __name__ == "__main__":
scene_list_cli()