stouchinglhh
Claude
sync processor from GitLab c697b6e: 版本 1.19.4 / 前端 1.12.4 —— 服务端 i18n(首屏不再闪英文)、推理模块并入模型页、前端抽出 shared/、缓存键改用前端号
bb59211
Raw History Blame Contribute Delete
3.17 kB
"""冻结 —— 画面连续多帧几乎不变。
为什么重要
----------
冻结段通常是采集程序卡住、编码器 stall、或 USB 带宽不足导致丢帧后补重复帧。
它和黑屏不同:画面是"正常的样子",但时间已经不动了。模型会以为任务停滞。
注意与"合理的静止"区分
----------------------
人拿着夹爪停在物体上方思考时,画面也接近静止。所以本项默认判 **WARN 而非
FAIL**,并在 metrics 里带上每段时长供人工/AI 复核 —— 方案明确要求
「阈值不要照搬」,静止多久算异常要按任务节奏定。
"""
from __future__ import annotations
from app.processing.cleaning.contract import VIDEO_CAPABLE_MODALITIES
from app.processing.cleaning.checks import (
CheckContext, CleaningCheck, register_check,
Finding, Range, WARN, FAIL,
)
@register_check
class VideoFreezeCheck(CleaningCheck):
slug = "video.freeze"
label = "Frozen Frame"
version = "1.0"
description = "Frame nearly unchanged across consecutive frames"
requires_channels = ("rgb",)
# 属于哪些设备卡片 —— 前端设置面板按它分 tab
device_cards = VIDEO_CAPABLE_MODALITIES
default_params = {"min_sec": 2.0, "fail_sec": 10.0}
default_severity = WARN
def run(self, ctx: CheckContext) -> list[Finding]:
findings: list[Finding] = []
for stream in ctx.streams_of("rgb"):
report = stream.video or {}
ranges = report.get("freeze_ranges") or []
if not ranges:
continue
fps = float(report.get("fps") or stream.fps or ctx.fps or 30.0)
min_sec = float(self.param(ctx, "min_sec", 2.0))
fail_sec = float(self.param(ctx, "fail_sec", 10.0))
converted: list[Range] = []
longest = 0.0
worst_status = WARN
for item in ranges:
try:
start, end = float(item[0]), float(item[1])
except (TypeError, ValueError, IndexError):
continue
span = end - start
if span < min_sec:
continue
longest = max(longest, span)
status = FAIL if span >= fail_sec else WARN
if status == FAIL:
worst_status = FAIL
converted.append(Range(
start_sec=start, end_sec=end,
code=self.slug, severity=status,
stream=stream.key, channel="rgb",
start_frame=int(start * fps), end_frame=int(end * fps),
rule=self.slug,
message=f"frozen for {span:.1f}s",
))
if not converted:
continue
findings.append(self.finding(
ctx, worst_status, stream=stream.key,
message=f"{len(converted)} frozen stretches, longest {longest:.1f}s",
metrics={"segments": len(converted),
"longest_sec": round(longest, 3)},
ranges=converted,
))
return findings