import os import time import json import subprocess from pathlib import Path from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from logger import get_system_logger, log_exception # ========================================== # 接口配置 # ========================================== SESSION_DIR = r'./session' CHECK_INTERVAL = 2 OUTPUT_SUBDIR = "split_video" DONE_FLAG = "split_done.flag" # 初始化日志 logger = get_system_logger('monitorSongs') # ========================================== class SongsJsonHandler(FileSystemEventHandler): def on_created(self, event): if not event.is_directory and event.src_path.lower().endswith('songs.json'): logger.debug(f"检测到歌曲列表创建: {event.src_path}") self.process_video_splitting(Path(event.src_path)) def on_moved(self, event): if not event.is_directory and event.dest_path.lower().endswith('songs.json'): logger.debug(f"检测到歌曲列表移动: {event.dest_path}") self.process_video_splitting(Path(event.dest_path)) def process_video_splitting(self, json_path): work_dir = json_path.parent split_dir = work_dir / OUTPUT_SUBDIR flag_file = work_dir / DONE_FLAG # 1. 检查标记位 if flag_file.exists(): logger.debug(f"切割已完成,跳过: {work_dir.name}") return logger.info("="*50) logger.info(f"检测到新歌曲列表: {work_dir.name}") logger.info("="*50) # 2. 读取并修正 JSON 时间格式 try: with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) songs = data.get('songs', []) logger.info(f"读取到 {len(songs)} 首歌曲") except Exception as e: log_exception(logger, e, f"读取 JSON 失败: {json_path}") return # 3. 定位源视频 source_video = None video_exts = {'.mp4', '.mkv', '.avi', '.mov', '.flv', '.wmv'} for f in work_dir.iterdir(): if f.suffix.lower() in video_exts: source_video = f break if not source_video: logger.error(f"工作区内未找到源视频文件,跳过") return logger.info(f"源视频: {source_video.name}") split_dir.mkdir(parents=True, exist_ok=True) # 4. 循环切割 logger.info(f"开始切割视频片段 (Stream Copy)...") success_count = 0 fail_count = 0 for idx, song in enumerate(songs, 1): # --- 关键修正:将时间戳中的逗号替换为点号 --- raw_start = song.get('start', '00:00:00.000') raw_end = song.get('end', '00:00:00.000') start = raw_start.replace(',', '.') end = raw_end.replace(',', '.') title = song.get('title', 'UNKNOWN').replace('/', '_').replace('\\', '_') artist = song.get('artist', 'UNKNOWN') output_filename = f"{idx:02d}_{title}{source_video.suffix}" output_path = split_dir / output_filename if output_path.exists(): logger.info(f"[{idx}] 已存在,跳过: {title}") continue # 构建高效率切割命令 # 注意:-ss 在 -i 前面是为了快速定位且避免不必要的解码 cmd = [ 'ffmpeg', '-y', '-ss', start, '-to', end, '-i', str(source_video), '-c', 'copy', '-map_metadata', '0', str(output_path) ] try: # 使用 subprocess.run 配合 capture_output 捕获详细错误 res = subprocess.run(cmd, capture_output=True, check=True) logger.info(f"[{idx}] ✓ {title} - {artist}") success_count += 1 except subprocess.CalledProcessError as e: logger.error(f"[{idx}] ✗ {title} 切割失败") logger.error(f"FFmpeg 错误: {e.stderr.decode('utf-8', errors='ignore')[:200]}") fail_count += 1 # 5. 生成完成标记 flag_file.touch() logger.info("="*50) logger.info(f"切割任务完成: 成功 {success_count} / 失败 {fail_count}") logger.info(f"输出目录: {split_dir}") logger.info("="*50) def main(): path = Path(SESSION_DIR) path.mkdir(parents=True, exist_ok=True) logger.info("="*50) logger.info("视频切割模块启动") logger.info("="*50) logger.info(f"监控目录: {SESSION_DIR}") event_handler = SongsJsonHandler() observer = Observer() observer.schedule(event_handler, str(path), recursive=True) # 启动扫描已存在的 songs.json logger.info("扫描现有歌曲列表...") scan_count = 0 for sub_dir in path.iterdir(): if sub_dir.is_dir(): json_file = sub_dir / "songs.json" if json_file.exists(): logger.info(f"发现已存在的歌曲列表: {sub_dir.name}") event_handler.process_video_splitting(json_file) scan_count += 1 logger.info(f"扫描完成,处理 {scan_count} 个歌曲列表") observer.start() logger.info("文件监控已启动") try: while True: time.sleep(CHECK_INTERVAL) except KeyboardInterrupt: logger.info("接收到停止信号,正在关闭...") observer.stop() observer.join() logger.info("视频切割模块已停止") if __name__ == "__main__": main()