1818"""
1919
2020from logging import getLogger
21- import typing as ty
21+ from typing import List , Optional
2222
2323import numpy as np
2424
2525from scenedetect .detectors import ContentDetector
26- from scenedetect .scene_detector import FlashFilter
2726
2827logger = getLogger ('pyscenedetect' )
2928
@@ -44,19 +43,15 @@ def __init__(
4443 min_content_val : float = 15.0 ,
4544 weights : ContentDetector .Components = ContentDetector .DEFAULT_COMPONENT_WEIGHTS ,
4645 luma_only : bool = False ,
47- kernel_size : ty .Optional [int ] = None ,
48- flash_filter : ty .Optional [FlashFilter ] = None ,
46+ kernel_size : Optional [int ] = None ,
4947 video_manager = None ,
50- min_delta_hsv : ty . Optional [float ] = None ,
48+ min_delta_hsv : Optional [float ] = None ,
5149 ):
5250 """
5351 Arguments:
5452 adaptive_threshold: Threshold (float) that score ratio must exceed to trigger a
5553 new scene (see frame metric adaptive_ratio in stats file).
56- min_scene_len: Defines the minimum length of a given scene. Sequences of consecutive
57- cuts that occur closer than this length will be merged. Equivalent to setting
58- `flash_filter = FlashFilter(length=min_scene_len)`.
59- Ignored if `flash_filter` is set.
54+ min_scene_len: Minimum length of any scene.
6055 window_width: Size of window (number of frames) before and after each frame to
6156 average together in order to detect deviations from the mean. Must be at least 1.
6257 min_content_val: Minimum threshold (float) that the content_val must exceed in order to
@@ -70,10 +65,8 @@ def __init__(
7065 Overrides `weights` if both are set.
7166 kernel_size: Size of kernel to use for post edge detection filtering. If None,
7267 automatically set based on video resolution.
73- flash_filter: Filter to use for scene length compliance. If None, initialized as
74- `FlashFilter(length=min_scene_len)`. If set, `min_scene_length` is ignored.
75- video_manager: [DEPRECATED] DO NOT USE.
76- min_delta_hsv: [DEPRECATED] DO NOT USE.
68+ video_manager: [DEPRECATED] DO NOT USE. For backwards compatibility only.
69+ min_delta_hsv: [DEPRECATED] DO NOT USE. Use `min_content_val` instead.
7770 """
7871 # TODO(v0.7): Replace with DeprecationWarning that `video_manager` and `min_delta_hsv` will
7972 # be removed in v0.8.
@@ -84,35 +77,44 @@ def __init__(
8477 min_content_val = min_delta_hsv
8578 if window_width < 1 :
8679 raise ValueError ('window_width must be at least 1.' )
80+
8781 super ().__init__ (
8882 threshold = 255.0 ,
89- min_scene_len = min_scene_len ,
83+ min_scene_len = 0 ,
9084 weights = weights ,
9185 luma_only = luma_only ,
9286 kernel_size = kernel_size ,
93- flash_filter = flash_filter ,
9487 )
95- self ._adaptive_threshold = adaptive_threshold
96- self ._min_content_val = min_content_val
97- self ._window_width = window_width
88+
89+ # TODO: Turn these options into properties.
90+ self .min_scene_len = min_scene_len
91+ self .adaptive_threshold = adaptive_threshold
92+ self .min_content_val = min_content_val
93+ self .window_width = window_width
94+
9895 self ._adaptive_ratio_key = AdaptiveDetector .ADAPTIVE_RATIO_KEY_TEMPLATE .format (
9996 window_width = window_width , luma_only = '' if not luma_only else '_lum' )
97+ self ._first_frame_num = None
98+
99+ # NOTE: This must be different than `self._last_scene_cut` which is used by the base class.
100+ self ._last_cut : Optional [int ] = None
101+
100102 self ._buffer = []
101103
102104 @property
103105 def event_buffer_length (self ) -> int :
104106 """Number of frames any detected cuts will be behind the current frame due to buffering."""
105- return self ._window_width
107+ return self .window_width
106108
107- def get_metrics (self ) -> ty . List [str ]:
109+ def get_metrics (self ) -> List [str ]:
108110 """Combines base ContentDetector metric keys with the AdaptiveDetector one."""
109111 return super ().get_metrics () + [self ._adaptive_ratio_key ]
110112
111113 def stats_manager_required (self ) -> bool :
112114 """Not required for AdaptiveDetector."""
113115 return False
114116
115- def process_frame (self , frame_num : int , frame_img : ty . Optional [np .ndarray ]) -> ty . List [int ]:
117+ def process_frame (self , frame_num : int , frame_img : Optional [np .ndarray ]) -> List [int ]:
116118 """Process the next frame. `frame_num` is assumed to be sequential.
117119
118120 Args:
@@ -124,33 +126,47 @@ def process_frame(self, frame_num: int, frame_img: ty.Optional[np.ndarray]) -> t
124126 List[int]: List of frames where scene cuts have been detected. There may be 0
125127 or more frames in the list, and not necessarily the same as frame_num.
126128 """
127- frame_score = self ._calculate_frame_score (frame_num = frame_num , frame_img = frame_img )
128- required_frames = 1 + (2 * self ._window_width )
129- self ._buffer .append ((frame_num , frame_score ))
129+
130+ # TODO(#283): Merge this with ContentDetector and turn it on by default.
131+
132+ super ().process_frame (frame_num = frame_num , frame_img = frame_img )
133+
134+ # Initialize last scene cut point at the beginning of the frames of interest.
135+ if self ._last_cut is None :
136+ self ._last_cut = frame_num
137+
138+ required_frames = 1 + (2 * self .window_width )
139+ self ._buffer .append ((frame_num , self ._frame_score ))
130140 if not len (self ._buffer ) >= required_frames :
131141 return []
132142 self ._buffer = self ._buffer [- required_frames :]
133- target = self ._buffer [self ._window_width ]
143+ target = self ._buffer [self .window_width ]
134144 average_window_score = (
135- sum (frame [1 ] for i , frame in enumerate (self ._buffer ) if i != self ._window_width ) /
136- (2.0 * self ._window_width ))
145+ sum (frame [1 ] for i , frame in enumerate (self ._buffer ) if i != self .window_width ) /
146+ (2.0 * self .window_width ))
147+
137148 average_is_zero = abs (average_window_score ) < 0.00001
149+
138150 adaptive_ratio = 0.0
139151 if not average_is_zero :
140152 adaptive_ratio = min (target [1 ] / average_window_score , 255.0 )
141- elif average_is_zero and target [1 ] >= self ._min_content_val :
153+ elif average_is_zero and target [1 ] >= self .min_content_val :
142154 # if we would have divided by zero, set adaptive_ratio to the max (255.0)
143155 adaptive_ratio = 255.0
144156 if self .stats_manager is not None :
145157 self .stats_manager .set_metrics (target [0 ], {self ._adaptive_ratio_key : adaptive_ratio })
146158
147159 # Check to see if adaptive_ratio exceeds the adaptive_threshold as well as there
148160 # being a large enough content_val to trigger a cut
149- found_cut : bool = (
150- adaptive_ratio >= self ._adaptive_threshold and target [1 ] >= self ._min_content_val )
151- return self ._flash_filter .apply (frame_num = target [0 ], found_cut = found_cut )
152-
153- def get_content_val (self , frame_num : int ) -> ty .Optional [float ]:
161+ threshold_met : bool = (
162+ adaptive_ratio >= self .adaptive_threshold and target [1 ] >= self .min_content_val )
163+ min_length_met : bool = (frame_num - self ._last_cut ) >= self .min_scene_len
164+ if threshold_met and min_length_met :
165+ self ._last_cut = target [0 ]
166+ return [target [0 ]]
167+ return []
168+
169+ def get_content_val (self , frame_num : int ) -> Optional [float ]:
154170 """Returns the average content change for a frame."""
155171 # TODO(v0.7): Add DeprecationWarning that `get_content_val` will be removed in v0.7.
156172 logger .error ("get_content_val is deprecated and will be removed. Lookup the value"
@@ -159,10 +175,6 @@ def get_content_val(self, frame_num: int) -> ty.Optional[float]:
159175 return self .stats_manager .get_metrics (frame_num , [ContentDetector .FRAME_SCORE_KEY ])[0 ]
160176 return 0.0
161177
162- def post_process (self , _frame_num : int ):
163- # Already processed frame at self._window_width, process the rest. This ensures we emit any
164- # cuts the filtering mode might require.
165- cuts = []
166- for (frame_num , _ ) in self ._buffer [self ._window_width + 1 :]:
167- cuts += self ._flash_filter .apply (frame_num = frame_num , found_cut = False )
168- return cuts
178+ def post_process (self , _unused_frame_num : int ):
179+ """Not required for AdaptiveDetector."""
180+ return []
0 commit comments