Initial commit: sanitize repository for remote push
This commit is contained in:
161
monitorSongs.py
Normal file
161
monitorSongs.py
Normal file
@ -0,0 +1,161 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user