Skip to content

Commit e81ba12

Browse files
authored
Add deprecation warnings for legacy framerate aliases (#563)
Closes #548 ## Summary * Emit `DeprecationWarning` for deprecated `framerate` aliases across `FrameTimecode`, `open_video()`, video backends, and the CLI. * Preserve existing compatibility behavior, including `frame_rate` taking precedence when both forms are provided. * Add/update tests for the deprecated aliases, including `VideoCaptureAdapter`. * Update `get_framerate()` to direct users to `frame_rate` without emitting duplicate warnings. ## Testing * Added warning assertions for deprecated aliases. * Verified canonical `frame_rate` usage remains warning-free. * Verified legacy aliases continue to work while emitting `DeprecationWarning`.
2 parents 953233c + 5c487e8 commit e81ba12

14 files changed

Lines changed: 149 additions & 70 deletions

File tree

docs/cli.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Options
6767

6868
.. option:: --framerate FPS
6969

70-
[DEPRECATED] Use :option:`-f/--frame-rate <-f>` instead.
70+
Alias of :option:`-f/--frame-rate <-f>`.
7171

7272
.. option:: -m TIMECODE, --min-scene-len TIMECODE
7373

docs/cli/backends.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The `OpenCV <https://opencv.org/>`_ backend (usually `opencv-python <https://pyp
1919

2020
It is mostly reliable and fast, although can occasionally run into issues processing videos with multiple audio tracks or small amounts of frame corruption. You can use a custom version of the ``cv2`` package, or install either the `opencv-python` or `opencv-python-headless` packages from `pip`.
2121

22-
The OpenCV backend also supports image sequences as inputs (e.g. ``frame%02d.jpg`` if you want to load frame001.jpg, frame002.jpg, frame003.jpg...). Make sure to specify the framerate manually (``-f``/``--framerate``) to ensure accurate timing calculations.
22+
The OpenCV backend also supports image sequences as inputs (e.g. ``frame%02d.jpg`` if you want to load frame001.jpg, frame002.jpg, frame003.jpg...). Make sure to specify the framerate manually (``-f``/``--frame-rate``) to ensure accurate timing calculations.
2323

2424
Variable framerate (VFR) video is supported. Scene detection uses PTS-derived timestamps from ``CAP_PROP_POS_MSEC`` for accurate timecodes. Seeking compensates for OpenCV's average-fps-based internal seek approximation, so output timecodes remain accurate across the full video.
2525

scenedetect/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
:class:`SceneManager <scenedetect.scene_manager.SceneManager>`.
1616
"""
1717

18+
import warnings
1819
from logging import getLogger
1920

2021
# OpenCV is a required package, but we don't have it as an explicit dependency since we
@@ -115,8 +116,13 @@ def open_video(
115116
:class:`VideoOpenFailure`: Constructing the VideoStream fails. If multiple backends have
116117
been attempted, the error from the first backend will be returned.
117118
"""
118-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
119-
# used, once internal callers and downstream users have had a release to migrate.
119+
if framerate is not None:
120+
warnings.warn(
121+
"`framerate` is deprecated and scheduled for removal in v0.9; "
122+
"use `frame_rate` instead.",
123+
DeprecationWarning,
124+
stacklevel=2,
125+
)
120126
if frame_rate is None:
121127
frame_rate = framerate
122128
# A list of paths is opened as a single concatenated stream. VideoStreamConcat handles

scenedetect/_cli/__init__.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,15 @@ def print_command_help(ctx: click.Context, command: click.Command):
237237
default=None,
238238
help="Override frame rate with value as frames/sec.",
239239
)
240+
# Keep --framerate separate so Click can hide it while mapping both spellings to frame_rate.
240241
@click.option(
241242
"--framerate",
242-
"framerate_legacy",
243+
"frame_rate",
243244
metavar="FPS",
244245
type=click.FLOAT,
245246
default=None,
246247
hidden=True,
247-
help="[DEPRECATED] Use -f/--frame-rate instead.",
248+
help="Alias of -f/--frame-rate.",
248249
)
249250
@click.option(
250251
"--min-scene-len",
@@ -346,7 +347,6 @@ def scenedetect(
346347
stats: str | None,
347348
config: str | None,
348349
frame_rate: float | None,
349-
framerate_legacy: float | None,
350350
min_scene_len: str | None,
351351
drop_short_scenes: bool | None,
352352
merge_last_scene: bool | None,
@@ -361,13 +361,6 @@ def scenedetect(
361361
ctx = ctx.obj
362362
assert isinstance(ctx, CliContext)
363363

364-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `--framerate`
365-
# is used, once downstream users have had a release to migrate to `--frame-rate`.
366-
if frame_rate is None:
367-
frame_rate = framerate_legacy
368-
elif framerate_legacy is not None:
369-
logger.warning("Both --frame-rate and --framerate were specified; using --frame-rate.")
370-
371364
ctx.handle_options(
372365
input_path=input,
373366
output=output,

scenedetect/backends/moviepy.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import os
2020
import time
2121
import typing as ty
22+
import warnings
2223
from fractions import Fraction
2324
from logging import getLogger
2425

@@ -94,8 +95,13 @@ def __init__(
9495
"""
9596
super().__init__()
9697

97-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
98-
# used, once internal callers and downstream users have had a release to migrate.
98+
if framerate is not None:
99+
warnings.warn(
100+
"`framerate` is deprecated and scheduled for removal in v0.9; "
101+
"use `frame_rate` instead.",
102+
DeprecationWarning,
103+
stacklevel=2,
104+
)
99105
if frame_rate is None:
100106
frame_rate = framerate
101107
# TODO: Investigate how MoviePy handles ffmpeg not being on PATH.

scenedetect/backends/opencv.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,13 @@ def __init__(
101101
ValueError: specified frame rate is invalid
102102
"""
103103
super().__init__()
104-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
105-
# used, once internal callers and downstream users have had a release to migrate.
104+
if framerate is not None:
105+
warnings.warn(
106+
"`framerate` is deprecated and scheduled for removal in v0.9; "
107+
"use `frame_rate` instead.",
108+
DeprecationWarning,
109+
stacklevel=2,
110+
)
106111
if frame_rate is None:
107112
frame_rate = framerate
108113
if path_or_device is not None:
@@ -395,8 +400,13 @@ def __init__(
395400
"""
396401
super().__init__()
397402

398-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
399-
# used, once internal callers and downstream users have had a release to migrate.
403+
if framerate is not None:
404+
warnings.warn(
405+
"`framerate` is deprecated and scheduled for removal in v0.9; "
406+
"use `frame_rate` instead.",
407+
DeprecationWarning,
408+
stacklevel=2,
409+
)
400410
if frame_rate is None:
401411
frame_rate = framerate
402412
if frame_rate is not None and frame_rate < MAX_FPS_DELTA:

scenedetect/backends/pyav.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import os
1515
import typing as ty
16+
import warnings
1617
from fractions import Fraction
1718
from logging import getLogger
1819

@@ -89,8 +90,13 @@ def __init__(
8990
# refinement for frames FFmpeg flags as corrupt but still decodes.
9091
super().__init__()
9192

92-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
93-
# used, once internal callers and downstream users have had a release to migrate.
93+
if framerate is not None:
94+
warnings.warn(
95+
"`framerate` is deprecated and scheduled for removal in v0.9; "
96+
"use `frame_rate` instead.",
97+
DeprecationWarning,
98+
stacklevel=2,
99+
)
94100
if frame_rate is None:
95101
frame_rate = framerate
96102
# Ensure specified frame rate is valid if set.

scenedetect/common.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,12 @@ def framerate(self) -> float | None:
296296
property returns an exact :class:`fractions.Fraction` and matches the naming used by
297297
:attr:`scenedetect.video_stream.VideoStream.frame_rate`.
298298
"""
299-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning here once internal
300-
# callers and downstream users have had a release to migrate to `frame_rate`.
299+
warnings.warn(
300+
"`framerate` is deprecated and scheduled for removal in v0.9; "
301+
"use `frame_rate` instead.",
302+
DeprecationWarning,
303+
stacklevel=2,
304+
)
301305
if self._rate is None:
302306
return None
303307
return float(self._rate)
@@ -335,16 +339,18 @@ def get_frames(self) -> int:
335339
def get_framerate(self) -> float | None:
336340
"""[DEPRECATED] Get Framerate: Returns the framerate used by the FrameTimecode object.
337341
338-
Use the `framerate` property instead.
342+
Use the `frame_rate` property instead.
339343
340344
:meta private:
341345
"""
342346
warnings.warn(
343-
"get_framerate() is deprecated, use the `framerate` property instead.",
347+
"get_framerate() is deprecated, use the `frame_rate` property instead.",
344348
DeprecationWarning,
345349
stacklevel=2,
346350
)
347-
return self.framerate
351+
if self.frame_rate is None:
352+
return None
353+
return float(self.frame_rate)
348354

349355
def equal_frame_rate(self, other: "float | Fraction | FrameTimecode") -> bool:
350356
"""Determine whether the passed frame rate equals this object's frame rate.
@@ -368,8 +374,12 @@ def equal_frame_rate(self, other: "float | Fraction | FrameTimecode") -> bool:
368374

369375
def equal_framerate(self, fps) -> bool:
370376
"""[DEPRECATED] Use :meth:`equal_frame_rate` instead."""
371-
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning here once internal
372-
# callers and downstream users have had a release to migrate to `equal_frame_rate`.
377+
warnings.warn(
378+
"`equal_framerate()` is deprecated and scheduled for removal in v0.9; "
379+
"use `equal_frame_rate()` instead.",
380+
DeprecationWarning,
381+
stacklevel=2,
382+
)
373383
return self.equal_frame_rate(fps)
374384

375385
@property

scenedetect/video_stream.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class FrameRateUnavailable(VideoOpenFailure):
6666

6767
def __init__(self):
6868
super().__init__(
69-
"Unable to obtain video framerate! Specify `framerate` manually, or"
69+
"Unable to obtain video framerate! Specify `frame_rate` manually, or"
7070
" re-encode/re-mux the video and try again."
7171
)
7272

tests/test_api.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
1414
These tests demonstrate common workflow patterns used when integrating the PySceneDetect API."""
1515

16+
import pytest
17+
1618

1719
def test_api_detect(test_video_file: str):
1820
"""Demonstrate usage of the `detect()` function to process a complete video."""
@@ -73,15 +75,17 @@ def test_api_scene_manager_start_end_time(test_video_file: str):
7375

7476

7577
def test_api_open_video_framerate_legacy_alias(test_video_file: str):
76-
"""`open_video(framerate=...)` is the soft-deprecated alias for `frame_rate=` (issue #548).
78+
"""`open_video(framerate=...)` is the deprecated alias for `frame_rate=` (issue #548).
7779
Both forms must produce equivalent streams; when both are provided, `frame_rate` wins."""
7880
from scenedetect import open_video
7981

80-
legacy = open_video(test_video_file, framerate=30.0)
82+
with pytest.warns(DeprecationWarning, match="frame_rate"):
83+
legacy = open_video(test_video_file, framerate=30.0)
8184
canonical = open_video(test_video_file, frame_rate=30.0)
8285
assert legacy.frame_rate == canonical.frame_rate
8386
# `frame_rate` takes precedence over `framerate` when both are provided.
84-
both = open_video(test_video_file, frame_rate=30.0, framerate=24.0)
87+
with pytest.warns(DeprecationWarning, match="frame_rate"):
88+
both = open_video(test_video_file, frame_rate=30.0, framerate=24.0)
8589
assert both.frame_rate == canonical.frame_rate
8690

8791

0 commit comments

Comments
 (0)