1 2 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)
|