Skip to content

Commit 47bfecd

Browse files
committed
[cli] Add new save-edl command (#495)
Output is still not verified but this is a good starting point.
1 parent a841943 commit 47bfecd

File tree

6 files changed

+260
-11
lines changed

6 files changed

+260
-11
lines changed

docs/cli.rst

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Options
6767

6868
.. option:: -m TIMECODE, --min-scene-len TIMECODE
6969

70-
Minimum length of any scene. TIMECODE can be specified as number of frames (:option:`-m 10 <-m>`), time in seconds (:option:`-m 2.5 <-m>`), or timecode (:option:`-m 00:02:53.633 <-m>`).
70+
Minimum length of any scene. TIMECODE can be specified as number of frames (-m 10), time in seconds (-m 2.5), or timecode (-m 00:02:53.633).
7171

7272
Default: ``0.6s``
7373

@@ -81,7 +81,7 @@ Options
8181

8282
.. option:: -b BACKEND, --backend BACKEND
8383

84-
Backend to use for video input. Backend options can be set using a config file (:option:`-c/--config <-c>`). [available: opencv, pyav, moviepy]
84+
Backend to use for video input. Backend options can be set using a config file (:option:`-c/--config <-c>`). [available: opencv, pyav]
8585

8686
Default: ``opencv``
8787

@@ -91,11 +91,11 @@ Options
9191

9292
.. option:: -d N, --downscale N
9393

94-
Integer factor to downscale video by before processing. If unset, value is selected based on resolution. Set :option:`-d 1 <-d>` to disable downscaling.
94+
Integer factor to downscale video by before processing. If unset, value is selected based on resolution. Set -d 1 to disable downscaling.
9595

9696
.. option:: -fs N, --frame-skip N
9797

98-
Skip N frames during processing. Reduces processing speed at expense of accuracy. :option:`-fs 1 <-fs>` skips every other frame processing 50% of the video, :option:`-fs 2 <-fs>` processes 33% of the video frames, :option:`-fs 3 <-fs>` processes 25%, etc...
98+
Skip N frames during processing. Reduces processing speed at expense of accuracy. -fs 1 skips every other frame processing 50% of the video, -fs 2 processes 33% of the video frames, -fs 3 processes 25%, etc...
9999

100100
Default: ``0``
101101

@@ -208,7 +208,7 @@ Options
208208

209209
.. option:: -m TIMECODE, --min-scene-len TIMECODE
210210

211-
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (:option:`-m 100 <-m>`), in seconds with `s` suffix (:option:`-m 3.5s <-m>`), or timecode (:option:`-m 00:01:52.778 <-m>`).
211+
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (-m 100), in seconds with `s` suffix (-m 3.5s), or timecode (-m 00:01:52.778).
212212

213213

214214
.. _command-detect-content:
@@ -424,7 +424,7 @@ Options
424424

425425
.. option:: -m TIMECODE, --min-scene-len TIMECODE
426426

427-
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (:option:`-m 100 <-m>`), in seconds with `s` suffix (:option:`-m 3.5s <-m>`), or timecode (:option:`-m 00:01:52.778 <-m>`).
427+
Minimum length of any scene. Overrides global option :option:`-m/--min-scene-len <scenedetect -m>`. TIMECODE can be specified in frames (-m 100), in seconds with `s` suffix (-m 3.5s), or timecode (-m 00:01:52.778).
428428

429429

430430
************************************************************************
@@ -506,7 +506,7 @@ Options
506506

507507
.. option:: -f NAME, --filename NAME
508508

509-
Filename format to use for the scene list CSV file. You can use the $VIDEO_NAME macro in the file name. Note that you may have to wrap the name using single quotes or use escape characters (e.g. :option:`-f \$VIDEO_NAME-Scenes.csv <-f>`).
509+
Filename format to use for the scene list CSV file. You can use the $VIDEO_NAME macro in the file name. Note that you may have to wrap the name using single quotes or use escape characters (e.g. -f \$VIDEO_NAME-Scenes.csv).
510510

511511
Default: ``$VIDEO_NAME-Scenes.csv``
512512

@@ -558,6 +558,84 @@ Options
558558
Default: ``"Start Frame"``
559559

560560

561+
.. _command-save-edl:
562+
563+
.. program:: scenedetect save-edl
564+
565+
566+
``save-edl``
567+
========================================================================
568+
569+
Save cuts in EDL format (CMX 3600).
570+
571+
572+
Options
573+
------------------------------------------------------------------------
574+
575+
576+
.. option:: -f NAME, --filename NAME
577+
578+
Filename format to use.
579+
580+
Default: ``$VIDEO_NAME.edl``
581+
582+
.. option:: -t NAME, --title NAME
583+
584+
Title format to use.
585+
586+
Default: ``$VIDEO_NAME``
587+
588+
.. option:: -r REEL, --reel REEL
589+
590+
Reel name to use.
591+
592+
Default: ``AX``
593+
594+
.. option:: -o DIR, --output DIR
595+
596+
Output directory to save EDL file to. Overrides global option :option:`-o/--output <scenedetect -o>`.
597+
598+
599+
.. _command-save-html:
600+
601+
.. program:: scenedetect save-html
602+
603+
604+
``save-html``
605+
========================================================================
606+
607+
Save scene list to HTML file.
608+
609+
To customize image generation, specify the :ref:`save-images <command-save-images>` command before :ref:`save-html <command-save-html>`. This command always uses the result of the preceeding :ref:`save-images <command-save-images>` command, or runs it with the default config values unless ``--no-images`` is set.
610+
611+
612+
Options
613+
------------------------------------------------------------------------
614+
615+
616+
.. option:: -f NAME, --filename NAME
617+
618+
Filename format to use for the scene list HTML file. You can use the $VIDEO_NAME macro in the file name. Note that you may have to wrap the format name using single quotes.
619+
620+
Default: ``$VIDEO_NAME-Scenes.html``
621+
622+
.. option:: -n, --no-images
623+
624+
Do not include images with the result.
625+
626+
.. option:: -w pixels, --image-width pixels
627+
628+
Width in pixels of the images in the resulting HTML table.
629+
630+
.. option:: -h pixels, --image-height pixels
631+
632+
Height in pixels of the images in the resulting HTML table.
633+
634+
.. option:: -s, --show
635+
636+
Automatically open resulting HTML when processing is complete.
637+
638+
561639
.. _command-save-images:
562640

563641
.. program:: scenedetect save-images
@@ -590,13 +668,13 @@ Options
590668

591669
.. option:: -f NAME, --filename NAME
592670

593-
Filename format *without* extension to use when saving images. You can use the $VIDEO_NAME, $SCENE_NUMBER, $IMAGE_NUMBER, and $FRAME_NUMBER macros in the file name. You may have to use escape characters (e.g. :option:`-f \$SCENE_NUMBER-Image-\$IMAGE_NUMBER <-f>`) or single quotes.
671+
Filename format *without* extension to use when saving images. You can use the $VIDEO_NAME, $SCENE_NUMBER, $IMAGE_NUMBER, and $FRAME_NUMBER macros in the file name. You may have to use escape characters (e.g. -f \$SCENE_NUMBER-Image-\$IMAGE_NUMBER) or single quotes.
594672

595673
Default: ``$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER``
596674

597675
.. option:: -n N, --num-images N
598676

599-
Number of images to generate per scene. Will always include start/end frame, unless :option:`-n 1 <-n>`, in which case the image will be the frame at the mid-point of the scene.
677+
Number of images to generate per scene. Will always include start/end frame, unless -n 1, in which case the image will be the frame at the mid-point of the scene.
600678

601679
Default: ``3``
602680

@@ -675,6 +753,38 @@ Options
675753
Disable shifting frame numbers by start time.
676754

677755

756+
.. _command-save-xml:
757+
758+
.. program:: scenedetect save-xml
759+
760+
761+
``save-xml``
762+
========================================================================
763+
764+
Save cuts in XML format.
765+
766+
767+
Options
768+
------------------------------------------------------------------------
769+
770+
771+
.. option:: -f NAME, --filename NAME
772+
773+
Filename format to use.
774+
775+
Default: ``$VIDEO_NAME.xml``
776+
777+
.. option:: --format TYPE
778+
779+
Format to export. TYPE must be one of: fcpx, fcp.
780+
781+
Default: ``XmlFormat.FCPX``
782+
783+
.. option:: -o DIR, --output DIR
784+
785+
Output directory to save XML file to. Overrides global option :option:`-o/--output <scenedetect -o>`.
786+
787+
678788
.. _command-split-video:
679789

680790
.. program:: scenedetect split-video
@@ -713,7 +823,7 @@ Options
713823

714824
.. option:: -f NAME, --filename NAME
715825

716-
File name format to use when saving videos, with or without extension. You can use $VIDEO_NAME and $SCENE_NUMBER macros in the filename. You may have to wrap the format in single quotes or use escape characters to avoid variable expansion (e.g. :option:`-f \$VIDEO_NAME-Scene-\$SCENE_NUMBER <-f>`).
826+
File name format to use when saving videos, with or without extension. You can use $VIDEO_NAME and $SCENE_NUMBER macros in the filename. You may have to wrap the format in single quotes or use escape characters to avoid variable expansion (e.g. -f \$VIDEO_NAME-Scene-\$SCENE_NUMBER).
717827

718828
Default: ``$VIDEO_NAME-Scene-$SCENE_NUMBER``
719829

scenedetect.cfg

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@
302302
#start-col-name = Start Frame
303303

304304

305+
[save-edl]
306+
307+
# Filename format of EDL file. Can use $VIDEO_NAME macro.
308+
#filename = $VIDEO_NAME.edl
309+
310+
# Folder to output EDL file to. Overrides [global] output option.
311+
#output = /usr/tmp/images
312+
313+
# Reel/tape name to use.
314+
#reel = AX
315+
316+
# Title to use for the EDL file. Can use $VIDEO_NAME macro.
317+
#title = $VIDEO_NAME
318+
319+
305320
[save-qp]
306321

307322
# Filename format of QP file. Can use $VIDEO_NAME macro.
@@ -314,7 +329,6 @@
314329
#disable-shift = no
315330

316331

317-
318332
[save-xml]
319333

320334
# Filename format of XML file. Can use $VIDEO_NAME macro.

scenedetect/_cli/__init__.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,62 @@ def save_images_command(
15201520
ctx.save_images = True
15211521

15221522

1523+
SAVE_EDL_HELP = """Save cuts in EDL format (CMX 3600)."""
1524+
1525+
1526+
@click.command("save-edl", cls=Command, help=SAVE_EDL_HELP)
1527+
@click.option(
1528+
"--filename",
1529+
"-f",
1530+
metavar="NAME",
1531+
default=None,
1532+
type=click.STRING,
1533+
help="Filename format to use.%s" % (USER_CONFIG.get_help_string("save-edl", "filename")),
1534+
)
1535+
@click.option(
1536+
"--title",
1537+
"-t",
1538+
metavar="NAME",
1539+
default=None,
1540+
type=click.STRING,
1541+
help="Title format to use.%s" % (USER_CONFIG.get_help_string("save-edl", "title")),
1542+
)
1543+
@click.option(
1544+
"--reel",
1545+
"-r",
1546+
metavar="REEL",
1547+
default=None,
1548+
type=click.STRING,
1549+
help="Reel name to use.%s" % (USER_CONFIG.get_help_string("save-edl", "reel")),
1550+
)
1551+
@click.option(
1552+
"--output",
1553+
"-o",
1554+
metavar="DIR",
1555+
type=click.Path(exists=False, dir_okay=True, writable=True, resolve_path=False),
1556+
help="Output directory to save EDL file to. Overrides global option -o/--output.%s"
1557+
% (USER_CONFIG.get_help_string("save-edl", "output", show_default=False)),
1558+
)
1559+
@click.pass_context
1560+
def save_edl_command(
1561+
ctx: click.Context,
1562+
filename: ty.Optional[ty.AnyStr],
1563+
title: ty.Optional[ty.AnyStr],
1564+
reel: ty.Optional[ty.AnyStr],
1565+
output: ty.Optional[ty.AnyStr],
1566+
):
1567+
ctx = ctx.obj
1568+
assert isinstance(ctx, CliContext)
1569+
1570+
save_edl_args = {
1571+
"filename": ctx.config.get_value("save-edl", "filename", filename),
1572+
"title": ctx.config.get_value("save-edl", "title", title),
1573+
"reel": ctx.config.get_value("save-edl", "reel", reel),
1574+
"output": ctx.config.get_value("save-edl", "output", output),
1575+
}
1576+
ctx.add_command(cli_commands.save_edl, save_edl_args)
1577+
1578+
15231579
SAVE_QP_HELP = """Save cuts as keyframes (I-frames) for video encoding.
15241580
15251581
The resulting QP file can be used with the `--qpfile` argument in x264/x265.
@@ -1643,6 +1699,7 @@ def save_xml_command(
16431699

16441700
# Output
16451701
scenedetect.add_command(list_scenes_command)
1702+
scenedetect.add_command(save_edl_command)
16461703
scenedetect.add_command(save_html_command)
16471704
scenedetect.add_command(save_images_command)
16481705
scenedetect.add_command(save_qp_command)

scenedetect/_cli/commands.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from scenedetect._cli.config import XmlFormat
2828
from scenedetect._cli.context import CliContext
29+
from scenedetect.frame_timecode import FrameTimecode
2930
from scenedetect.platform import get_and_create_path
3031
from scenedetect.scene_manager import (
3132
CutList,
@@ -255,6 +256,65 @@ def split_video(
255256
logger.info("Video splitting completed, scenes written to disk.")
256257

257258

259+
def save_edl(
260+
context: CliContext,
261+
scenes: SceneList,
262+
cuts: CutList,
263+
filename: str,
264+
output: str,
265+
title: str,
266+
reel: str,
267+
):
268+
"""Handles the `save-edl` command. Outputs in CMX 3600 format."""
269+
# We only use scene information.
270+
del cuts
271+
272+
# Converts FrameTimecode to HH:MM:SS:FF
273+
# TODO: This should be part of the FrameTimecode object itself.
274+
def get_edl_timecode(timecode: FrameTimecode):
275+
total_seconds = timecode.get_seconds()
276+
hours = int(total_seconds // 3600)
277+
minutes = int((total_seconds % 3600) // 60)
278+
seconds = int(total_seconds % 60)
279+
frames_part = int((total_seconds * timecode.get_framerate()) % timecode.get_framerate())
280+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}:{frames_part:02d}"
281+
282+
edl_content = []
283+
284+
title = Template(title).safe_substitute(VIDEO_NAME=context.video_stream.name)
285+
edl_content.append(f"TITLE: {title}")
286+
edl_content.append("FCM: NON-DROP FRAME")
287+
edl_content.append("")
288+
289+
# Add each shot as an edit entry
290+
for i, (start, end) in enumerate(scenes):
291+
# TODO: Handle start time shift.
292+
in_tc = get_edl_timecode(start)
293+
out_tc = get_edl_timecode(end)
294+
295+
# TODO: How should the source/rec timestamps be aligned? One example I found showed:
296+
#
297+
# 001 AX V C 00:00:00:00 00:00:10:00 00:00:00:00 00:00:10:00
298+
# 002 AX V C 00:00:10:01 00:00:20:00 00:00:10:00 00:00:20:00
299+
# 003 AX V C 00:00:20:01 00:00:30:00 00:00:20:00 00:00:30:00
300+
# 004 AX V C 00:00:30:01 00:00:40:00 00:00:30:00 00:00:40:00
301+
# ^
302+
# |- Shifted by 1 frame here
303+
304+
# Format the edit entry according to CMX 3600 format
305+
event_line = f"{(i + 1):03d} {reel} V C {in_tc} {out_tc} {in_tc} {out_tc}"
306+
edl_content.append(event_line)
307+
308+
edl_path = get_and_create_path(
309+
Template(filename).safe_substitute(VIDEO_NAME=context.video_stream.name),
310+
output,
311+
)
312+
logger.info(f"Writing scenes in EDL format to {edl_path}")
313+
with open(edl_path, "w") as f:
314+
f.write("\n".join(edl_content))
315+
f.write("\n")
316+
317+
258318
def _save_xml_fcpx(
259319
context: CliContext,
260320
scenes: SceneList,

scenedetect/_cli/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,12 @@ class XmlFormat(Enum):
397397
"output": None,
398398
"verbosity": "info",
399399
},
400+
"save-edl": {
401+
"filename": "$VIDEO_NAME.edl",
402+
"output": None,
403+
"reel": "AX",
404+
"title": "$VIDEO_NAME",
405+
},
400406
"save-html": {
401407
"filename": "$VIDEO_NAME-Scenes.html",
402408
"image-height": 0,

0 commit comments

Comments
 (0)