|
24 | 24 | event (in, out, cut, etc...). |
25 | 25 | """ |
26 | 26 |
|
| 27 | +import math |
27 | 28 | import typing as ty |
28 | 29 | from abc import ABC, abstractmethod |
29 | 30 | from enum import Enum |
@@ -114,26 +115,48 @@ class Mode(Enum): |
114 | 115 | SUPPRESS = 1 |
115 | 116 | """Suppress consecutive cuts until the filter length has passed.""" |
116 | 117 |
|
117 | | - def __init__(self, mode: Mode, length: int): |
| 118 | + def __init__(self, mode: Mode, length: ty.Union[int, float, str]): |
118 | 119 | """ |
119 | 120 | Arguments: |
120 | 121 | mode: The mode to use when enforcing `length`. |
121 | | - length: Number of frames to use when filtering cuts. |
| 122 | + length: Minimum scene length. Accepts an `int` (number of frames), `float` (seconds), |
| 123 | + or `str` (timecode, e.g. ``"0.6s"`` or ``"00:00:00.600"``). |
122 | 124 | """ |
123 | 125 | self._mode = mode |
124 | | - self._filter_length = length # Number of frames to use for activating the filter. |
125 | | - self._filter_secs: ty.Optional[float] = None # Threshold in seconds, computed on first use. |
| 126 | + # Frame count (int) and seconds (float) representations of `length`. Exactly one is |
| 127 | + # populated up front; the other is computed on the first frame once the framerate is |
| 128 | + # known. Temporal inputs (float/non-digit str) populate `_filter_secs`; integer inputs |
| 129 | + # (int/digit str) populate `_filter_length`. |
| 130 | + self._filter_length: int = 0 |
| 131 | + self._filter_secs: ty.Optional[float] = None |
| 132 | + if isinstance(length, float): |
| 133 | + self._filter_secs = length |
| 134 | + elif isinstance(length, str) and not length.strip().isdigit(): |
| 135 | + self._filter_secs = FrameTimecode(timecode=length, fps=100.0).seconds |
| 136 | + else: |
| 137 | + self._filter_length = int(length) |
126 | 138 | self._last_above = None # Last frame above threshold. |
127 | 139 | self._merge_enabled = False # Used to disable merging until at least one cut was found. |
128 | 140 | self._merge_triggered = False # True when the merge filter is active. |
129 | 141 | self._merge_start = None # Frame number where we started the merge filter. |
130 | 142 |
|
131 | 143 | @property |
132 | 144 | def max_behind(self) -> int: |
133 | | - return 0 if self._mode == FlashFilter.Mode.SUPPRESS else self._filter_length |
| 145 | + if self._mode == FlashFilter.Mode.SUPPRESS: |
| 146 | + return 0 |
| 147 | + if self._filter_secs is not None: |
| 148 | + # Estimate using 240fps so the event buffer is large enough for any reasonable input. |
| 149 | + return math.ceil(self._filter_secs * 240.0) |
| 150 | + return self._filter_length |
| 151 | + |
| 152 | + @property |
| 153 | + def _is_disabled(self) -> bool: |
| 154 | + if self._filter_secs is not None: |
| 155 | + return self._filter_secs <= 0.0 |
| 156 | + return self._filter_length <= 0 |
134 | 157 |
|
135 | 158 | def filter(self, timecode: FrameTimecode, above_threshold: bool) -> ty.List[FrameTimecode]: |
136 | | - if not self._filter_length > 0: |
| 159 | + if self._is_disabled: |
137 | 160 | return [timecode] if above_threshold else [] |
138 | 161 | if self._last_above is None: |
139 | 162 | self._last_above = timecode |
|
0 commit comments