161 lines
5.7 KiB
Python
161 lines
5.7 KiB
Python
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() |