Initial commit: sanitize repository for remote push

This commit is contained in:
theshy
2026-03-21 01:36:28 +08:00
commit 3925cb508f
21 changed files with 3357 additions and 0 deletions

161
monitorSongs.py Normal file
View 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()