Skip to content

Commit 2fa8290

Browse files
authored
[bugfix] Apply AdaptiveDetector min_scene_len to the emitted cut (#408) (#571)
## Summary - `AdaptiveDetector` was checking `min_scene_len` against the *current* frame while emitting the cut `window_width` frames earlier. With the reporter's `window_width=20` and `min_scene_len=24`, a second peak could be written 4 frames after the previous cut (issue #408). - The length check now uses the emitted target frame, which is what 0.6.4 (`d8397bc`) intended but did not actually change (the comparison was left on the current frame after a rename). Provenance: the owner identified the current-vs-target mismatch on the issue; collaborator @wjs018 described the 100/124/104 example; the reporter confirmed that example and measured a 4-frame clip with ffprobe. ## Decision - **Chose:** Compare `min_scene_len` to `target_timecode` (the cut that is actually returned). - **Alternative:** Keep the current-frame comparison and add `window_width` to the threshold, or move AdaptiveDetector onto `FlashFilter` like `ContentDetector`. - **Why:** Smallest change that matches the owner's diagnosis and the stated intent of `d8397bc`. Happy to switch to `FlashFilter` if you want merge-mode behaviour here. A second judgement call: the regression test uses synthetic luma steps with the reporter's `window_width=20` / `min_scene_len=24`, not `goldeneye.mp4`. Well-spaced cuts on that clip never enter the `min_scene_len - window_width` window, so a goldeneye-only assertion would pass with or without the fix. ## Checklist - [x] New AdaptiveDetector unit test fails without the fix (`[100, 104, 140, 144]` vs `[100, 140]`) and passes with it - [x] `ruff check` / `ruff format` on the touched Python files - [x] Follows the Google Python Style Guide - [x] Changelog entry under 0.7.2 (development section) ## Test plan - [x] Synthetic luma cuts at frames 100/104 and 140/144 with `window_width=20`, `min_scene_len=24`: without the fix the detector emits `[100, 104, 140, 144]`; with the fix it emits `[100, 140]`. - [x] `pytest tests/test_detectors.py::test_detectors_with_stats` (all detectors including AdaptiveDetector, with a StatsManager) - [ ] Optional: `detect-adaptive` on a clip with a decaying double peak and `--min-scene-len 24 --frame-window 20`; no output scene should be shorter than 24 frames. Fixes #408
2 parents 56ab41b + a3ffe29 commit 2fa8290

3 files changed

Lines changed: 34 additions & 1 deletion

File tree

‎scenedetect/detectors/adaptive_detector.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ def process_frame(self, timecode: FrameTimecode, frame_img: np.ndarray) -> list[
136136
threshold_met: bool = (
137137
adaptive_ratio >= self.adaptive_threshold and target_score >= self.min_content_val
138138
)
139-
min_length_met: bool = (timecode - self._last_cut) >= self.min_scene_len
139+
# Cuts are emitted at `target_timecode` (`window_width` behind `timecode`).
140+
min_length_met: bool = (target_timecode - self._last_cut) >= self.min_scene_len
140141
if threshold_met and min_length_met:
141142
self._last_cut = target_timecode
142143
return [target_timecode]

‎tests/test_detectors.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import os
2020
from dataclasses import dataclass
2121

22+
import numpy as np
2223
import pytest
2324

2425
from scenedetect import FrameTimecode, SceneDetector, SceneManager, StatsManager, detect
@@ -261,3 +262,33 @@ def test_min_scene_len_accepts_time_values(detector_type, min_scene_len):
261262
scene_list = test_case.detect()
262263
start_frames = [timecode.frame_num for timecode, _ in scene_list]
263264
assert start_frames == test_case.scene_boundaries
265+
266+
267+
def test_adaptive_detector_min_scene_len_uses_target_frame():
268+
"""AdaptiveDetector applies min_scene_len to the emitted cut, not the current frame.
269+
270+
AdaptiveDetector scores a target frame `window_width` behind the frame currently
271+
being processed. Comparing min_scene_len against the current frame lets a second
272+
peak emit a cut only `window_width` frames after the previous one (issue #408).
273+
"""
274+
window_width = 20
275+
min_scene_len = 24
276+
detector = AdaptiveDetector(
277+
adaptive_threshold=2.0,
278+
window_width=window_width,
279+
min_scene_len=min_scene_len,
280+
min_content_val=15.0,
281+
luma_only=True,
282+
)
283+
fps = 30.0
284+
n_frames = 180
285+
cuts: list[FrameTimecode] = []
286+
for frame_num in range(n_frames):
287+
# Hard cuts at 100 and 104 (4 frames apart) plus a later valid cut at 140.
288+
# The 4-frame pair matches the report: min_scene_len - window_width.
289+
value = 255 if 100 <= frame_num <= 103 or 140 <= frame_num <= 143 else 0
290+
frame = np.full((16, 16, 3), value, dtype=np.uint8)
291+
cuts.extend(detector.process_frame(FrameTimecode(frame_num, fps), frame))
292+
293+
cut_frames = [cut.frame_num for cut in cuts]
294+
assert cut_frames == [100, 140]

‎website/pages/changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ Development
782782

783783
## PySceneDetect 0.7.2 (TBD)
784784

785+
- [bugfix] Fix `AdaptiveDetector` / `detect-adaptive` emitting scenes shorter than `min_scene_len` when `window_width` is large, by applying the minimum-length check to the emitted (target) frame instead of the current frame [#408](https://github.com/Breakthrough/PySceneDetect/issues/408)
785786
- [general] The `scenedetect-core` package introduced in 0.7.1 has been discontinued, and its only release (0.7.1) yanked from PyPI: pip cannot safely support multiple packages that install the same module files, and restructuring the existing packages around a shared core would break in-place upgrades. Existing `scenedetect-core` installs keep working but will not receive updates; continue to install `scenedetect` or `scenedetect-headless` as usual.
786787
- [improvement] `HistogramDetector` (`detect-hist`) default `threshold` changed from 0.05 to 0.20 and default `bins` from 256 to 128, calibrated from the [benchmark sweep](https://www.scenedetect.com/benchmarks/) for significantly better accuracy. Default output for this detector will change [#559](https://github.com/Breakthrough/PySceneDetect/issues/559)
787788
- [improvement] `HashDetector` (`detect-hash`) default `threshold` changed from 0.395 to 0.35 and default `size` from 16 to 8, calibrated from the [benchmark sweep](https://www.scenedetect.com/benchmarks/) for better accuracy. Default output for this detector will change, including the statsfile metric key (now `hash_dist [size=8 lowpass=2]`) [#559](https://github.com/Breakthrough/PySceneDetect/issues/559)

0 commit comments

Comments
 (0)