151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
"""命令行:ZED 分段录制。
|
||
|
||
- ``start``(默认):本进程内 ``start_zed_recording``,前台阻塞直到 Ctrl+C 再 ``stop``(不经过 HTTP)。
|
||
- ``start --remote``:``POST`` 已运行的 fish_api,不阻塞。
|
||
- ``stop`` / ``status``:仅 HTTP,用于停/查 **由 uvicorn 进程托管** 的录制。
|
||
|
||
HTTP 基址:环境变量 ``FISH_API_BASE_URL`` 或 ``PUBLIC_BASE_URL``,否则 ``http://127.0.0.1:8000``。
|
||
ZED 路由不校验 ingest API Key,请求无需 ``X-API-Key``。
|
||
|
||
每次启动由服务端分配 ``fish_id``(库表与 ``output_dir``、目标父目录下 ``fish``+数字 及 ``.svo2`` 路径综合)并写入 ``zed_recording_sessions``。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
import time
|
||
import urllib.error
|
||
import urllib.request
|
||
from typing import Any, Dict, Optional
|
||
|
||
|
||
def _base_url() -> str:
|
||
"""优先环境变量;否则从 ``fish_api/.env`` 经 ``get_settings()`` 读 ``FISH_API_BASE_URL`` / ``PUBLIC_BASE_URL``。"""
|
||
u = os.environ.get("FISH_API_BASE_URL") or os.environ.get("PUBLIC_BASE_URL")
|
||
if u:
|
||
return str(u).strip().rstrip("/")
|
||
from app.settings import get_settings
|
||
|
||
s = get_settings()
|
||
u2 = (s.fish_api_base_url or "").strip() or (s.public_base_url or "").strip()
|
||
if u2:
|
||
return u2.rstrip("/")
|
||
return "http://127.0.0.1:8000"
|
||
|
||
|
||
def _http_request_json(method: str, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||
url = _base_url() + path
|
||
if method == "GET":
|
||
req = urllib.request.Request(url, method="GET")
|
||
elif method == "POST":
|
||
payload = json.dumps(body if body is not None else {}).encode("utf-8")
|
||
req = urllib.request.Request(
|
||
url,
|
||
data=payload,
|
||
headers={"Content-Type": "application/json; charset=utf-8"},
|
||
method="POST",
|
||
)
|
||
else:
|
||
raise ValueError(method)
|
||
|
||
try:
|
||
with urllib.request.urlopen(req, timeout=120) as resp:
|
||
raw = resp.read().decode("utf-8")
|
||
if not raw.strip():
|
||
return {}
|
||
return json.loads(raw)
|
||
except urllib.error.HTTPError as e:
|
||
err_body = e.read().decode("utf-8", errors="replace")
|
||
print(f"HTTP {e.code}: {err_body}", file=sys.stderr)
|
||
raise SystemExit(1) from None
|
||
except urllib.error.URLError as e:
|
||
print(f"请求失败: {e}", file=sys.stderr)
|
||
raise SystemExit(1) from None
|
||
|
||
|
||
def _cmd_start_remote(args: argparse.Namespace) -> None:
|
||
body: Dict[str, Any] = {}
|
||
if args.segment_sec is not None:
|
||
body["segment_sec"] = args.segment_sec
|
||
r = _http_request_json("POST", "/api/v1/zed/recording/start", body)
|
||
print(json.dumps(r, ensure_ascii=False, indent=2))
|
||
|
||
|
||
def _cmd_start_local(args: argparse.Namespace) -> None:
|
||
from app.services.zed_recording_control import (
|
||
start_zed_recording,
|
||
stop_zed_recording,
|
||
)
|
||
from app.settings import get_settings
|
||
|
||
s = get_settings()
|
||
ok, msg, fish_id, _ = start_zed_recording(
|
||
s,
|
||
segment_sec=args.segment_sec,
|
||
)
|
||
if not ok:
|
||
print(f"启动失败: {msg}", file=sys.stderr)
|
||
raise SystemExit(1)
|
||
|
||
print(
|
||
f"fish_id={fish_id},录制中,按 Ctrl+C 停止…",
|
||
flush=True,
|
||
)
|
||
try:
|
||
while True:
|
||
time.sleep(1.0)
|
||
except KeyboardInterrupt:
|
||
print("\n正在停止…", flush=True)
|
||
stop_ok, stop_msg, stop_fish = stop_zed_recording(s)
|
||
if stop_ok:
|
||
print(f"已退出。fish_id={stop_fish}", flush=True)
|
||
else:
|
||
print(f"停止结果: {stop_msg}", file=sys.stderr)
|
||
|
||
|
||
def _cmd_stop(_args: argparse.Namespace) -> None:
|
||
r = _http_request_json("POST", "/api/v1/zed/recording/stop", {})
|
||
print(json.dumps(r, ensure_ascii=False, indent=2))
|
||
|
||
|
||
def _cmd_status(_args: argparse.Namespace) -> None:
|
||
r = _http_request_json("GET", "/api/v1/zed/recording/status", None)
|
||
print(json.dumps(r, ensure_ascii=False, indent=2))
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(
|
||
description="ZED 分段录制:本地阻塞 start,或 HTTP 调用 fish_api",
|
||
)
|
||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||
|
||
p_start = sub.add_parser("start", help="本地启动(阻塞至 Ctrl+C)或 --remote 仅请求 API")
|
||
p_start.add_argument(
|
||
"--remote",
|
||
action="store_true",
|
||
help="通过 HTTP 调用 fish_api,不阻塞本进程",
|
||
)
|
||
p_start.add_argument("--segment-sec", type=float, default=None, metavar="SEC")
|
||
|
||
sub.add_parser("stop", help="HTTP 停止 fish_api 进程中的录制")
|
||
sub.add_parser("status", help="HTTP 查询 fish_api 中的录制状态")
|
||
|
||
args = parser.parse_args()
|
||
|
||
if args.cmd == "start":
|
||
if args.remote:
|
||
_cmd_start_remote(args)
|
||
else:
|
||
_cmd_start_local(args)
|
||
elif args.cmd == "stop":
|
||
_cmd_stop(args)
|
||
elif args.cmd == "status":
|
||
_cmd_status(args)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|