| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 
 | 
 
 
 import os
 import sys
 import subprocess
 import argparse
 from pathlib import Path
 
 
 FFPROBE_PATH = '/var/packages/ffmpeg/target/bin/ffprobe'
 SUPPORTED_FORMATS = {
 '.mp4', '.avi', '.wmv', '.mkv', '.flv',
 '.mov', '.rmvb', '.amv', '.m1v', '.m2ts',
 '.m2v', '.m4v', '.swf', '.ts'
 }
 
 def setup_arguments():
 """初始化命令行参数解析器"""
 parser = argparse.ArgumentParser(
 description='为Synology Photos生成视频缩略图'
 )
 parser.add_argument(
 "-o", "--overwrite",
 action="store_true",
 help="覆盖已存在的缩略图文件"
 )
 return parser.parse_args()
 
 def get_video_duration(video_path):
 """使用ffprobe获取视频时长(秒)"""
 cmd = [
 FFPROBE_PATH,
 '-loglevel', 'error',
 '-show_entries', 'format=duration',
 '-of', 'csv=p=0',
 str(video_path)
 ]
 try:
 result = subprocess.run(
 cmd,
 check=True,
 stdout=subprocess.PIPE,
 stderr=subprocess.PIPE,
 text=True
 )
 return float(result.stdout.strip())
 except subprocess.CalledProcessError as e:
 print(f"[错误] 无法获取视频时长 {video_path}: {e.stderr}")
 return None
 
 def generate_thumbnail(video_path, output_path, duration):
 """在视频中点生成缩略图"""
 midpoint = duration / 2
 cmd = [
 'ffmpeg',
 '-loglevel', 'warning',
 '-ss', str(midpoint),
 '-i', str(video_path),
 '-y',
 '-vframes', '1',
 '-vf', 'scale=-1:480',
 str(output_path)
 ]
 try:
 subprocess.run(cmd, check=True)
 return True
 except subprocess.CalledProcessError as e:
 print(f"[错误] 生成缩略图失败: {e.stderr}")
 return False
 
 def process_video_file(video_path, args):
 """处理单个视频文件生成缩略图"""
 video_dir = video_path.parent
 thumb_dir = video_dir / '@eaDir' / video_path.name
 
 try:
 
 for fail_file in thumb_dir.glob('*.fail'):
 fail_file.unlink()
 
 
 thumb_dir.mkdir(parents=True, exist_ok=True)
 
 
 thumb_m = thumb_dir / 'SYNOPHOTO_THUMB_M.jpg'
 thumb_xl = thumb_dir / 'SYNOVIDEO_VIDEO_SCREENSHOT.jpg'
 
 
 duration = get_video_duration(video_path)
 if duration is None:
 return False
 
 
 if args.overwrite or not thumb_m.exists():
 print(f'正在生成中等缩略图: {thumb_m}')
 if not generate_thumbnail(video_path, thumb_m, duration):
 return False
 
 
 if args.overwrite or not thumb_xl.exists():
 print(f'正在生成大尺寸缩略图: {thumb_xl}')
 if not generate_thumbnail(video_path, thumb_xl, duration):
 return False
 
 return True
 
 except Exception as e:
 print(f"[错误] 处理 {video_path} 时出错: {str(e)}")
 return False
 finally:
 
 if thumb_dir.exists() and not any(thumb_dir.iterdir()):
 thumb_dir.rmdir()
 
 def generate_all_thumbnails(args):
 """主函数:遍历目录生成所有缩略图"""
 base_path = Path(__file__).parent.resolve()
 processed_count = 0
 success_count = 0
 
 for item in base_path.rglob('*'):
 if item.is_file() and item.suffix.lower() in SUPPORTED_FORMATS:
 processed_count += 1
 print(f"\n正在处理 [{processed_count}] {item}")
 success = process_video_file(item, args)
 if success:
 success_count += 1
 print(f"[成功] 已处理: {item}")
 else:
 print(f"[失败] 处理失败: {item}")
 
 print(f"\n处理完成。总计: {processed_count} 个文件, 成功: {success_count} 个, 失败: {processed_count-success_count} 个")
 
 if __name__ == '__main__':
 print("=== Synology 视频缩略图生成工具 ===")
 args = setup_arguments()
 generate_all_thumbnails(args)
 
 |