This commit is contained in:
zaiun xu
2026-04-13 17:13:02 +08:00
parent 6f006def64
commit c1aafc69bf
8 changed files with 580 additions and 66 deletions

View File

@@ -6,13 +6,19 @@ import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
from typing import List
from app.logging_config import format_json_pretty
from app.services.video_slice import get_video_duration, slice_video
from app.settings import Settings
from app.state import HealthSnapshot
from app.subprocess_run import run_subprocess_with_log
from loguru import logger
# 视频切片配置
DEFAULT_SLICE_DURATION = 10.0 # 每10秒切一个片段
DEFAULT_MIN_DURATION_FOR_SLICE = 15.0 # 视频超过15秒才切片
BEHAVIOR_EN_TO_ZH = {
"feeding": "吃饵",
"normal": "正常游行",
@@ -100,8 +106,95 @@ def run_action_subprocess(mp4_path: Path, settings: Settings) -> str:
Path(out_json).unlink(missing_ok=True)
def run_full_action(mp4_path: Path, settings: Settings) -> HealthSnapshot:
def run_full_action(mp4_path: Path, settings: Settings) -> tuple[HealthSnapshot, list[HealthSnapshot]]:
"""运行 FishAction 健康检测。如果视频较长,会自动切片后分别检测。
每个切片被视为独立的视频,返回所有切片的结果列表。
Args:
mp4_path: 输入视频路径
settings: 应用配置
Returns:
tuple[HealthSnapshot, list[HealthSnapshot]]: (第一个切片/完整视频的快照, 所有切片快照列表)
- 如果视频被切片:返回 (第一个切片, 所有切片列表)
- 如果视频未被切片:返回 (完整视频快照, [完整视频快照])
"""
logger.info("[FishAction] start mp4={}", mp4_path.resolve())
# 检查视频时长
duration = get_video_duration(mp4_path)
should_slice = duration > DEFAULT_MIN_DURATION_FOR_SLICE
if should_slice:
# 视频较长,需要切片处理
logger.info(
"[FishAction] video duration {}s > {}s, slicing into {}s segments",
duration,
DEFAULT_MIN_DURATION_FOR_SLICE,
DEFAULT_SLICE_DURATION,
)
slice_files, slice_dir = slice_video(mp4_path, DEFAULT_SLICE_DURATION)
if len(slice_files) > 1:
logger.info(
"[FishAction] processing {} slices for {}",
len(slice_files),
mp4_path.name,
)
# 处理每个切片
all_snaps: list[HealthSnapshot] = []
for i, slice_file in enumerate(slice_files):
start_time = i * DEFAULT_SLICE_DURATION
end_time = min(start_time + DEFAULT_SLICE_DURATION, duration)
try:
pred_en = run_action_subprocess(slice_file, settings)
zh = BEHAVIOR_EN_TO_ZH[pred_en]
health = behavior_to_health(pred_en)
snap = HealthSnapshot(
behavior_result=zh,
health_result=health,
updated_at=datetime.now(timezone.utc),
raw_class_en=pred_en,
)
logger.info(
"[FishAction] slice {} ({}s-{}s): pred={} behavior={} health={}",
i, start_time, end_time, pred_en, zh, health,
)
all_snaps.append(snap)
except Exception as e:
logger.error("[FishAction] failed to process slice {}: {}", i, e)
# 创建一个表示失败的快照
error_snap = HealthSnapshot(
behavior_result="处理失败",
health_result="未知",
updated_at=datetime.now(timezone.utc),
raw_class_en="error",
error=str(e),
)
all_snaps.append(error_snap)
logger.info(
"[FishAction] done mp4={} total_slices={}",
mp4_path.name,
len(slice_files),
)
# 返回第一个切片的结果和所有切片列表
first_snap = all_snaps[0] if all_snaps else HealthSnapshot(
behavior_result="",
health_result="",
updated_at=datetime.now(timezone.utc),
)
return first_snap, all_snaps
# 视频较短,直接处理(原有逻辑)
pred_en = run_action_subprocess(mp4_path, settings)
zh = BEHAVIOR_EN_TO_ZH[pred_en]
health = behavior_to_health(pred_en)
@@ -112,9 +205,10 @@ def run_full_action(mp4_path: Path, settings: Settings) -> HealthSnapshot:
zh,
health,
)
return HealthSnapshot(
snap = HealthSnapshot(
behavior_result=zh,
health_result=health,
updated_at=datetime.now(timezone.utc),
raw_class_en=pred_en,
)
return snap, [snap]