Created
December 26, 2025 03:32
-
-
Save Zayrick/9a5c3c7cca78561aa800c47dd5ef9f93 to your computer and use it in GitHub Desktop.
批量将 webm 目录下的 VP9 透明通道视频文件并行转换为保留透明度的 GIF 动图。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| 批量将 webm 目录下的所有 .webm 文件转换为带透明通道的 .gif | |
| 使用 libvpx-vp9 解码器正确读取 VP9 alpha 通道 | |
| 使用 palettegen/paletteuse 保留透明度 | |
| 禁用 GIF offsetting 和 transdiff 避免白色活动矩形 | |
| 准备工作: | |
| --------- | |
| 1. Python 依赖: | |
| 本脚本仅使用 Python 标准库,无需安装额外 pip 包 | |
| 2. FFmpeg 安装 (必需): | |
| - Windows: | |
| 方法一: 使用 winget (推荐) | |
| winget install FFmpeg | |
| 方法二: 使用 scoop | |
| scoop install ffmpeg | |
| 方法三: 手动下载 | |
| 从 https://www.gyan.dev/ffmpeg/builds/ 下载 | |
| 解压后将 bin 目录添加到系统 PATH | |
| - macOS: | |
| brew install ffmpeg | |
| - Linux (Ubuntu/Debian): | |
| sudo apt update && sudo apt install ffmpeg | |
| 安装后验证: ffmpeg -version | |
| 3. 目录结构: | |
| 脚本同级目录下需要: | |
| - webm/ 存放待转换的 .webm 文件 | |
| - gif/ 输出目录 (自动创建) | |
| """ | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| def convert_webm_to_gif(webm_path: Path, output_dir: Path) -> tuple[Path, bool, str]: | |
| """ | |
| 将单个 webm 文件转换为透明 gif | |
| 返回: (文件路径, 是否成功, 消息) | |
| """ | |
| gif_name = webm_path.stem + ".gif" | |
| gif_path = output_dir / gif_name | |
| cmd = [ | |
| "ffmpeg", | |
| "-y", # 覆盖输出 | |
| "-c:v", "libvpx-vp9", # 使用 libvpx 解码器以正确读取 alpha | |
| "-i", str(webm_path), | |
| "-an", # 无音频 | |
| "-filter_complex", | |
| "[0:v]format=rgba,split[v][p];" | |
| "[p]palettegen=reserve_transparent=1:stats_mode=diff[pal];" | |
| "[v][pal]paletteuse=alpha_threshold=128:dither=sierra2_4a", | |
| "-gifflags", "-offsetting", # 禁用帧偏移(避免白色矩形) | |
| "-gifflags", "-transdiff", # 禁用帧间透明差异优化 | |
| "-loop", "0", # 无限循环 | |
| str(gif_path), | |
| ] | |
| try: | |
| result = subprocess.run( | |
| cmd, | |
| capture_output=True, | |
| text=True, | |
| timeout=120, | |
| ) | |
| if result.returncode == 0: | |
| return (webm_path, True, f"✓ {webm_path.name} -> {gif_name}") | |
| else: | |
| return (webm_path, False, f"✗ {webm_path.name}: {result.stderr[:200]}") | |
| except subprocess.TimeoutExpired: | |
| return (webm_path, False, f"✗ {webm_path.name}: 超时") | |
| except Exception as e: | |
| return (webm_path, False, f"✗ {webm_path.name}: {e}") | |
| def main(): | |
| script_dir = Path(__file__).parent | |
| webm_dir = script_dir / "webm" | |
| output_dir = script_dir / "gif" | |
| # 确保输出目录存在 | |
| output_dir.mkdir(exist_ok=True) | |
| # 获取所有 webm 文件 | |
| webm_files = sorted(webm_dir.glob("*.webm")) | |
| total = len(webm_files) | |
| if total == 0: | |
| print("没有找到 .webm 文件") | |
| sys.exit(1) | |
| print(f"找到 {total} 个 webm 文件,开始转换...") | |
| print(f"输出目录: {output_dir.absolute()}\n") | |
| success_count = 0 | |
| fail_count = 0 | |
| # 使用线程池并行转换(ffmpeg 主要是 I/O 和外部进程,线程池足够) | |
| max_workers = 4 # 控制并发数量,避免资源耗尽 | |
| with ThreadPoolExecutor(max_workers=max_workers) as executor: | |
| futures = { | |
| executor.submit(convert_webm_to_gif, f, output_dir): f | |
| for f in webm_files | |
| } | |
| for i, future in enumerate(as_completed(futures), 1): | |
| webm_path, success, msg = future.result() | |
| print(f"[{i}/{total}] {msg}") | |
| if success: | |
| success_count += 1 | |
| else: | |
| fail_count += 1 | |
| print(f"\n转换完成: {success_count} 成功, {fail_count} 失败") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment