Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions scenedetect/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@


def write_scene_list(
output_csv_file: ty.TextIO,
output_csv_file: ty.TextIO | str | Path,
scene_list: SceneList,
include_cut_list: bool = True,
cut_list: CutList | None = None,
Expand All @@ -79,7 +79,8 @@ def write_scene_list(
"""Writes the given list of scenes to an output file handle in CSV format.

Arguments:
output_csv_file: Handle to open file in write mode.
output_csv_file: Handle to open file in write mode, or a filesystem path. A path is
opened with a context manager so the file is closed after writing.
scene_list: List of pairs of FrameTimecodes denoting each scene's start/end FrameTimecode.
include_cut_list: Bool indicating if the first row should include the timecodes where
each scene starts. Should be set to False if RFC 4180 compliant CSV output is required.
Expand All @@ -92,6 +93,17 @@ def write_scene_list(
Raises:
TypeError: "delimiter" must be a 1-character string
"""
if isinstance(output_csv_file, (str, Path)):
with open(output_csv_file, "w", newline="") as file_handle:
write_scene_list(
file_handle,
scene_list,
include_cut_list=include_cut_list,
cut_list=cut_list,
col_separator=col_separator,
row_separator=row_separator,
)
return
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:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"""Tests for scenedetect.output module."""

import json
from io import StringIO
from fractions import Fraction
from pathlib import Path
from xml.etree import ElementTree
Expand All @@ -31,6 +32,7 @@
VideoMetadata,
is_ffmpeg_available,
split_video_ffmpeg,
write_scene_list,
write_scene_list_edl,
write_scene_list_fcp7,
write_scene_list_fcpx,
Expand Down Expand Up @@ -248,6 +250,34 @@ def _fake_scenes(fps: Fraction, frames):
return [(FrameTimecode(start, fps=fps), FrameTimecode(end, fps=fps)) for start, end in frames]


def test_write_scene_list_file_handle():
"""Existing callers that pass an open file handle keep working."""
scenes = _fake_scenes(_FPS_CFR, [(0, 30), (30, 60)])
buf = StringIO()
write_scene_list(buf, scenes, include_cut_list=False)
text = buf.getvalue()
assert "Scene Number" in text
assert "00:00:00.000" in text or "00:00:00:00" in text


def test_write_scene_list_accepts_str_path(tmp_path: Path):
"""A filesystem path string must not raise TypeError from csv.writer."""
scenes = _fake_scenes(_FPS_CFR, [(0, 30)])
output_path = tmp_path / "scenes.csv"
write_scene_list(str(output_path), scenes, include_cut_list=False)
assert output_path.exists()
assert "Scene Number" in output_path.read_text()


def test_write_scene_list_accepts_path(tmp_path: Path):
"""pathlib.Path is opened with a context manager and closed after write."""
scenes = _fake_scenes(_FPS_CFR, [(0, 30)])
output_path = tmp_path / "scenes.csv"
write_scene_list(output_path, scenes, include_cut_list=False)
assert output_path.exists()
assert "Scene Number" in output_path.read_text()


def test_write_scene_list_edl(tmp_path: Path):
"""EDL output has title header, FCM line, and one event per scene in CMX 3600 format."""
scenes = _fake_scenes(_FPS_CFR, [(0, 30), (30, 60)])
Expand Down
Loading