实现 video batch 自动清理与按需标注视频,并补充子进程调用测试。
batch 完成后仅保留数据库文本结果,勾选时才生成临时标注视频(24h TTL);新增 FastAPI 到 reference bundle 与 algorithm_runner 的单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,7 +21,14 @@ from app.schemas import SurgeryApiResponse, SurgeryStartRequest
|
||||
from app.or_site_config import merge_video_rtsp_urls_into_file
|
||||
from app.services.synthetic_rtsp import StreamSpec, SyntheticRtspManager
|
||||
from app.services.surgery_pipeline import SurgeryPipeline
|
||||
from app.services.video_batch_runner import VideoBatchRunner, VideoBatchRunResult
|
||||
from app.baked import pipeline as bp
|
||||
from app.services.video_batch_cleanup import (
|
||||
purge_batch_artifacts,
|
||||
purge_expired_visualizations,
|
||||
purge_surgery_batch_tree,
|
||||
stage_visualization_pending,
|
||||
)
|
||||
from app.services.video_batch_runner import VideoBatchRunner
|
||||
from app.services.voice_terminal_hub import (
|
||||
VoiceTerminalHub,
|
||||
assign_voice_terminal_after_recording_started,
|
||||
@@ -31,25 +38,29 @@ from app.surgery_errors import SurgeryPipelineError
|
||||
router = APIRouter(prefix="/internal/demo", tags=["demo"])
|
||||
|
||||
# Bumped when video-batch flow changes; grep this string in logs after restart to confirm new code.
|
||||
VIDEO_BATCH_FLOW_MARKER = "early-save+background-vis-v3"
|
||||
VIDEO_BATCH_FLOW_MARKER = "purge-all+opt-in-vis-v4"
|
||||
|
||||
|
||||
def _background_finalize_visualization(
|
||||
runner: VideoBatchRunner,
|
||||
result: VideoBatchRunResult,
|
||||
surgery_id: str,
|
||||
) -> None:
|
||||
try:
|
||||
runner.finalize_visualization(result, surgery_id=surgery_id)
|
||||
runner.finalize_visualization(surgery_id=surgery_id)
|
||||
except Exception:
|
||||
logger.exception("video batch background visualization failed surgery_id={}", surgery_id)
|
||||
finally:
|
||||
purge_expired_visualizations(
|
||||
runner.root_dir,
|
||||
ttl_hours=float(bp.VIDEO_BATCH_VIS_TTL_HOURS),
|
||||
)
|
||||
|
||||
|
||||
class VideoBatchSurgeryResponse(BaseModel):
|
||||
surgery_id: str
|
||||
status: str
|
||||
message: str
|
||||
visualization_url: str
|
||||
visualization_url: str | None = None
|
||||
doctor_name: str | None = None
|
||||
doctor_id: str | None = None
|
||||
doctor_display: str | None = None
|
||||
@@ -72,7 +83,7 @@ def _orchestrate_write_rtsp_host() -> str:
|
||||
summary="非实时精确模式:上传单路 MP4 并跑配置引用包 batch",
|
||||
description=(
|
||||
"仅当 DEMO_ORCHESTRATOR_ENABLED=true。保存上传视频,调用配置算法子进程包 main.py(默认 algorithm_subprocesses/5.15),"
|
||||
"解析 TSV 后写入最终结果,并调用 visualize_result_video.py 生成带标签视频。"
|
||||
"解析 TSV 后写入最终结果;可选 include_visualization 生成临时标注视频。"
|
||||
),
|
||||
)
|
||||
async def video_batch_surgery(
|
||||
@@ -80,6 +91,7 @@ async def video_batch_surgery(
|
||||
surgery_id: Annotated[str, Form()],
|
||||
video1: Annotated[UploadFile, File(description="单路完整 MP4")],
|
||||
candidate_consumables_json: Annotated[str, Form()] = "[]",
|
||||
include_visualization: Annotated[bool, Form()] = False,
|
||||
pipeline: SurgeryPipeline = Depends(get_surgery_pipeline),
|
||||
) -> SurgeryApiResponse:
|
||||
if len(surgery_id) != 6 or not surgery_id.isdigit():
|
||||
@@ -113,9 +125,10 @@ async def video_batch_surgery(
|
||||
detail="video1 is empty",
|
||||
)
|
||||
logger.info(
|
||||
"video batch request surgery_id={} flow={}",
|
||||
"video batch request surgery_id={} flow={} include_visualization={}",
|
||||
surgery_id,
|
||||
VIDEO_BATCH_FLOW_MARKER,
|
||||
include_visualization,
|
||||
)
|
||||
runner = VideoBatchRunner()
|
||||
suffix = Path(video1.filename or "video.mp4").suffix or ".mp4"
|
||||
@@ -155,13 +168,34 @@ async def video_batch_surgery(
|
||||
surgery_id,
|
||||
)
|
||||
|
||||
background_tasks.add_task(_background_finalize_visualization, runner, result, surgery_id)
|
||||
visualization_url = f"/internal/demo/video-batch-surgery/{surgery_id}/visualization"
|
||||
cache_input = result.output_path.parent.parent / "input" / "input.mp4"
|
||||
if include_visualization:
|
||||
stage_visualization_pending(
|
||||
runner.root_dir,
|
||||
surgery_id,
|
||||
source_mp4=cache_input if cache_input.is_file() else result.input_path,
|
||||
result_tsv=result.output_path,
|
||||
)
|
||||
background_tasks.add_task(_background_finalize_visualization, runner, surgery_id)
|
||||
|
||||
purge_batch_artifacts(
|
||||
runner.root_dir,
|
||||
surgery_id,
|
||||
digest=result.video_sha256,
|
||||
candidate_key=result.candidate_cache_key,
|
||||
)
|
||||
purge_surgery_batch_tree(runner.root_dir, surgery_id)
|
||||
|
||||
visualization_url: str | None = None
|
||||
if include_visualization:
|
||||
visualization_url = f"/internal/demo/video-batch-surgery/{surgery_id}/visualization"
|
||||
doctor = result.doctor
|
||||
doctor_suffix = ""
|
||||
if doctor is not None and doctor.display:
|
||||
doctor_suffix = f";医生={doctor.display}"
|
||||
vis_suffix = ";标注视频后台生成中(完成后刷新 visualization URL)"
|
||||
vis_suffix = ""
|
||||
if include_visualization:
|
||||
vis_suffix = ";标注视频后台生成中(完成后刷新 visualization URL,24 小时内有效)"
|
||||
return VideoBatchSurgeryResponse(
|
||||
surgery_id=surgery_id,
|
||||
status="accepted",
|
||||
|
||||
Reference in New Issue
Block a user