fix(锁与查询组): #12 熔断器状态转换加锁;#10 问答人名匹配改 JSON_CONTAINS 精确匹配(防 LIKE 子串/特殊字符误匹配);#4 OracleDB 复合写加锁;#6 每消费者独立 VideoProcessor(消除 adapter.timeout 共享竞争);#15 done 视频文件被覆盖(mtime>processed_at)自动重置重分析
This commit is contained in:
@@ -32,7 +32,6 @@ class VideoQueue:
|
||||
def __init__(self, db: oracle_db.OracleDB):
|
||||
self.config = load_config()
|
||||
self.db = db
|
||||
self.processor = VideoProcessor(db)
|
||||
self.local_dir = self.config.get('gdrive_sync', {}).get(
|
||||
'local_dir', '/opt/fam-edge/gdrive_videos')
|
||||
self.watch_interval = self.config.get('gdrive_sync', {}).get(
|
||||
@@ -105,6 +104,28 @@ class VideoQueue:
|
||||
self._enqueue(vid)
|
||||
elif row['status'] in ('pending', 'failed') and self._retry_allowed(row):
|
||||
self._enqueue(row['id'])
|
||||
elif row['status'] == 'done' and self._file_changed(row, path):
|
||||
# 文件被覆盖(rclone 重新同步/更新):重置 pending 重新分析
|
||||
logger.info(f"文件内容变更,重置重新分析: {fn} (id={row['id']})")
|
||||
self.db._conn.execute(
|
||||
"UPDATE videos SET status='pending', retry_count=0, summary_json=NULL, "
|
||||
"events_json=NULL, people_json=NULL, compute_provider=NULL, "
|
||||
"processed_at=NULL, file_valid=1 WHERE id=?",
|
||||
(row['id'],))
|
||||
self.db._conn.commit()
|
||||
self._enqueue(row['id'])
|
||||
|
||||
def _file_changed(self, row, path: str) -> bool:
|
||||
"""判断视频文件在处理后被覆盖(mtime 晚于 processed_at)。"""
|
||||
try:
|
||||
proc = row['processed_at'] or ''
|
||||
if not proc:
|
||||
return False
|
||||
from datetime import datetime
|
||||
pt = datetime.strptime(proc[:19], '%Y-%m-%d %H:%M:%S').timestamp()
|
||||
return os.path.getmtime(path) > pt + 5 # 5s 容差
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _enqueue_existing(self):
|
||||
"""启动时把 DB 中未处理完的视频补入队(进程重启恢复)"""
|
||||
@@ -161,13 +182,16 @@ class VideoQueue:
|
||||
# 消费者
|
||||
# ------------------------------------------------------------------
|
||||
def _consume_loop(self):
|
||||
# 每消费者独立 VideoProcessor(各自 build_adapters),
|
||||
# 避免多消费者共享 adapter 实例导致 timeout 等实例属性改写竞争
|
||||
processor = VideoProcessor(self.db)
|
||||
while not self._consumer_stop.is_set():
|
||||
try:
|
||||
video_id = self._queue.get(timeout=1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
try:
|
||||
self._consume_one(video_id)
|
||||
self._consume_one(video_id, processor)
|
||||
except Exception as e:
|
||||
logger.error(f"消费 video_id={video_id} 异常: {e}", exc_info=True)
|
||||
finally:
|
||||
@@ -175,7 +199,7 @@ class VideoQueue:
|
||||
self._queued.discard(video_id)
|
||||
self._queue.task_done()
|
||||
|
||||
def _consume_one(self, video_id: int):
|
||||
def _consume_one(self, video_id: int, processor: VideoProcessor):
|
||||
row = self.db.get_video_by_id(video_id)
|
||||
if row is None:
|
||||
logger.warning(f"消费到不存在的 video_id={video_id},跳过")
|
||||
@@ -185,7 +209,7 @@ class VideoQueue:
|
||||
if not self._retry_allowed(row):
|
||||
logger.warning(f"[video_id={video_id}] 已达重试上限({row['retry_count']}),放弃")
|
||||
return
|
||||
ok = self.processor.process_video(
|
||||
ok = processor.process_video(
|
||||
video_id, row['filename'], row['local_path'],
|
||||
timeout_multiplier=self.timeout_multiplier)
|
||||
if ok:
|
||||
|
||||
Reference in New Issue
Block a user