diff --git a/scenedetect.cfg b/scenedetect.cfg index 83c1d925..33a83d43 100644 --- a/scenedetect.cfg +++ b/scenedetect.cfg @@ -269,6 +269,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/_cli/__init__.py b/scenedetect/_cli/__init__.py index c26b6263..1b2c3d4e 100644 --- a/scenedetect/_cli/__init__.py +++ b/scenedetect/_cli/__init__.py @@ -1114,6 +1114,7 @@ def list_scenes_command( output_dir = ctx.config.get_value("list-scenes", "output", output) name_format = ctx.config.get_value("list-scenes", "filename", filename) list_scenes_args = { + "col_separator": ctx.config.get_value("list-scenes", "col-separator"), "cut_format": ctx.config.get_value("list-scenes", "cut-format"), "display_scenes": ctx.config.get_value("list-scenes", "display-scenes"), "display_cuts": ctx.config.get_value("list-scenes", "display-cuts"), @@ -1122,7 +1123,9 @@ def list_scenes_command( "skip_cuts": ctx.config.get_value("list-scenes", "skip-cuts", skip_cuts), "output_dir": output_dir, "quiet": ctx.config.get_value("list-scenes", "quiet", quiet) or ctx.quiet_mode, + "row_separator": ctx.config.get_value("list-scenes", "row-separator"), } + # 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 17b6b5c9..8ebc3700 100644 --- a/scenedetect/_cli/commands.py +++ b/scenedetect/_cli/commands.py @@ -105,6 +105,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. @@ -125,6 +127,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 2236ac43..1a863295 100644 --- a/scenedetect/_cli/config.py +++ b/scenedetect/_cli/config.py @@ -303,11 +303,13 @@ def format(self, timecode: FrameTimecode) -> str: "show": False, }, "list-scenes": { + "col-separator": ",", "cut-format": TimecodeFormat.TIMECODE, "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 43bc46a9..7a8b962e 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 3fc83772..865fccf6 100644 --- a/website/pages/changelog.md +++ b/website/pages/changelog.md @@ -596,4 +596,6 @@ Development - [improvement] `save_to_csv` now works with paths from `pathlib` - [bugfix] Fix `SyntaxWarning` due to incorrect escaping [#400](https://github.com/Breakthrough/PySceneDetect/issues/400) - [bugfix] Fix `ContentDetector` crash when using callbacks [#416](https://github.com/Breakthrough/PySceneDetect/issues/416) [#420](https://github.com/Breakthrough/PySceneDetect/issues/420) - + - [api] The `save_to_csv` function now works correctly with paths from the `pathlib` module + - [api] Add `col_separator` and `row_separator` args to `write_scene_list` function in `scenedetect.scene_manager` + - [feature] Add ability to configure CSV separators for rows/columns in config file [#423](https://github.com/Breakthrough/PySceneDetect/issues/423)