Reject vision matches with action phase mismatches
This commit is contained in:
@@ -632,6 +632,66 @@ def _merge_best_results(existing: list, candidates: list, cfg) -> list:
|
||||
return sorted(by_id.values(), key=lambda r: r.beat_id)
|
||||
|
||||
|
||||
def _filter_semantically_invalid_vision_matches(results: list, beats: list, cfg) -> list:
|
||||
"""Drop vision-enabled matches whose final action phase contradicts the beat."""
|
||||
if not cfg.vision.enabled or not results:
|
||||
return results
|
||||
|
||||
from dataclasses import replace
|
||||
from src.llm.vision_cache import validate_match_window_with_vision
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
beats_by_id = {beat.beat_id: beat for beat in beats}
|
||||
kept = []
|
||||
for result in results:
|
||||
beat = beats_by_id.get(result.beat_id)
|
||||
if beat is None:
|
||||
kept.append(result)
|
||||
continue
|
||||
|
||||
windows = []
|
||||
if getattr(result, "segments", ()):
|
||||
for segment in result.segments:
|
||||
segment_beat = replace(
|
||||
beat,
|
||||
start_s=beat.start_s + segment.trailer_offset_s,
|
||||
end_s=beat.start_s + segment.trailer_offset_s + segment.duration_s,
|
||||
)
|
||||
windows.append((
|
||||
segment_beat,
|
||||
segment.scene_id,
|
||||
segment.in_point_s,
|
||||
segment.out_point_s,
|
||||
))
|
||||
else:
|
||||
windows.append((beat, result.scene_id, result.in_point_s, result.out_point_s))
|
||||
|
||||
valid = True
|
||||
reasons: list[str] = []
|
||||
for check_beat, scene_id, in_point_s, out_point_s in windows:
|
||||
ok, reason = validate_match_window_with_vision(
|
||||
check_beat,
|
||||
source_path=result.source_path,
|
||||
scene_id=scene_id,
|
||||
in_point_s=in_point_s,
|
||||
out_point_s=out_point_s,
|
||||
cfg=cfg,
|
||||
)
|
||||
reasons.append(reason)
|
||||
if not ok:
|
||||
valid = False
|
||||
break
|
||||
if valid:
|
||||
kept.append(result)
|
||||
else:
|
||||
logger.warning(
|
||||
"Beat %d: rejected by vision action-phase verification (%s)",
|
||||
result.beat_id,
|
||||
"; ".join(reasons),
|
||||
)
|
||||
return kept
|
||||
|
||||
|
||||
def _attach_visual_segments(results: list, beats: list, cfg) -> list:
|
||||
"""Attach automatic sub-shot matches for multi-island trailer beats."""
|
||||
from dataclasses import replace
|
||||
@@ -976,6 +1036,7 @@ def cmd_match(args: argparse.Namespace, cfg) -> list:
|
||||
skip_global_segment_scan_for=set(single_island_trims),
|
||||
)
|
||||
results = _attach_visual_segments(results, beats, cfg)
|
||||
results = _filter_semantically_invalid_vision_matches(results, beats, cfg)
|
||||
|
||||
# A targeted one-beat match should improve the cache without deleting
|
||||
# automatic matches for other beats.
|
||||
|
||||
Reference in New Issue
Block a user