fix: 时区统一北京时区 — Edge(UTC机器)三处时间处理修复
背景: 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。
This commit is contained in:
@@ -82,8 +82,13 @@ class NvidiaVisionAdapter(BaseModelAdapter):
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _ts_to_seconds(ts: str) -> float:
|
def _ts_to_seconds(ts: str) -> float:
|
||||||
"""'HH:MM:SS' 或 'HH:MM:SS.mmm' -> 秒"""
|
"""'2026-08-20 04:34:10'(生产格式)/ 'HH:MM:SS' -> 当日秒偏移"""
|
||||||
parts = str(ts).strip().split(':')
|
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:
|
try:
|
||||||
if len(parts) == 3:
|
if len(parts) == 3:
|
||||||
return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
|
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))
|
ts_list = '\n'.join(f' 片段{i}: 原始时间 {ts}' for i, ts in enumerate(frame_timestamps, 1))
|
||||||
return f"""你是家庭监控视频分析助手。下面的视频是由一段长时间监控录像中抽取的片段集锦,
|
return f"""你是家庭监控视频分析助手。下面的视频是由一段长时间监控录像中抽取的片段集锦,
|
||||||
共 {len(frame_timestamps)} 个片段(每个约 3 秒),按顺序拼接。每个片段左上角叠加了
|
共 {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}
|
{ts_list}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ def _init_db():
|
|||||||
error_message TEXT,
|
error_message TEXT,
|
||||||
failure_stage TEXT,
|
failure_stage TEXT,
|
||||||
retry_count INTEGER DEFAULT 0,
|
retry_count INTEGER DEFAULT 0,
|
||||||
created_at TEXT DEFAULT (datetime('now', 'localtime')),
|
created_at TEXT DEFAULT (datetime('now', '+8 hours')),
|
||||||
updated_at TEXT DEFAULT (datetime('now', 'localtime')),
|
updated_at TEXT DEFAULT (datetime('now', '+8 hours')),
|
||||||
delivered INTEGER DEFAULT 0,
|
delivered INTEGER DEFAULT 0,
|
||||||
UNIQUE(nas_task_id)
|
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, "
|
"UPDATE task_queue SET status='PENDING', result_json=NULL, error_message=NULL, "
|
||||||
"failure_stage=NULL, retry_count=0, delivered=0, video_filename=?, video_path=?, "
|
"failure_stage=NULL, retry_count=0, delivered=0, video_filename=?, video_path=?, "
|
||||||
"camera_name=?, event_start_time=?, known_members_context=?, "
|
"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,
|
(video_filename, video_path, camera_name, event_start_time,
|
||||||
known_members_context, row['id'])
|
known_members_context, row['id'])
|
||||||
)
|
)
|
||||||
@@ -105,7 +105,7 @@ def claim_next() -> Optional[Dict]:
|
|||||||
).fetchone()
|
).fetchone()
|
||||||
if row:
|
if row:
|
||||||
conn.execute(
|
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'],)
|
(row['id'],)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -123,7 +123,7 @@ def mark_success(task_id: int, result_json: str):
|
|||||||
conn = _get_conn()
|
conn = _get_conn()
|
||||||
try:
|
try:
|
||||||
conn.execute(
|
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)
|
(result_json, task_id)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -136,7 +136,7 @@ def mark_failed(task_id: int, error_message: str, failure_stage: str = ''):
|
|||||||
try:
|
try:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE task_queue SET status='FAILED', error_message=?, failure_stage=?, "
|
"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)
|
(error_message, failure_stage, task_id)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@@ -165,7 +165,7 @@ def mark_delivered(task_ids: List[int]):
|
|||||||
try:
|
try:
|
||||||
placeholders = ','.join('?' * len(task_ids))
|
placeholders = ','.join('?' * len(task_ids))
|
||||||
conn.execute(
|
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
|
f"WHERE id IN ({placeholders})", task_ids
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -287,10 +287,15 @@ class VideoPreprocessor:
|
|||||||
duration = frame_count * 60 # 兜底
|
duration = frame_count * 60 # 兜底
|
||||||
|
|
||||||
interval = duration / frame_count
|
interval = duration / frame_count
|
||||||
|
from datetime import timedelta, timezone
|
||||||
|
# 统一北京时区: 视频均为北京时间录制,Edge 机器是 UTC,
|
||||||
|
# fallback 不能用本地 datetime.now()
|
||||||
try:
|
try:
|
||||||
start_dt = datetime.fromisoformat(event_start_time.replace('Z', '+00:00'))
|
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:
|
except Exception:
|
||||||
start_dt = datetime.now()
|
start_dt = datetime.now(timezone(timedelta(hours=8))).replace(tzinfo=None)
|
||||||
|
|
||||||
timestamps = []
|
timestamps = []
|
||||||
for i in range(frame_count):
|
for i in range(frame_count):
|
||||||
|
|||||||
Reference in New Issue
Block a user