Skip to content

Commit 7615a65

Browse files
committed
[api][cli] Rename temporal margin to margin
Much shorter and more concise.
1 parent f1703c2 commit 7615a65

File tree

6 files changed

+57
-67
lines changed

6 files changed

+57
-67
lines changed

scenedetect.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@
232232
#frame-margin = 1
233233

234234
# Amount of time to ignore at the beginning/end of a shot when selecting frames.
235-
#temporal-margin = 0.04s
235+
#margin = 0.04s
236236

237237
# Resize by scale factor (0.5 = half, 1.0 = same, 2.0 = double).
238238
#scale = 1.0

scenedetect/_cli/__init__.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,17 +1401,17 @@ def split_video_command(
14011401
metavar="N",
14021402
default=None,
14031403
type=click.INT,
1404-
help="[DEPRECATED] Use --temporal-margin instead. Number of frames to ignore at beginning/end of scenes when saving images.%s"
1404+
help="[DEPRECATED] Use --margin instead. Number of frames to ignore at beginning/end of scenes when saving images.%s"
14051405
% (USER_CONFIG.get_help_string("save-images", "frame-margin")),
14061406
)
14071407
@click.option(
14081408
"-M",
1409-
"--temporal-margin",
1409+
"--margin",
14101410
metavar="TIME",
14111411
default=None,
14121412
type=click.STRING,
14131413
help="Amount of time to ignore at the beginning/end of each scene. Discards frame-margin if set. Can be specified as seconds (0.1), frames (3), or timecode (00:00:00.100).%s"
1414-
% (USER_CONFIG.get_help_string("save-images", "temporal-margin")),
1414+
% (USER_CONFIG.get_help_string("save-images", "margin")),
14151415
)
14161416
@click.option(
14171417
"--scale",
@@ -1452,7 +1452,7 @@ def save_images_command(
14521452
png: bool = False,
14531453
compression: ty.Optional[int] = None,
14541454
frame_margin: ty.Optional[int] = None,
1455-
temporal_margin: ty.Optional[str] = None,
1455+
margin: ty.Optional[str] = None,
14561456
scale: ty.Optional[float] = None,
14571457
height: ty.Optional[int] = None,
14581458
width: ty.Optional[int] = None,
@@ -1498,13 +1498,11 @@ def save_images_command(
14981498
raise click.BadParameter("\n".join(error_strs), param_hint="save-images")
14991499
output = ctx.config.get_value("save-images", "output", output)
15001500

1501-
# Get temporal_margin value (from CLI arg or config), converting to FrameTimecode
1502-
temporal_margin_value = ctx.config.get_value("save-images", "temporal-margin", temporal_margin)
1503-
temporal_margin_tc = None
1504-
if temporal_margin_value is not None:
1505-
temporal_margin_tc = FrameTimecode(
1506-
timecode=temporal_margin_value, fps=ctx.video_stream.frame_rate
1507-
)
1501+
# Get margin value (from CLI arg or config), converting to FrameTimecode
1502+
margin_value = ctx.config.get_value("save-images", "margin", margin)
1503+
margin_tc = None
1504+
if margin_value is not None:
1505+
margin_tc = FrameTimecode(timecode=margin_value, fps=ctx.video_stream.frame_rate)
15081506

15091507
save_images_args = {
15101508
"encoder_param": compression if png else quality,
@@ -1517,7 +1515,7 @@ def save_images_command(
15171515
"output": output,
15181516
"scale": scale,
15191517
"show_progress": not ctx.quiet_mode,
1520-
"temporal_margin": temporal_margin_tc,
1518+
"margin": margin_tc,
15211519
"threading": ctx.config.get_value("save-images", "threading"),
15221520
"width": width,
15231521
}

scenedetect/_cli/commands.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def save_images(
191191
width: int,
192192
interpolation: Interpolation,
193193
threading: bool,
194-
temporal_margin: ty.Optional["FrameTimecode"] = None,
194+
margin: ty.Optional["FrameTimecode"] = None,
195195
):
196196
"""Handles the `save-images` command."""
197197
del cuts # save-images only uses scenes.
@@ -211,7 +211,7 @@ def save_images(
211211
width=width,
212212
interpolation=interpolation,
213213
threading=threading,
214-
temporal_margin=temporal_margin,
214+
margin=margin,
215215
)
216216
# Save the result for use by `save-html` if required.
217217
context.save_images_result = (images, output)

scenedetect/_cli/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ class XmlFormat(Enum):
419419
"quality": RangeValue(_PLACEHOLDER, min_val=0, max_val=100),
420420
"scale": 1.0,
421421
"scale-method": Interpolation.LINEAR,
422-
"temporal-margin": TimecodeValue("0.04s"),
422+
"margin": TimecodeValue("0.04s"),
423423
"threading": True,
424424
"width": 0,
425425
},

scenedetect/output/image.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(
7777
height: ty.Optional[int] = None,
7878
width: ty.Optional[int] = None,
7979
interpolation: Interpolation = Interpolation.CUBIC,
80-
temporal_margin: ty.Optional[FrameTimecode] = None,
80+
margin: ty.Optional[FrameTimecode] = None,
8181
):
8282
"""Multi-threaded implementation of save-images functionality. Uses background threads to
8383
handle image encoding and saving images to disk to improve parallelism.
@@ -89,7 +89,7 @@ def __init__(
8989
frame_margin: [DEPRECATED] Number of frames to pad each scene around the beginning
9090
and end (e.g. moves the first/last image into the scene by N frames).
9191
Can set to 0, but will result in some video files failing to extract
92-
the very last frame. Use `temporal_margin` instead.
92+
the very last frame. Use `margin` instead.
9393
image_extension: Type of image to save (must be one of 'jpg', 'png', or 'webp').
9494
encoder_param: Quality/compression efficiency, based on type of image:
9595
'jpg' / 'webp': Quality 0-100, higher is better quality. 100 is lossless for webp.
@@ -110,14 +110,14 @@ def __init__(
110110
Specifying only width will rescale the image to that number of pixels wide
111111
while preserving the aspect ratio.
112112
interpolation: Type of interpolation to use when resizing images.
113-
temporal_margin: Amount of time to ignore at the beginning/end of a scene when
113+
margin: Amount of time to ignore at the beginning/end of a scene when
114114
selecting frames. Can be specified as frames (int), seconds (float), or timecode
115115
string when creating the FrameTimecode. Uses presentation time (PTS) for selection.
116116
When set, takes precedence over `frame_margin`.
117117
"""
118118
self._num_images = num_images
119119
self._frame_margin = frame_margin
120-
self._temporal_margin = temporal_margin
120+
self._margin = margin
121121
self._image_extension = image_extension
122122
self._image_name_template = image_name_template
123123
self._scale = scale
@@ -301,12 +301,12 @@ def _generate_scene_timecodes(
301301
) -> ty.Iterable[FrameTimecode]:
302302
"""Generate timecodes for images to extract from a single scene.
303303
304-
Uses temporal_margin to determine the effective time range, then distributes
304+
Uses margin to determine the effective time range, then distributes
305305
images evenly across that range using time-based arithmetic.
306306
"""
307-
# Use temporal_margin if set, otherwise fall back to frame_margin converted to time
308-
if self._temporal_margin is not None:
309-
margin = self._temporal_margin
307+
# Use margin if set, otherwise fall back to frame_margin converted to time
308+
if self._margin is not None:
309+
margin = self._margin
310310
elif self._frame_margin > 0:
311311
margin = FrameTimecode(self._frame_margin, fps=start.framerate)
312312
else:
@@ -371,7 +371,7 @@ def save_images(
371371
width: ty.Optional[int] = None,
372372
interpolation: Interpolation = Interpolation.CUBIC,
373373
threading: bool = True,
374-
temporal_margin: ty.Optional[FrameTimecode] = None,
374+
margin: ty.Optional[FrameTimecode] = None,
375375
) -> ty.Dict[int, ty.List[str]]:
376376
"""Save a set number of images from each scene, given a list of scenes
377377
and the associated video/frame source.
@@ -385,7 +385,7 @@ def save_images(
385385
frame_margin: Number of frames to pad each scene around the beginning
386386
and end (e.g. moves the first/last image into the scene by N frames).
387387
Can set to 0, but will result in some video files failing to extract
388-
the very last frame. Discarded if `temporal_margin` is set.
388+
the very last frame. Discarded if `margin` is set.
389389
image_extension: Type of image to save (must be one of 'jpg', 'png', or 'webp').
390390
encoder_param: Quality/compression efficiency, based on type of image:
391391
'jpg' / 'webp': Quality 0-100, higher is better quality. 100 is lossless for webp.
@@ -410,7 +410,7 @@ def save_images(
410410
while preserving the aspect ratio.
411411
interpolation: Type of interpolation to use when resizing images.
412412
threading: Offload image encoding and disk IO to background threads to improve performance.
413-
temporal_margin: Amount of time to pad each scene around the beginning and end. Takes
413+
margin: Amount of time to pad each scene around the beginning and end. Takes
414414
precedence over `frame_margin` when set. Can be created from seconds (float), frames
415415
(int), or timecode string.
416416
@@ -449,7 +449,7 @@ def save_images(
449449
height,
450450
width,
451451
interpolation,
452-
temporal_margin,
452+
margin,
453453
)
454454
return extractor.run(video, scene_list, output_dir, show_progress)
455455

@@ -473,7 +473,7 @@ def save_images(
473473
extractor = _ImageExtractor(
474474
num_images=num_images,
475475
frame_margin=frame_margin,
476-
temporal_margin=temporal_margin,
476+
margin=margin,
477477
)
478478
timecode_list = [list(tc) for tc in extractor.generate_timecode_list(scene_list)]
479479

tests/test_output.py

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -202,22 +202,20 @@ def test_deprecated_output_modules_emits_warning_on_import():
202202
from scenedetect.video_splitter import split_video_ffmpeg as _
203203

204204

205-
class TestImageExtractorTemporalMargin:
206-
"""Tests for _ImageExtractor temporal margin functionality using PTS-based selection."""
205+
class TestImageExtractorMargin:
206+
"""Tests for _ImageExtractor margin functionality using PTS-based selection."""
207207

208-
def test_temporal_margin_uses_seconds_not_frames(self):
209-
"""Test that temporal_margin operates on presentation time, not frame count.
208+
def test_margin_uses_seconds_not_frames(self):
209+
"""Test that margin operates on presentation time, not frame count.
210210
211211
With a 0.1s margin on a scene from 0s to 3s at 30fps:
212212
- First image should be at ~0.1s (frame 3)
213213
- Last image should be at ~2.9s (frame 87)
214214
"""
215215
from scenedetect.output.image import _ImageExtractor
216216

217-
# 30 fps, 0.1s temporal margin
218-
extractor = _ImageExtractor(
219-
num_images=3, temporal_margin=FrameTimecode(timecode=0.1, fps=30.0)
220-
)
217+
# 30 fps, 0.1s margin
218+
extractor = _ImageExtractor(num_images=3, margin=FrameTimecode(timecode=0.1, fps=30.0))
221219

222220
# Scene from frame 0 to 90 (0s to 3s at 30fps)
223221
scene_list = [
@@ -233,18 +231,16 @@ def test_temporal_margin_uses_seconds_not_frames(self):
233231
# Last image: end.seconds - margin = 3.0 - 0.1 = 2.9s → frame 87
234232
assert timecodes[2].seconds == pytest.approx(2.9, abs=0.05)
235233

236-
def test_temporal_margin_different_framerates(self):
237-
"""Test temporal margin works consistently across different framerates.
234+
def test_margin_different_framerates(self):
235+
"""Test margin works consistently across different framerates.
238236
239-
The same temporal margin (0.1s) should result in different frame offsets
237+
The same margin (0.1s) should result in different frame offsets
240238
but the same time offset regardless of framerate.
241239
"""
242240
from scenedetect.output.image import _ImageExtractor
243241

244242
for fps in [24.0, 25.0, 30.0, 60.0]:
245-
extractor = _ImageExtractor(
246-
num_images=3, temporal_margin=FrameTimecode(timecode=0.1, fps=fps)
247-
)
243+
extractor = _ImageExtractor(num_images=3, margin=FrameTimecode(timecode=0.1, fps=fps))
248244
# 3 second scene
249245
scene_list = [
250246
(FrameTimecode(0, fps=fps), FrameTimecode(int(3 * fps), fps=fps)),
@@ -256,14 +252,12 @@ def test_temporal_margin_different_framerates(self):
256252
assert timecodes[0].seconds == pytest.approx(0.1, abs=0.05), f"Failed at {fps}fps"
257253
assert timecodes[2].seconds == pytest.approx(2.9, abs=0.05), f"Failed at {fps}fps"
258254

259-
def test_temporal_margin_clamped_to_scene_bounds(self):
260-
"""Test that temporal margin is clamped when scene is shorter than 2x margin."""
255+
def test_margin_clamped_to_scene_bounds(self):
256+
"""Test that margin is clamped when scene is shorter than 2x margin."""
261257
from scenedetect.output.image import _ImageExtractor
262258

263259
# 0.5s margin on a 0.5s scene - should clamp to scene bounds
264-
extractor = _ImageExtractor(
265-
num_images=3, temporal_margin=FrameTimecode(timecode=0.5, fps=30.0)
266-
)
260+
extractor = _ImageExtractor(num_images=3, margin=FrameTimecode(timecode=0.5, fps=30.0))
267261

268262
# Scene from frame 0 to 15 (0s to 0.5s at 30fps)
269263
scene_list = [
@@ -276,13 +270,11 @@ def test_temporal_margin_clamped_to_scene_bounds(self):
276270
for tc in timecodes:
277271
assert 0.0 <= tc.seconds <= 0.5
278272

279-
def test_temporal_margin_zero(self):
280-
"""Test that zero temporal margin selects frames at scene boundaries."""
273+
def test_margin_zero(self):
274+
"""Test that zero margin selects frames at scene boundaries."""
281275
from scenedetect.output.image import _ImageExtractor
282276

283-
extractor = _ImageExtractor(
284-
num_images=3, temporal_margin=FrameTimecode(timecode=0.0, fps=30.0)
285-
)
277+
extractor = _ImageExtractor(num_images=3, margin=FrameTimecode(timecode=0.0, fps=30.0))
286278

287279
scene_list = [
288280
(FrameTimecode(30, fps=30.0), FrameTimecode(90, fps=30.0)),
@@ -295,11 +287,11 @@ def test_temporal_margin_zero(self):
295287
# Last image near scene end (3s)
296288
assert timecodes[2].seconds == pytest.approx(2.97, abs=0.1)
297289

298-
def test_temporal_margin_with_pts_timecodes(self):
299-
"""Test temporal margin works correctly with PTS-based FrameTimecodes.
290+
def test_margin_with_pts_timecodes(self):
291+
"""Test margin works correctly with PTS-based FrameTimecodes.
300292
301293
PTS (Presentation Time Stamp) based timecodes use a time_base rather than
302-
a fixed framerate. This test verifies that temporal margin calculations
294+
a fixed framerate. This test verifies that margin calculations
303295
work correctly when scenes are defined using PTS.
304296
"""
305297
from fractions import Fraction
@@ -314,10 +306,10 @@ def test_temporal_margin_with_pts_timecodes(self):
314306
start = FrameTimecode(timecode=Timecode(pts=0, time_base=time_base), fps=30.0)
315307
end = FrameTimecode(timecode=Timecode(pts=3000, time_base=time_base), fps=30.0)
316308

317-
# 100ms (0.1s) temporal margin, also as PTS-based
309+
# 100ms (0.1s) margin, also as PTS-based
318310
margin = FrameTimecode(timecode=Timecode(pts=100, time_base=time_base), fps=30.0)
319311

320-
extractor = _ImageExtractor(num_images=3, temporal_margin=margin)
312+
extractor = _ImageExtractor(num_images=3, margin=margin)
321313

322314
scene_list = [(start, end)]
323315
timecode_list = extractor.generate_timecode_list(scene_list)
@@ -330,7 +322,7 @@ def test_temporal_margin_with_pts_timecodes(self):
330322
# Last image: 3s - 0.1s margin = 2.9s
331323
assert timecodes[2].seconds == pytest.approx(2.9, abs=0.05)
332324

333-
def test_temporal_margin_pts_preserves_time_base(self):
325+
def test_margin_pts_preserves_time_base(self):
334326
"""Test that output timecodes preserve the time_base from input PTS timecodes."""
335327
from fractions import Fraction
336328

@@ -346,7 +338,7 @@ def test_temporal_margin_pts_preserves_time_base(self):
346338
# 0.1s margin = 9000 pts at 1/90000 time_base
347339
margin = FrameTimecode(timecode=Timecode(pts=9000, time_base=time_base), fps=30.0)
348340

349-
extractor = _ImageExtractor(num_images=2, temporal_margin=margin)
341+
extractor = _ImageExtractor(num_images=2, margin=margin)
350342

351343
scene_list = [(start, end)]
352344
timecode_list = extractor.generate_timecode_list(scene_list)
@@ -357,7 +349,7 @@ def test_temporal_margin_pts_preserves_time_base(self):
357349
assert timecodes[1].seconds == pytest.approx(1.9, abs=0.01)
358350

359351
def test_frame_margin_backwards_compatibility(self):
360-
"""Test that frame_margin still works when temporal_margin is not set.
352+
"""Test that frame_margin still works when margin is not set.
361353
362354
This ensures backwards compatibility with existing code using frame_margin.
363355
"""
@@ -380,16 +372,16 @@ def test_frame_margin_backwards_compatibility(self):
380372
# Last image: 3s - 3 frames = 2.9s
381373
assert timecodes[2].seconds == pytest.approx(2.9, abs=0.05)
382374

383-
def test_temporal_margin_overrides_frame_margin(self):
384-
"""Test that temporal_margin takes precedence over frame_margin when both are set."""
375+
def test_margin_overrides_frame_margin(self):
376+
"""Test that margin takes precedence over frame_margin when both are set."""
385377
from scenedetect.output.image import _ImageExtractor
386378

387-
# Set frame_margin to 30 frames (1s at 30fps), but temporal_margin to 0.1s
388-
# temporal_margin should win
379+
# Set frame_margin to 30 frames (1s at 30fps), but margin to 0.1s
380+
# margin should win
389381
extractor = _ImageExtractor(
390382
num_images=3,
391383
frame_margin=30, # Would be 1s at 30fps
392-
temporal_margin=FrameTimecode(timecode=0.1, fps=30.0), # 0.1s
384+
margin=FrameTimecode(timecode=0.1, fps=30.0), # 0.1s
393385
)
394386

395387
scene_list = [
@@ -398,6 +390,6 @@ def test_temporal_margin_overrides_frame_margin(self):
398390
timecode_list = extractor.generate_timecode_list(scene_list)
399391
timecodes = list(timecode_list[0])
400392

401-
# Should use temporal_margin (0.1s), not frame_margin (1s)
393+
# Should use margin (0.1s), not frame_margin (1s)
402394
assert timecodes[0].seconds == pytest.approx(0.1, abs=0.05)
403395
assert timecodes[2].seconds == pytest.approx(2.9, abs=0.05)

0 commit comments

Comments
 (0)