Skip to content

Commit

Permalink
[split_video] Fix not respecting global output directory option.
Browse files Browse the repository at this point in the history
  • Loading branch information
Breakthrough committed Jan 23, 2024
1 parent 9a715a8 commit 14daf89
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 74 deletions.
36 changes: 17 additions & 19 deletions scenedetect/_cli/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def __init__(self):
self.stats_manager: StatsManager = None

# Global `scenedetect` Options
self.output_directory: str = None # -o/--output
self.output_dir: str = None # -o/--output
self.quiet_mode: bool = None # -q/--quiet or -v/--verbosity quiet
self.stats_file_path: str = None # -s/--stats
self.drop_short_scenes: bool = None # --drop-short-scenes
Expand All @@ -131,7 +131,7 @@ def __init__(self):
# `save-images` Command Options
self.save_images: bool = False
self.image_extension: str = None # save-images -j/--jpeg, -w/--webp, -p/--png
self.image_directory: str = None # save-images -o/--output
self.image_dir: str = None # save-images -o/--output
self.image_param: int = None # save-images -q/--quality if -j/-w,
# otherwise -c/--compression if -p
self.image_name_format: str = None # save-images -f/--name-format
Expand All @@ -146,14 +146,14 @@ def __init__(self):
self.split_video: bool = False
self.split_mkvmerge: bool = None # split-video -m/--mkvmerge
self.split_args: str = None # split-video -a/--args, -c/--copy
self.split_directory: str = None # split-video -o/--output
self.split_dir: str = None # split-video -o/--output
self.split_name_format: str = None # split-video -f/--filename
self.split_quiet: bool = None # split-video -q/--quiet

# `list-scenes` Command Options
self.list_scenes: bool = False
self.list_scenes_quiet: bool = None # list-scenes -q/--quiet
self.scene_list_directory: str = None # list-scenes -o/--output
self.scene_list_dir: str = None # list-scenes -o/--output
self.scene_list_name_format: str = None # list-scenes -f/--filename
self.scene_list_output: bool = None # list-scenes -n/--no-output
self.skip_cuts: bool = None # list-scenes -s/--skip-cuts
Expand Down Expand Up @@ -256,9 +256,9 @@ def handle_options(
framerate=framerate,
backend=self.config.get_value("global", "backend", backend, ignore_default=True))

self.output_directory = output if output else self.config.get_value("global", "output")
if self.output_directory:
logger.info('Output directory set:\n %s', self.output_directory)
self.output_dir = output if output else self.config.get_value("global", "output")
if self.output_dir:
logger.info('Output directory set:\n %s', self.output_dir)

