Skip to content

Commit

Permalink
Revert "[cli] Add --filter-mode and replace --drop-short-scenes"
Browse files Browse the repository at this point in the history
Reason: Moving to a detector-only option.

This reverts commit 894297d.
  • Loading branch information
Breakthrough committed Apr 24, 2024
1 parent 09057df commit df85d7d
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 223 deletions.
36 changes: 17 additions & 19 deletions scenedetect.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,16 @@
# Method to use for downscaling (nearest, linear, cubic, area, lanczos4).
#downscale-method = linear

# Minimum length of a given scene. See filter-mode to control how this is enforced.
# Minimum length of a given scene.
#min-scene-len = 0.6s

# Mode to use when filtering out scenes (merge, suppress, drop):
# merge: Consecutive scenes shorter than min-scene-len are combined.
# suppress: No new scenes can be generated until min-scene-len passes.
# drop: Drop all scenes shorter than global min-scene-len.
#filter-mode = merge

# If the video ends less than min-scene-len after the last cut, merge it with the
# previous scene (yes/no).
# Merge last scene if it is shorter than min-scene-len (yes/no). This can occur
# when a cut is detected just before the video ends.
#merge-last-scene = no

# Drop scenes shorter than min-scene-len instead of merging (yes/no).
#drop-short-scenes = no

# Verbosity of console output (debug, info, warning, error, or none).
# Set to none for the same behavior as specifying -q/--quiet.
#verbosity = debug
Expand All @@ -68,6 +65,14 @@
# Sensitivity threshold from 0 to 255. Lower values are more sensitive.
#threshold = 27

# Minimum length of a given scene (overrides [global] option).
#min-scene-len = 0.6s

# Mode to use when filtering scenes to comply with min-scene-len:
# merge: Consecutive scenes shorter than min-scene-len are combined.
# suppress: No new scenes can be generated until min-scene-len passes.
#filter-mode = merge

# Weight to place on each component when calculating frame score (the value
# `threshold` is compared against). The components are in the order
# (delta_hue, delta_sat, delta_lum, delta_edges). Description of components:
Expand All @@ -87,10 +92,6 @@
# than or equal to 3. If None, automatically set using video resolution.
#kernel-size = -1

# Minimum length of a given scene. No new cuts can be emitted after one is found
# until this length of time passes.
#min-scene-len = 0.6s


[detect-threshold]
# Average pixel intensity from 0-255 at which a fade event is triggered.
Expand All @@ -106,8 +107,7 @@
# Discard colour information and only use luminance (yes/no).
#luma-only = no

# Minimum length of a given scene. No new cuts can be emitted after one is found
# until this length of time passes.
# Minimum length of a given scene (overrides [global] option).
#min-scene-len = 0.6s


Expand All @@ -121,8 +121,7 @@
# Window size (number of frames) before and after each frame to average together.
#frame-window = 2

# Minimum length of a given scene. No new cuts can be emitted after one is found
# until this length of time passes.
# Minimum length of a given scene (overrides [global] option).
#min-scene-len = 0.6s

# The following parameters are the those used to calculate `content_val`.
Expand All @@ -144,8 +143,7 @@
# Number of bits to use for image quantization before binning.
#bits = 4

# Minimum length of a given scene. No new cuts can be emitted after one is found
# until this length of time passes.
# Minimum length of a given scene (overrides [global] option).
#min-scene-len = 0.6s


Expand Down
2 changes: 1 addition & 1 deletion scenedetect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

# Used for module identification and when printing version & about info
# (e.g. calling `scenedetect version` or `scenedetect about`).
__version__ = '0.6.4-dev0'
__version__ = '0.7-dev0'

