From 795e8acc6ce55d84cdebdb1cc1e38998c5db6455 Mon Sep 17 00:00:00 2001 From: ericwyuan Date: Thu, 20 Aug 2026 17:41:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=97=B6=E5=8C=BA=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=8C=97=E4=BA=AC=E6=97=B6=E5=8C=BA=20=E2=80=94=20Edge(UTC?= =?UTF-8?q?=E6=9C=BA=E5=99=A8)=E4=B8=89=E5=A4=84=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E5=A4=84=E7=90=86=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景: Oracle Edge 机器为 UTC 时区,NAS 与视频均为北京时间, 网页要求统一北京时区。排查结论: NAS 端(北京时间)与 UI(直读 MariaDB)均正确,问题集中在 Edge 端三处。 1. queue_manager: 7 处 datetime('now','localtime') 在 UTC 机器上 写入 UTC 时间(比北京慢8h),全部改为 datetime('now','+8 hours') 2. preprocessor.compute_timestamps: event_start_time 缺失时 fallback datetime.now() 用了 Edge 本地时间(UTC),改为北京时间 datetime.now(timezone(+8h));带时区的 ISO 输入统一转北京时间 3. nvidia_adapter._ts_to_seconds: 不支持生产格式 'YYYY-MM-DD HH:MM:SS'(split后int抛ValueError全部返回-1), 导致集锦视频永远为空、视频模式永远降级逐帧——上一轮引入的 bug,测试用 HH:MM:SS 格式未暴露。现支持两种格式;drawtext 标签与提示词同步为完整时间戳说明 数据修正: event 17(task 298 手动测试缺 event_start_time)的 frame_timestamp 全为 UTC,按视频文件名真实时间(04:34:10)重算; Edge 队列存量时间戳 +8h。 验证: 单测三例通过(生产格式解析/fallback北京时间/ISO带时区转换); task 41 新代码正确写入北京时间 17:36:52。 --- .../src/fam_edge/model_adapters/nvidia_adapter.py | 11 ++++++++--- fam-edge/src/fam_edge/queue/queue_manager.py | 14 +++++++------- .../fam_edge/video_preprocessor/preprocessor.py | 7 ++++++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/fam-edge/src/fam_edge/model_adapters/nvidia_adapter.py b/fam-edge/src/fam_edge/model_adapters/nvidia_adapter.py index 52268c5..6f68779 100644 --- a/fam-edge/src/fam_edge/model_adapters/nvidia_adapter.py +++ b/fam-edge/src/fam_edge/model_adapters/nvidia_adapter.py @@ -82,8 +82,13 @@ class NvidiaVisionAdapter(BaseModelAdapter): # ------------------------------------------------------------------ @staticmethod def _ts_to_seconds(ts: str) -> float: - """'HH:MM:SS' 或 'HH:MM:SS.mmm' -> 秒""" - parts = str(ts).strip().split(':') + """'2026-08-20 04:34:10'(生产格式)/ 'HH:MM:SS' -> 当日秒偏移""" + s = str(ts).strip() + # 绝对时间格式: 取时间部分(同一天 30min 片段内足够) + date_part, _, time_part = s.partition(' ') + if time_part and '-' in date_part: + s = time_part + parts = s.split(':') try: if len(parts) == 3: return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2]) @@ -230,7 +235,7 @@ class NvidiaVisionAdapter(BaseModelAdapter): ts_list = '\n'.join(f' 片段{i}: 原始时间 {ts}' for i, ts in enumerate(frame_timestamps, 1)) return f"""你是家庭监控视频分析助手。下面的视频是由一段长时间监控录像中抽取的片段集锦, 共 {len(frame_timestamps)} 个片段(每个约 3 秒),按顺序拼接。每个片段左上角叠加了 -原始时间戳(ts 后的 00-05-00 表示 00:05:00)。 +原始时间戳(ts 后的 2026-08-20 04-34-10 表示北京时间 2026年8月20日 04:34:10)。 片段时间对照: {ts_list} diff --git a/fam-edge/src/fam_edge/queue/queue_manager.py b/fam-edge/src/fam_edge/queue/queue_manager.py index e77a6e1..d8d946b 100644 --- a/fam-edge/src/fam_edge/queue/queue_manager.py +++ b/fam-edge/src/fam_edge/queue/queue_manager.py @@ -47,8 +47,8 @@ def _init_db(): error_message TEXT, failure_stage TEXT, retry_count INTEGER DEFAULT 0, - created_at TEXT DEFAULT (datetime('now', 'localtime')), - updated_at TEXT DEFAULT (datetime('now', 'localtime')), + created_at TEXT DEFAULT (datetime('now', '+8 hours')), + updated_at TEXT DEFAULT (datetime('now', '+8 hours')), delivered INTEGER DEFAULT 0, UNIQUE(nas_task_id) ) @@ -85,7 +85,7 @@ def enqueue(nas_task_id: int, video_filename: str, video_path: str, "UPDATE task_queue SET status='PENDING', result_json=NULL, error_message=NULL, " "failure_stage=NULL, retry_count=0, delivered=0, video_filename=?, video_path=?, " "camera_name=?, event_start_time=?, known_members_context=?, " - "updated_at=datetime('now','localtime') WHERE id=?", + "updated_at=datetime('now','+8 hours') WHERE id=?", (video_filename, video_path, camera_name, event_start_time, known_members_context, row['id']) ) @@ -105,7 +105,7 @@ def claim_next() -> Optional[Dict]: ).fetchone() if row: conn.execute( - "UPDATE task_queue SET status='PROCESSING', updated_at=datetime('now','localtime') WHERE id=?", + "UPDATE task_queue SET status='PROCESSING', updated_at=datetime('now','+8 hours') WHERE id=?", (row['id'],) ) conn.commit() @@ -123,7 +123,7 @@ def mark_success(task_id: int, result_json: str): conn = _get_conn() try: conn.execute( - "UPDATE task_queue SET status='SUCCESS', result_json=?, updated_at=datetime('now','localtime') WHERE id=?", + "UPDATE task_queue SET status='SUCCESS', result_json=?, updated_at=datetime('now','+8 hours') WHERE id=?", (result_json, task_id) ) conn.commit() @@ -136,7 +136,7 @@ def mark_failed(task_id: int, error_message: str, failure_stage: str = ''): try: conn.execute( "UPDATE task_queue SET status='FAILED', error_message=?, failure_stage=?, " - "updated_at=datetime('now','localtime') WHERE id=?", + "updated_at=datetime('now','+8 hours') WHERE id=?", (error_message, failure_stage, task_id) ) conn.commit() @@ -165,7 +165,7 @@ def mark_delivered(task_ids: List[int]): try: placeholders = ','.join('?' * len(task_ids)) conn.execute( - f"UPDATE task_queue SET delivered=1, updated_at=datetime('now','localtime') " + f"UPDATE task_queue SET delivered=1, updated_at=datetime('now','+8 hours') " f"WHERE id IN ({placeholders})", task_ids ) conn.commit() diff --git a/fam-edge/src/fam_edge/video_preprocessor/preprocessor.py b/fam-edge/src/fam_edge/video_preprocessor/preprocessor.py index 8a1e548..ec74d80 100644 --- a/fam-edge/src/fam_edge/video_preprocessor/preprocessor.py +++ b/fam-edge/src/fam_edge/video_preprocessor/preprocessor.py @@ -287,10 +287,15 @@ class VideoPreprocessor: duration = frame_count * 60 # 兜底 interval = duration / frame_count + from datetime import timedelta, timezone + # 统一北京时区: 视频均为北京时间录制,Edge 机器是 UTC, + # fallback 不能用本地 datetime.now() try: start_dt = datetime.fromisoformat(event_start_time.replace('Z', '+00:00')) + if start_dt.tzinfo is not None: + start_dt = start_dt.astimezone(timezone(timedelta(hours=8))).replace(tzinfo=None) except Exception: - start_dt = datetime.now() + start_dt = datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None) timestamps = [] for i in range(frame_count):