# hls_cleanup.py # HLS文件夹清理进程 # 功能:定时清理过期的HLS会话文件夹,支持浮点数天数,不依赖摄像头配置 import os import sys import argparse import signal import shutil import time from datetime import datetime, timedelta from typing import List from utils.logger import get_logger logger = get_logger(__name__) running = True def parse_args(): """解析命令行参数""" parser = argparse.ArgumentParser(description="HLS Folder Cleanup Service") parser.add_argument("--hls-root-path", required=True, help="HLS root path") parser.add_argument("--retention-days", required=True, type=float, help="Retention days (supports float, e.g. 0.5 for 12 hours)") parser.add_argument("--interval", required=True, type=int, help="Cleanup interval in hours") return parser.parse_args() def parse_folder_time(folder_name: str) -> datetime: """ 从文件夹名称解析时间 格式: yyyyMMdd_HHmmss """ try: date_str, time_str = folder_name.split("_") return datetime.strptime(f"{date_str}{time_str}", "%Y%m%d%H%M%S") except (ValueError, IndexError) as e: raise ValueError(f"Invalid folder name format: {folder_name}") from e def cleanup_hls_folders(hls_root_path: str, retention_days: float) -> tuple: """ 清理过期的HLS会话文件夹 Args: hls_root_path: HLS根目录 retention_days: 保留天数(支持浮点数) Returns: (deleted_count, error_count) 删除数量和错误数量 """ if not os.path.exists(hls_root_path): logger.info(f"HLS root path does not exist: {hls_root_path}") return 0, 0 cutoff_time = datetime.now() - timedelta(days=retention_days) deleted_count = 0 error_count = 0 # 遍历所有摄像头目录 for camera_folder in os.listdir(hls_root_path): camera_path = os.path.join(hls_root_path, camera_folder) if not os.path.isdir(camera_path): continue # 遍历该摄像头下的所有会话文件夹 for session_folder in os.listdir(camera_path): session_path = os.path.join(camera_path, session_folder) if not os.path.isdir(session_path): continue try: # 解析文件夹名称获取时间 folder_time = parse_folder_time(session_folder) if folder_time < cutoff_time: shutil.rmtree(session_path) logger.info(f"Deleted: {session_path} (created: {folder_time})") deleted_count += 1 except ValueError as e: # 文件夹名称格式不匹配,跳过 logger.debug(f"Skipped: {session_path} ({e})") except Exception as e: logger.warning(f"Failed to delete {session_path}: {e}") error_count += 1 return deleted_count, error_count def signal_handler(signum, frame): """信号处理器""" global running logger.info(f"Received signal {signum}, shutting down...") running = False def main(): global running args = parse_args() # 注册信号处理器 signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) logger.info(f"HLS Cleanup Service starting") logger.info(f"Config: hls_root={args.hls_root_path}, " f"retention_days={args.retention_days}, interval={args.interval}h") # 主循环 while running: try: logger.info(f"Starting cleanup scan...") deleted, errors = cleanup_hls_folders(args.hls_root_path, args.retention_days) if deleted > 0 or errors > 0: logger.info(f"Cleanup completed: {deleted} deleted, {errors} errors " f"(retention: {args.retention_days} days)") else: logger.info(f"Cleanup scan completed, no folders to delete " f"(retention: {args.retention_days} days)") except Exception as e: logger.error(f"Error during cleanup: {e}") # 等待下一次轮询(每小时检查一次) for _ in range(args.interval * 3600): if not running: break time.sleep(1) logger.info("HLS Cleanup Service stopped") if __name__ == "__main__": main()