From 9be43ba89034fb9ee3d3f70545dcb8f7cf8f6fc5 Mon Sep 17 00:00:00 2001 From: Breakthrough Date: Sun, 29 Sep 2024 23:42:03 -0400 Subject: [PATCH] [cli] Add ability to customize delimiters for CSV output (#423) --- scenedetect.cfg | 8 ++++++++ scenedetect/__main__.py | 4 ++-- scenedetect/_cli/__init__.py | 3 +++ scenedetect/_cli/commands.py | 4 ++++ scenedetect/_cli/config.py | 2 ++ scenedetect/scene_manager.py | 9 ++++++++- website/pages/changelog.md | 6 ++++++ 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/scenedetect.cfg b/scenedetect.cfg index bde791de..a615c668 100644 --- a/scenedetect.cfg +++ b/scenedetect.cfg @@ -266,6 +266,14 @@ # Display list of cut points generated from scene boundaries (yes/no). #display-cuts = yes +# Separator to use between columns in output file. Must be single (escaped) +# ASCII character. +#col-separator = , + +# Separator to use between rows in output file. Must be (escaped) ASCII +# characters. +#row-separator = \n + # Format to use for list of cut points (frames, seconds, timecode). #cut-format = timecode diff --git a/scenedetect/__main__.py b/scenedetect/__main__.py index ea6d6b0a..eeebe7b1 100755 --- a/scenedetect/__main__.py +++ b/scenedetect/__main__.py @@ -48,11 +48,11 @@ def main(): logger.info("Stopped.") if __debug__: raise - except BaseException as ex: + except Exception as ex: if __debug__: raise else: - logger.critical("Unhandled exception:", exc_info=ex) + logger.critical("ERROR: Unhandled exception:", exc_info=ex) raise SystemExit(1) from None diff --git a/scenedetect/_cli/__init__.py b/scenedetect/_cli/__init__.py index 208e4d1d..c96e004d 100644 --- a/scenedetect/_cli/__init__.py +++ b/scenedetect/_cli/__init__.py @@ -1064,14 +1064,17 @@ def list_scenes_command( scene_list_name_format = ctx.config.get_value("list-scenes", "filename", filename) list_scenes_args = { "cut_format": TimecodeFormat[ctx.config.get_value("list-scenes", "cut-format").upper()], + "col_separator": ctx.config.get_value("list-scenes", "col-separator"), "display_scenes": ctx.config.get_value("list-scenes", "display-scenes"), "display_cuts": ctx.config.get_value("list-scenes", "display-cuts"), + "row_separator": ctx.config.get_value("list-scenes", "row-separator"), "scene_list_output": not no_output_file, "scene_list_name_format": scene_list_name_format, "skip_cuts": skip_cuts or ctx.config.get_value("list-scenes", "skip-cuts"), "output_dir": scene_list_dir, "quiet": quiet or ctx.config.get_value("list-scenes", "quiet") or ctx.quiet_mode, } + # TODO(#423): Need to validate that col_separator is a 1-character string after decoding. ctx.add_command(cli_commands.list_scenes, list_scenes_args) diff --git a/scenedetect/_cli/commands.py b/scenedetect/_cli/commands.py index 83a23bf2..1826e6e6 100644 --- a/scenedetect/_cli/commands.py +++ b/scenedetect/_cli/commands.py @@ -76,6 +76,8 @@ def list_scenes( display_scenes: bool, display_cuts: bool, cut_format: str, + col_separator: str, + row_separator: str, ): """Handles the `list-scenes` command.""" # Write scene list CSV to if required. @@ -96,6 +98,8 @@ def list_scenes( scene_list=scenes, include_cut_list=not skip_cuts, cut_list=cuts, + col_separator=col_separator.encode("utf-8").decode("unicode_escape"), + row_separator=row_separator.encode("utf-8").decode("unicode_escape"), ) # Suppress output if requested. if quiet: diff --git a/scenedetect/_cli/config.py b/scenedetect/_cli/config.py index 3ea5babe..3d78bfd9 100644 --- a/scenedetect/_cli/config.py +++ b/scenedetect/_cli/config.py @@ -303,10 +303,12 @@ def format(self, timecode: FrameTimecode) -> str: }, "list-scenes": { "cut-format": "timecode", + "col-separator": ",", "display-cuts": True, "display-scenes": True, "filename": "$VIDEO_NAME-Scenes.csv", "output": None, + "row-separator": "\n", "no-output-file": False, "quiet": False, "skip-cuts": False, diff --git a/scenedetect/scene_manager.py b/scenedetect/scene_manager.py index f844ba57..45fa7eba 100644 --- a/scenedetect/scene_manager.py +++ b/scenedetect/scene_manager.py @@ -216,6 +216,8 @@ def write_scene_list( scene_list: SceneList, include_cut_list: bool = True, cut_list: Optional[CutList] = None, + col_separator: str = ",", + row_separator: str = "\n", ) -> None: """Writes the given list of scenes to an output file handle in CSV format. @@ -227,8 +229,13 @@ def write_scene_list( cut_list: Optional list of FrameTimecode objects denoting the cut list (i.e. the frames in the video that need to be split to generate individual scenes). If not specified, the cut list is generated using the start times of each scene following the first one. + delimiter: Delimiter to use between values. Must be single character. + lineterminator: Line terminator to use between rows. + + Raises: + TypeError: "delimiter" must be a 1-character string """ - csv_writer = csv.writer(output_csv_file, lineterminator="\n") + csv_writer = csv.writer(output_csv_file, delimiter=col_separator, lineterminator=row_separator) # If required, output the cutting list as the first row (i.e. before the header row). if include_cut_list: csv_writer.writerow( diff --git a/website/pages/changelog.md b/website/pages/changelog.md index e4348b7e..0300bfad 100644 --- a/website/pages/changelog.md +++ b/website/pages/changelog.md @@ -4,6 +4,12 @@ Releases ## PySceneDetect 0.6 +### 0.6.5 (TBD) + + - [api] Add `col_separator` and `row_separator` args to `write_scene_list` function in `scenedetect.scene_manager` + - [cli] Add ability to configure CSV separators for rows/columns in config file [#423](https://github.com/Breakthrough/PySceneDetect/issues/423) + + ### 0.6.4 (June 10, 2024) #### Release Notes