feat/ ver0.1
This commit is contained in:
@@ -71,10 +71,10 @@ def _load_weight_json(svo_path: Path, settings: Settings) -> Dict[str, Any]:
|
||||
|
||||
|
||||
def _find_preview_videos(output_dir: Path) -> Tuple[Optional[Path], Optional[Path]]:
|
||||
previews = sorted(output_dir.glob("*preview*.mp4"))
|
||||
previews = sorted(output_dir.rglob("*preview*.mp4"))
|
||||
if len(previews) >= 2:
|
||||
return previews[0], previews[1]
|
||||
all_mp4 = sorted(output_dir.glob("*.mp4"))
|
||||
all_mp4 = sorted(output_dir.rglob("*.mp4"))
|
||||
if len(all_mp4) >= 2:
|
||||
return all_mp4[0], all_mp4[1]
|
||||
if len(all_mp4) == 1:
|
||||
@@ -84,6 +84,46 @@ def _find_preview_videos(output_dir: Path) -> Tuple[Optional[Path], Optional[Pat
|
||||
return None, None
|
||||
|
||||
|
||||
def _split_sbs_video(src: Path, left_dst: Path, right_dst: Path) -> bool:
|
||||
"""Split a side-by-side stereo video (W x H where W == 2*H_single) into left/right halves.
|
||||
|
||||
Returns True if split succeeded, False otherwise (caller should fall back to copy).
|
||||
"""
|
||||
probe = subprocess.run(
|
||||
[
|
||||
"ffprobe", "-v", "quiet", "-print_format", "json",
|
||||
"-show_streams", str(src),
|
||||
],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if probe.returncode != 0:
|
||||
return False
|
||||
import json as _json
|
||||
try:
|
||||
streams = _json.loads(probe.stdout).get("streams", [])
|
||||
vstream = next((s for s in streams if s.get("codec_type") == "video"), None)
|
||||
if vstream is None:
|
||||
return False
|
||||
w, h = int(vstream["width"]), int(vstream["height"])
|
||||
except Exception:
|
||||
return False
|
||||
half_w = w // 2
|
||||
if half_w < 1 or w < h:
|
||||
return False
|
||||
|
||||
for crop, dst in [
|
||||
(f"crop={half_w}:{h}:{half_w}:0", left_dst),
|
||||
(f"crop={half_w}:{h}:0:0", right_dst),
|
||||
]:
|
||||
r = subprocess.run(
|
||||
["ffmpeg", "-y", "-i", str(src), "-vf", crop, "-an", "-q:v", "5", str(dst)],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
if r.returncode != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _publish_media(
|
||||
left: Optional[Path],
|
||||
right: Optional[Path],
|
||||
@@ -94,6 +134,13 @@ def _publish_media(
|
||||
right_dst = settings.media_root / "latest_right.mp4"
|
||||
base = settings.public_base_url.rstrip("/")
|
||||
|
||||
if left is not None and left == right and left.is_file():
|
||||
if _split_sbs_video(left, left_dst, right_dst):
|
||||
return (
|
||||
f"{base}/media/{left_dst.name}",
|
||||
f"{base}/media/{right_dst.name}",
|
||||
)
|
||||
|
||||
def publish(src: Optional[Path], dst: Path) -> str:
|
||||
if src is None or not src.is_file():
|
||||
return ""
|
||||
|
||||
@@ -29,22 +29,39 @@ def _fmt_body(resp: httpx.Response) -> Any:
|
||||
return resp.text
|
||||
|
||||
|
||||
_last_camera: dict | None = None
|
||||
_last_health: dict | None = None
|
||||
|
||||
|
||||
def _has_data(body: Any) -> bool:
|
||||
"""非空业务数据:code==200 且 data 里至少有一个非空字段。"""
|
||||
if not isinstance(body, dict):
|
||||
return False
|
||||
if body.get("code") != 200:
|
||||
return True # 错误也算"新信息"
|
||||
data = body.get("data", {})
|
||||
if not isinstance(data, dict):
|
||||
return bool(data)
|
||||
return any(bool(v) for v in data.values())
|
||||
|
||||
|
||||
async def poll_once(client: httpx.AsyncClient, base: str) -> None:
|
||||
global _last_camera, _last_health
|
||||
base = base.rstrip("/")
|
||||
camera_url = f"{base}/api/v1/biomass/real/camera/"
|
||||
health_url = f"{base}/api/v1/biomass/health/result/"
|
||||
r1 = await client.get(camera_url)
|
||||
r2 = await client.get(health_url)
|
||||
logger.info(
|
||||
"[real/camera/] HTTP {} | {}",
|
||||
r1.status_code,
|
||||
json.dumps(_fmt_body(r1), ensure_ascii=False),
|
||||
)
|
||||
logger.info(
|
||||
"[health/result/] HTTP {} | {}",
|
||||
r2.status_code,
|
||||
json.dumps(_fmt_body(r2), ensure_ascii=False),
|
||||
)
|
||||
b1 = _fmt_body(r1)
|
||||
b2 = _fmt_body(r2)
|
||||
changed1 = b1 != _last_camera
|
||||
changed2 = b2 != _last_health
|
||||
if changed1 and _has_data(b1):
|
||||
logger.info("[real/camera/] HTTP {} | {}", r1.status_code, json.dumps(b1, ensure_ascii=False))
|
||||
if changed2 and _has_data(b2):
|
||||
logger.info("[health/result/] HTTP {} | {}", r2.status_code, json.dumps(b2, ensure_ascii=False))
|
||||
_last_camera = b1
|
||||
_last_health = b2
|
||||
|
||||
|
||||
async def poll_loop(base: str, interval: float) -> None:
|
||||
|
||||
Reference in New Issue
Block a user