auto-save 2026-05-14 04:15 (~4)
This commit is contained in:
29
api/main.py
29
api/main.py
@@ -90,6 +90,7 @@ JobStatus = Literal[
|
||||
KEYFRAME_COUNT = int(os.getenv("KEYFRAME_COUNT", "5"))
|
||||
FrameExtractTarget = Literal["balanced", "subject", "transition", "expression", "motion"]
|
||||
FrameExtractMode = Literal["replace", "append"]
|
||||
FrameExtractQuality = Literal["fast", "accurate", "ultra"]
|
||||
FRAME_TARGET_LABELS: dict[FrameExtractTarget, str] = {
|
||||
"balanced": "综合关键帧",
|
||||
"subject": "清晰主体",
|
||||
@@ -97,6 +98,11 @@ FRAME_TARGET_LABELS: dict[FrameExtractTarget, str] = {
|
||||
"expression": "表情瞬间",
|
||||
"motion": "动作峰值",
|
||||
}
|
||||
FRAME_QUALITY_LABELS: dict[FrameExtractQuality, str] = {
|
||||
"fast": "快速",
|
||||
"accurate": "精细",
|
||||
"ultra": "极准",
|
||||
}
|
||||
|
||||
|
||||
class GeneratedImage(BaseModel):
|
||||
@@ -399,20 +405,23 @@ def _sharpness_from_gray(g: np.ndarray) -> float:
|
||||
return float(lap.var())
|
||||
|
||||
|
||||
def _frame_metrics(img_path: Path, idx: int, timestamp: float) -> dict | None:
|
||||
def _frame_metrics(img_path: Path, idx: int, timestamp: float, metric_width: int = 160) -> dict | None:
|
||||
"""低清候选帧的本地评分特征。只用于排序,最终仍从原视频抽原尺寸帧。"""
|
||||
try:
|
||||
with Image.open(img_path) as raw:
|
||||
img = raw.convert("RGB")
|
||||
h = imagehash.phash(img)
|
||||
small = img.resize((160, 90))
|
||||
src_w, src_h = img.size
|
||||
metric_height = max(1, round(metric_width * src_h / max(src_w, 1)))
|
||||
small = img.resize((metric_width, metric_height))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
arr = np.asarray(small, dtype=np.float32)
|
||||
# Rec. 601 luma,保留 0-255 范围,便于和清晰度 / 对比度阈值一起看。
|
||||
gray = (0.299 * arr[:, :, 0] + 0.587 * arr[:, :, 1] + 0.114 * arr[:, :, 2]).astype(np.float32)
|
||||
center = gray[22:68, 40:120]
|
||||
gh, gw = gray.shape
|
||||
center = gray[gh // 4:max(gh // 4 + 1, gh * 3 // 4), gw // 4:max(gw // 4 + 1, gw * 3 // 4)]
|
||||
rg = arr[:, :, 0] - arr[:, :, 1]
|
||||
yb = 0.5 * (arr[:, :, 0] + arr[:, :, 1]) - arr[:, :, 2]
|
||||
colorfulness = float(np.sqrt(rg.var() + yb.var()) + 0.3 * np.sqrt(rg.mean() ** 2 + yb.mean() ** 2))
|
||||
@@ -432,6 +441,20 @@ def _frame_metrics(img_path: Path, idx: int, timestamp: float) -> dict | None:
|
||||
}
|
||||
|
||||
|
||||
def _scan_profile(duration: float, quality: FrameExtractQuality) -> tuple[float, int, int, int]:
|
||||
"""返回 scan_fps / scan_width / metric_width / estimated_count。"""
|
||||
if quality == "ultra":
|
||||
base_fps, scan_width, cap, metric_width = 12.0, 960, 1800, 320
|
||||
elif quality == "accurate":
|
||||
base_fps, scan_width, cap, metric_width = 8.0, 720, 900, 240
|
||||
else:
|
||||
base_fps, scan_width, cap, metric_width = 2.0, 360, 240, 160
|
||||
|
||||
estimated = max(1, min(int(duration * base_fps), cap))
|
||||
scan_fps = max(0.02, min(base_fps, estimated / max(duration, 0.1)))
|
||||
return scan_fps, scan_width, metric_width, estimated
|
||||
|
||||
|
||||
def _attach_temporal_metrics(items: list[dict]) -> None:
|
||||
"""相邻低清帧差异:转场 / 动作目标依赖它,不需要逐帧高分辨率扫描。"""
|
||||
for i, it in enumerate(items):
|
||||
|
||||
Reference in New Issue
Block a user