self.min_scene_len = parse_timecode(
min_scene_len if min_scene_len is not None else self.config.get_value(
Expand All @@ -271,7 +271,7 @@ def handle_options(

# Create StatsManager if --stats is specified.
if stats_file:
self.stats_file_path = get_and_create_path(stats_file, self.output_directory)
self.stats_file_path = get_and_create_path(stats_file, self.output_dir)
self.stats_manager = StatsManager()

# Initialize default detector with values in the config file.
Expand Down Expand Up @@ -483,14 +483,14 @@ def handle_list_scenes(
self.list_scenes_quiet = quiet or self.config.get_value("list-scenes", "quiet")
no_output_file = no_output_file or self.config.get_value("list-scenes", "no-output-file")

self.scene_list_directory = self.config.get_value(
self.scene_list_dir = self.config.get_value(
"list-scenes", "output", output, ignore_default=True)
self.scene_list_name_format = self.config.get_value("list-scenes", "filename", filename)
if self.scene_list_name_format is not None and not no_output_file:
logger.info("Scene list filename format:\n %s", self.scene_list_name_format)
self.scene_list_output = not no_output_file
if self.scene_list_directory is not None:
logger.info("Scene list output directory:\n %s", self.scene_list_directory)
if self.scene_list_dir is not None:
logger.info("Scene list output directory:\n %s", self.scene_list_dir)

self.list_scenes = True

Expand Down Expand Up @@ -523,10 +523,9 @@ def handle_split_video(

self.split_video = True
self.split_quiet = quiet or self.config.get_value('split-video', 'quiet')
self.split_directory = self.config.get_value(
'split-video', 'output', output, ignore_default=True)
if self.split_directory is not None:
logger.info('Video output path set: \n%s', self.split_directory)
self.split_dir = self.config.get_value('split-video', 'output', output, ignore_default=True)
if self.split_dir is not None:
logger.info('Video output path set: \n%s', self.split_dir)
self.split_name_format = self.config.get_value('split-video', 'filename', filename)

# We only load the config values for these flags/options if none of the other
Expand Down Expand Up @@ -656,8 +655,7 @@ def handle_save_images(
logger.debug('\n'.join(error_strs))
raise click.BadParameter('\n'.join(error_strs), param_hint='save-images')

self.image_directory = self.config.get_value(
'save-images', 'output', output, ignore_default=True)
self.image_dir = self.config.get_value('save-images', 'output', output, ignore_default=True)

self.image_name_format = self.config.get_value('save-images', 'filename', filename)
self.num_images = self.config.get_value('save-images', 'num-images', num_images)
Expand All @@ -667,8 +665,8 @@ def handle_save_images(
image_param_type = 'Compression' if png else 'Quality'
image_param_type = ' [%s: %d]' % (image_param_type, self.image_param)
logger.info('Image output format set: %s%s', image_type, image_param_type)
if self.image_directory is not None:
logger.info('Image output directory set:\n %s', os.path.abspath(self.image_directory))
if self.image_dir is not None:
logger.info('Image output directory set:\n %s', os.path.abspath(self.image_dir))

self.save_images = True

Expand Down
29 changes: 12 additions & 17 deletions scenedetect/_cli/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ def _list_scenes(context: CliContext, scene_list: List[Tuple[FrameTimecode, Fram
if not scene_list_filename.lower().endswith('.csv'):
scene_list_filename += '.csv'
scene_list_path = get_and_create_path(
scene_list_filename, context.scene_list_directory
if context.scene_list_directory is not None else context.output_directory)
scene_list_filename,
context.scene_list_dir if context.scene_list_dir is not None else context.output_dir)
logger.info('Writing scene list to CSV file:\n %s', scene_list_path)
with open(scene_list_path, 'wt') as scene_list_file:
write_scene_list(
Expand Down Expand Up @@ -180,11 +180,8 @@ def _save_images(
"""Handles the `save-images` command."""
if not context.save_images:
return None

image_output_dir = context.output_directory
if context.image_directory is not None:
image_output_dir = context.image_directory

# Command can override global output directory setting.
output_dir = (context.output_dir if context.image_dir is None else context.image_dir)
return save_images(
scene_list=scene_list,
video=context.video_stream,
Expand All @@ -193,7 +190,7 @@ def _save_images(
image_extension=context.image_extension,
encoder_param=context.image_param,
image_name_template=context.image_name_format,
output_dir=image_output_dir,
output_dir=output_dir,
show_progress=not context.quiet_mode,
scale=context.scale,
height=context.height,
Expand All @@ -207,14 +204,13 @@ def _export_html(context: CliContext, scene_list: List[Tuple[FrameTimecode, Fram
"""Handles the `export-html` command."""
if not context.export_html:
return

# Command can override global output directory setting.
output_dir = (context.output_dir if context.image_dir is None else context.image_dir)
html_filename = Template(
context.html_name_format).safe_substitute(VIDEO_NAME=context.video_stream.name)
if not html_filename.lower().endswith('.html'):
html_filename += '.html'
html_path = get_and_create_path(
html_filename, context.image_directory
if context.image_directory is not None else context.output_directory)
html_path = get_and_create_path(html_filename, output_dir)
logger.info('Exporting to html file:\n %s:', html_path)
if not context.html_include_images:
image_filenames = None
Expand All @@ -232,7 +228,6 @@ def _split_video(context: CliContext, scene_list: List[Tuple[FrameTimecode,
"""Handles the `split-video` command."""
if not context.split_video:
return

output_path_template = context.split_name_format
# Add proper extension to filename template if required.
dot_pos = output_path_template.rfind('.')
Expand All @@ -243,23 +238,23 @@ def _split_video(context: CliContext, scene_list: List[Tuple[FrameTimecode,
# Otherwise, if using ffmpeg, only add an extension if one doesn't exist.
elif not 2 <= extension_length <= 4:
output_path_template += '.mp4'

# Ensure the appropriate tool is available before handling split-video.
check_split_video_requirements(context.split_mkvmerge)

# Command can override global output directory setting.
output_dir = context.output_dir if context.split_dir is None else context.split_dir
if context.split_mkvmerge:
split_video_mkvmerge(
input_video_path=context.video_stream.path,
scene_list=scene_list,
output_dir=context.split_directory,
output_dir=output_dir,
output_file_template=output_path_template,
show_output=not (context.quiet_mode or context.split_quiet),
)
else:
split_video_ffmpeg(
input_video_path=context.video_stream.path,
scene_list=scene_list,
output_dir=context.split_directory,
output_dir=output_dir,
output_file_template=output_path_template,
arg_override=context.split_args,
show_progress=not context.quiet_mode,
Expand Down
36 changes: 18 additions & 18 deletions scenedetect/video_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,20 +99,9 @@ def is_ffmpeg_available() -> bool:
##


@dataclass
class SceneMetadata:
"""Information about the scenes being exported."""
index: int
"""0-based index of this scene."""
start: FrameTimecode
"""First frame."""
end: FrameTimecode
"""Last frame."""


@dataclass
class VideoMetadata:
"""Information about the video."""
"""Information about the video being split."""
name: str
"""Expected name of the video. May differ from `path`."""
path: Path
Expand All @@ -121,7 +110,18 @@ class VideoMetadata:
"""Total number of scenes that will be written."""


PathFormatter = ty.Callable[[SceneMetadata, VideoMetadata], ty.AnyStr]
@dataclass
class SceneMetadata:
"""Information about the scene being extracted."""
index: int
"""0-based index of this scene."""
start: FrameTimecode
"""First frame."""
end: FrameTimecode
"""Last frame."""


PathFormatter = ty.Callable[[VideoMetadata, SceneMetadata], ty.AnyStr]


def default_formatter(template: str) -> PathFormatter:
Expand All @@ -130,13 +130,13 @@ def default_formatter(template: str) -> PathFormatter:
`$VIDEO_NAME`, `$SCENE_NUMBER`, `$START_TIME`, `$END_TIME`, `$START_FRAME`, `$END_FRAME`
"""
MIN_DIGITS = 3
format_scene_number: PathFormatter = lambda scene, video: (
format_scene_number: PathFormatter = lambda video, scene: (
('%0' + str(max(MIN_DIGITS,
math.floor(math.log(video.total_scenes, 10)) + 1)) + 'd') %
(scene.index + 1))
formatter: PathFormatter = lambda scene, video: Template(template).safe_substitute(
formatter: PathFormatter = lambda video, scene: Template(template).safe_substitute(
VIDEO_NAME=video.name,
SCENE_NUMBER=format_scene_number(scene, video),
SCENE_NUMBER=format_scene_number(video, scene),
START_TIME=str(scene.start.get_timecode().replace(":", ";")),
END_TIME=str(scene.end.get_timecode().replace(":", ";")),
START_FRAME=str(scene.start.get_frames()),
Expand All @@ -157,7 +157,7 @@ def split_video_mkvmerge(
video_name: ty.Optional[str] = None,
show_output: bool = False,
suppress_output=None,
):
) -> int:
""" Calls the mkvmerge command on the input video, splitting it at the
passed timecodes, where each scene is written in sequence from 001.
Expand Down Expand Up @@ -246,7 +246,7 @@ def split_video_ffmpeg(
suppress_output=None,
hide_progress=None,
formatter: ty.Optional[PathFormatter] = None,
):
) -> int:
""" Calls the ffmpeg command on the input video, generating a new video for
each scene based on the start/end timecodes.
Expand Down
Loading

0 comments on commit 14daf89

Please sign in to comment.