init_logger()
logger = getLogger('pyscenedetect')
Expand Down
11 changes: 0 additions & 11 deletions scenedetect/_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,19 +206,10 @@ def _print_command_help(ctx: click.Context, command: click.Command):
help='Minimum length of any scene. TIMECODE can be specified as number of frames (-m=10), time in seconds (-m=2.5), or timecode (-m=00:02:53.633).%s'
% USER_CONFIG.get_help_string("global", "min-scene-len"),
)
@click.option(
"--filter-mode",
metavar="MODE",
type=click.Choice(CHOICE_MAP["global"]["filter-mode"], False),
default=None,
help='Mode used when enforcing min-scene-len. MODE must be one of: %s. %s' % (', '.join(
CHOICE_MAP["global"]["filter-mode"]), USER_CONFIG.get_help_string("global", "filter-mode")),
)
@click.option(
'--drop-short-scenes',
is_flag=True,
flag_value=True,
hidden=True,
help='Drop scenes shorter than -m/--min-scene-len, instead of combining with neighbors.%s' %
(USER_CONFIG.get_help_string('global', 'drop-short-scenes')),
)
Expand Down Expand Up @@ -290,7 +281,6 @@ def scenedetect(
config: Optional[AnyStr],
framerate: Optional[float],
min_scene_len: Optional[str],
filter_mode: Optional[str],
drop_short_scenes: bool,
merge_last_scene: bool,
backend: Optional[str],
Expand Down Expand Up @@ -335,7 +325,6 @@ def scenedetect(
downscale=downscale,
frame_skip=frame_skip,
min_scene_len=min_scene_len,
filter_mode=filter_mode,
drop_short_scenes=drop_short_scenes,
merge_last_scene=merge_last_scene,
backend=backend,
Expand Down
13 changes: 1 addition & 12 deletions scenedetect/_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

from scenedetect.detectors import ContentDetector
from scenedetect.frame_timecode import FrameTimecode
from scenedetect.scene_detector import FlashFilter
from scenedetect.scene_manager import Interpolation
from scenedetect.video_splitter import DEFAULT_FFMPEG_ARGS

Expand Down Expand Up @@ -233,13 +232,6 @@ def format(self, timecode: FrameTimecode) -> str:
assert False


class FlashFilterMode(Enum):
"""Filter mode for the CLI. Has additional DROP mode which runs as a post-processing step."""
MERGE = FlashFilter.Mode.MERGE
SUPPRESS = FlashFilter.Mode.SUPPRESS
DROP = -1


ConfigValue = Union[bool, int, float, str]
ConfigDict = Dict[str, Dict[str, ConfigValue]]

Expand All @@ -251,8 +243,7 @@ class FlashFilterMode(Enum):
DEFAULT_JPG_QUALITY = 95
DEFAULT_WEBP_QUALITY = 100

# TODO(v0.6.4): Warn if [detect-adaptive] min-delta-hsv and [global] drop-short-scenes are used.
# TODO(v0.7): Remove [detect-adaptive] min-delta-hsv and [global] drop-short-scenes
# TODO(v0.7): Remove [detect-adaptive] min-delta-hsv
CONFIG_MAP: ConfigDict = {
'backend-opencv': {
'max-decode-attempts': 5,
Expand Down Expand Up @@ -314,7 +305,6 @@ class FlashFilterMode(Enum):
'downscale': 0,
'downscale-method': 'linear',
'drop-short-scenes': False,
'filter-mode': 'merge',
'frame-skip': 0,
'merge-last-scene': False,
'min-scene-len': TimecodeValue('0.6s'),
Expand Down Expand Up @@ -358,7 +348,6 @@ class FlashFilterMode(Enum):
'backend': ['opencv', 'pyav', 'moviepy'],
'default-detector': ['detect-adaptive', 'detect-content', 'detect-threshold'],
'downscale-method': [value.name.lower() for value in Interpolation],
'filter-mode': [value.name.lower() for value in FlashFilterMode],
'verbosity': ['debug', 'info', 'warning', 'error', 'none'],
},
'list-scenes': {
Expand Down
95 changes: 53 additions & 42 deletions scenedetect/_cli/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,17 @@

from scenedetect import open_video, AVAILABLE_BACKENDS

from scenedetect.scene_detector import SceneDetector, FlashFilter
from scenedetect.platform import get_cv2_imwrite_params, init_logger
from scenedetect.scene_detector import SceneDetector
from scenedetect.platform import get_and_create_path, get_cv2_imwrite_params, init_logger
from scenedetect.frame_timecode import FrameTimecode, MAX_FPS_DELTA
from scenedetect.video_stream import VideoStream, VideoOpenFailure, FrameRateUnavailable
from scenedetect.video_splitter import is_mkvmerge_available, is_ffmpeg_available
from scenedetect.detectors import AdaptiveDetector, ContentDetector, ThresholdDetector, HistogramDetector
from scenedetect.stats_manager import StatsManager
from scenedetect.scene_manager import SceneManager, Interpolation

from scenedetect._cli.config import (ConfigRegistry, ConfigLoadFailure, TimecodeFormat,
FlashFilterMode, CHOICE_MAP, DEFAULT_JPG_QUALITY,
DEFAULT_WEBP_QUALITY)
from scenedetect._cli.config import (ConfigRegistry, ConfigLoadFailure, TimecodeFormat, CHOICE_MAP,
DEFAULT_JPG_QUALITY, DEFAULT_WEBP_QUALITY)

logger = logging.getLogger('pyscenedetect')

Expand Down Expand Up @@ -117,9 +116,9 @@ def __init__(self):
self.output_dir: str = None # -o/--output
self.quiet_mode: bool = None # -q/--quiet or -v/--verbosity quiet
self.stats_file_path: str = None # -s/--stats
self.min_scene_len: FrameTimecode = None # -m/--min-scene-len
self.filter_mode: FlashFilterMode = None # --filter-mode
self.drop_short_scenes: bool = None # --drop-short-scenes
self.merge_last_scene: bool = None # --merge-last-scene
self.min_scene_len: FrameTimecode = None # -m/--min-scene-len
self.frame_skip: int = None # -fs/--frame-skip
self.default_detector: Tuple[Type[SceneDetector],
Dict[str, Any]] = None # [global] default-detector
Expand Down Expand Up @@ -187,7 +186,6 @@ def handle_options(
downscale: Optional[int],
frame_skip: int,
min_scene_len: str,
filter_mode: Optional[str],
drop_short_scenes: bool,
merge_last_scene: bool,
backend: Optional[str],
Expand Down Expand Up @@ -271,15 +269,8 @@ def handle_options(
self.min_scene_len = parse_timecode(
min_scene_len if min_scene_len is not None else self.config.get_value(
"global", "min-scene-len"), self.video_stream.frame_rate)

if drop_short_scenes:
logger.warning(
"WARNING: --drop-short-scenes is deprecated, use --filter-mode=drop instead.")
if filter_mode is None:
self.filter_mode = FlashFilterMode.DROP
else:
self.filter_mode = FlashFilterMode[self.config.get_value("global", "filter-mode",
filter_mode).upper()]
self.drop_short_scenes = drop_short_scenes or self.config.get_value(
"global", "drop-short-scenes")
self.merge_last_scene = merge_last_scene or self.config.get_value(
"global", "merge-last-scene")
self.frame_skip = self.config.get_value("global", "frame-skip", frame_skip)
Expand All @@ -290,7 +281,6 @@ def handle_options(
self.stats_manager = StatsManager()

# Initialize default detector with values in the config file.
# TODO(v0.6.4): Integrate perceptual hash detector.
default_detector = self.config.get_value("global", "default-detector")
if default_detector == 'detect-adaptive':
self.default_detector = (AdaptiveDetector, self.get_detect_adaptive_params())
Expand Down Expand Up @@ -330,18 +320,29 @@ def get_detect_content_params(
) -> Dict[str, Any]:
"""Handle detect-content command options and return dict to construct one with."""
self._ensure_input_open()

if self.drop_short_scenes:
min_scene_len = 0
else:
if min_scene_len is None:
if self.config.is_default('detect-content', 'min-scene-len'):
min_scene_len = self.min_scene_len.frame_num
else:
min_scene_len = self.config.get_value('detect-content', 'min-scene-len')
min_scene_len = parse_timecode(min_scene_len, self.video_stream.frame_rate).frame_num

if weights is not None:
try:
weights = ContentDetector.Components(*weights)
except ValueError as ex:
logger.debug(str(ex))
raise click.BadParameter(str(ex), param_hint="weights")
raise click.BadParameter(str(ex), param_hint='weights')
return {
"weights": self.config.get_value("detect-content", "weights", weights),
"kernel_size": self.config.get_value("detect-content", "kernel-size", kernel_size),
"luma_only": luma_only or self.config.get_value("detect-content", "luma-only"),
"flash_filter": self._init_flash_filter("detect-content", min_scene_len),
"threshold": self.config.get_value("detect-content", "threshold", threshold),
'weights': self.config.get_value('detect-content', 'weights', weights),
'kernel_size': self.config.get_value('detect-content', 'kernel-size', kernel_size),
'luma_only': luma_only or self.config.get_value('detect-content', 'luma-only'),
'min_scene_len': min_scene_len,
'threshold': self.config.get_value('detect-content', 'threshold', threshold),
}

def get_detect_adaptive_params(
Expand Down Expand Up @@ -371,8 +372,15 @@ def get_detect_adaptive_params(
self.config.config_dict["detect-adaptive"]["min-content-val"] = (
self.config.config_dict["detect-adaptive"]["min-deleta-hsv"])

# TODO(v0.6.4): Integrate flash filter.
min_scene_len = self._init_flash_filter("detect-adaptive", min_scene_len)._filter_length
if self.drop_short_scenes:
min_scene_len = 0
else:
if min_scene_len is None:
if self.config.is_default("detect-adaptive", "min-scene-len"):
min_scene_len = self.min_scene_len.frame_num
else:
min_scene_len = self.config.get_value("detect-adaptive", "min-scene-len")
min_scene_len = parse_timecode(min_scene_len, self.video_stream.frame_rate).frame_num

if weights is not None:
try:
Expand Down Expand Up @@ -406,8 +414,16 @@ def get_detect_threshold_params(
) -> Dict[str, Any]:
"""Handle detect-threshold command options and return dict to construct one with."""
self._ensure_input_open()
# TODO(v0.6.4): Integrate flash filter.
min_scene_len = self._init_flash_filter("detect-adaptive", min_scene_len)._filter_length

if self.drop_short_scenes:
min_scene_len = 0
else:
if min_scene_len is None:
if self.config.is_default("detect-threshold", "min-scene-len"):
min_scene_len = self.min_scene_len.frame_num
else:
min_scene_len = self.config.get_value("detect-threshold", "min-scene-len")
min_scene_len = parse_timecode(min_scene_len, self.video_stream.frame_rate).frame_num
# TODO(v1.0): add_last_scene cannot be disabled right now.
return {
'add_final_scene':
Expand Down Expand Up @@ -439,8 +455,15 @@ def get_detect_hist_params(self, threshold: Optional[float], bits: Optional[int]
min_scene_len: Optional[str]) -> Dict[str, Any]:
"""Handle detect-hist command options and return dict to construct one with."""
self._ensure_input_open()
# TODO(v0.6.4): Integrate flash filter.
min_scene_len = self._init_flash_filter("detect-adaptive", min_scene_len)._filter_length
if self.drop_short_scenes:
min_scene_len = 0
else:
if min_scene_len is None:
if self.config.is_default("detect-hist", "min-scene-len"):
min_scene_len = self.min_scene_len.frame_num
else:
min_scene_len = self.config.get_value("detect-hist", "min-scene-len")
min_scene_len = parse_timecode(min_scene_len, self.video_stream.frame_rate).frame_num
return {
'bits': self.config.get_value("detect-hist", "bits", bits),
'min_scene_len': min_scene_len,
Expand Down Expand Up @@ -829,15 +852,3 @@ def _on_duplicate_command(self, command: str) -> None:
raise click.BadParameter(
'\n Command %s may only be specified once.' % command,
param_hint='%s command' % command)

def _init_flash_filter(self, detector_name: str,
min_scene_len: ty.Optional[int]) -> FlashFilter:
if self.filter_mode == FlashFilterMode.DROP:
return FlashFilter(length=0)
if min_scene_len is None:
if self.config.is_default(detector_name, 'min-scene-len'):
min_scene_len = self.min_scene_len.frame_num
else:
min_scene_len = self.config.get_value(detector_name, 'min-scene-len')
min_scene_len = parse_timecode(min_scene_len, self.video_stream.frame_rate).frame_num
return FlashFilter(length=min_scene_len, mode=self.filter_mode.value)
7 changes: 2 additions & 5 deletions scenedetect/_cli/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from scenedetect.video_stream import SeekError

from scenedetect._cli.context import CliContext, check_split_video_requirements
from scenedetect._cli.config import FlashFilterMode

logger = logging.getLogger('pyscenedetect')

Expand Down Expand Up @@ -331,13 +330,11 @@ def _postprocess_scene_list(
# it will be merged with the previous one.
if context.merge_last_scene and context.min_scene_len is not None and context.min_scene_len > 0:
if len(scene_list) > 1 and (scene_list[-1][1] - scene_list[-1][0]) < context.min_scene_len:
logger.debug("Last scene is shorter than %d frames, merging with previous.",
context.min_scene_len.get_frames())
new_last_scene = (scene_list[-2][0], scene_list[-1][1])
scene_list = scene_list[:-2] + [new_last_scene]

if context.filter_mode == FlashFilterMode.DROP:
logger.debug("Dropping scenes shorter than %d frames.", context.min_scene_len.get_frames())
# Handle --drop-short-scenes.
if context.drop_short_scenes and context.min_scene_len > 0:
scene_list = [s for s in scene_list if (s[1] - s[0]) >= context.min_scene_len]

return scene_list
Loading

0 comments on commit df85d7d

Please sign in to comment.