[2.1-2.5] FAM-Core 六个子模块 - Scheduler/Dispatcher/Event-Receiver/Chat-Handler/Member-Manager/Video-Server + DB层 + 配置
This commit is contained in:
0
fam-core/src/fam_core/scheduler/__init__.py
Normal file
0
fam-core/src/fam_core/scheduler/__init__.py
Normal file
111
fam-core/src/fam_core/scheduler/scheduler.py
Normal file
111
fam-core/src/fam_core/scheduler/scheduler.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Task-Scheduler - 60s 轮询视频目录,创建 PENDING 任务
|
||||
|
||||
判定视频完成(is_complete):
|
||||
- 修改时间 > 60s(文件已停止写入)
|
||||
- 文件大小稳定(连续两次检查大小一致)
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
from ..logger import setup_logger, log_task
|
||||
from ..config_loader import load_config
|
||||
from .. import db_layer
|
||||
|
||||
logger = setup_logger('fam-core.scheduler')
|
||||
|
||||
|
||||
class TaskScheduler:
|
||||
"""视频目录扫描器,60s 轮询"""
|
||||
|
||||
def __init__(self):
|
||||
cfg = load_config()
|
||||
self.scan_interval = cfg.get('scheduler', {}).get('scan_interval', 60)
|
||||
self.video_dir = cfg.get('scheduler', {}).get('video_dir', '/volume1/surveillance')
|
||||
self.media_base_url = cfg.get('video_server', {}).get('base_url', 'http://127.0.0.1:8000/media')
|
||||
self.video_extensions = cfg.get('scheduler', {}).get('video_extensions', ['.mp4', '.mkv', '.avi'])
|
||||
self.file_stable_seconds = cfg.get('scheduler', {}).get('file_stable_seconds', 60)
|
||||
self.camera_name = cfg.get('scheduler', {}).get('camera_name', '默认摄像头')
|
||||
|
||||
# 文件大小缓存,用于判断文件是否稳定
|
||||
self._file_sizes: dict = {} # path -> size
|
||||
self._running = False
|
||||
self._thread = None
|
||||
|
||||
def _is_video(self, filename):
|
||||
return any(filename.lower().endswith(ext) for ext in self.video_extensions)
|
||||
|
||||
def _is_complete(self, filepath):
|
||||
"""判断视频是否已停止写入"""
|
||||
try:
|
||||
stat = os.stat(filepath)
|
||||
now = time.time()
|
||||
# 修改时间距当前 > stable_seconds
|
||||
if now - stat.st_mtime < self.file_stable_seconds:
|
||||
return False
|
||||
# 文件大小稳定(与上次检查一致)
|
||||
prev_size = self._file_sizes.get(filepath)
|
||||
if prev_size is not None and prev_size == stat.st_size:
|
||||
return True
|
||||
self._file_sizes[filepath] = stat.st_size
|
||||
return False
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def _build_video_url(self, filepath):
|
||||
"""构建 Video-Server 下载 URL"""
|
||||
filename = os.path.basename(filepath)
|
||||
token = load_config().get('video_server', {}).get('token', '')
|
||||
return f"{self.media_base_url}/{filename}?token={token}"
|
||||
|
||||
def scan_once(self):
|
||||
"""执行一次扫描"""
|
||||
if not os.path.isdir(self.video_dir):
|
||||
logger.warning(f"视频目录不存在: {self.video_dir}")
|
||||
return
|
||||
|
||||
new_count = 0
|
||||
for root, dirs, files in os.walk(self.video_dir):
|
||||
for filename in files:
|
||||
if not self._is_video(filename):
|
||||
continue
|
||||
filepath = os.path.join(root, filename)
|
||||
if not self._is_complete(filepath):
|
||||
continue
|
||||
# 检查是否已有任务
|
||||
if db_layer.get_video_url_exists(filepath):
|
||||
continue
|
||||
# 创建新任务
|
||||
video_url = self._build_video_url(filepath)
|
||||
task_id = db_layer.create_task(filepath, video_url)
|
||||
new_count += 1
|
||||
log_task(logger, task_id, 'scheduler', f'新任务: {filename}')
|
||||
|
||||
if new_count > 0:
|
||||
logger.info(f"本次扫描发现 {new_count} 个新视频")
|
||||
|
||||
def _run(self):
|
||||
"""线程主循环"""
|
||||
logger.info(f"Task-Scheduler 启动,扫描间隔 {self.scan_interval}s,目录: {self.video_dir}")
|
||||
while self._running:
|
||||
try:
|
||||
self.scan_once()
|
||||
except Exception as e:
|
||||
logger.error(f"扫描异常: {e}", exc_info=True)
|
||||
time.sleep(self.scan_interval)
|
||||
|
||||
def start(self):
|
||||
"""启动调度线程"""
|
||||
if self._running:
|
||||
return
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._run, daemon=True, name='task-scheduler')
|
||||
self._thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""停止调度线程"""
|
||||
self._running = False
|
||||
if self._thread:
|
||||
self._thread.join(timeout=5)
|
||||
Reference in New Issue
Block a user