Compare commits

...

12 Commits

Author SHA1 Message Date
5f5e5350a7 fix(gui): 简化GIF生成错误处理逻辑,直接解码错误输出 2025-05-24 20:19:27 +08:00
7808d7a555 fix(gui): 隐藏调色板生成和GIF转换过程中的CMD窗口并改进错误处理 2025-05-24 20:02:52 +08:00
78a6ec111f fix(gui): 移除质量设置选项并简化 FFmpeg 调用参数处理 2025-05-24 18:01:59 +08:00
d407d3f72e fix(gui): 更新 FFmpeg 参数以改进 GIF 生成质量和错误处理 2025-05-24 17:36:04 +08:00
163f71564f Merge branch 'main' of https://github.com/woodchen-ink/video2gif 2025-05-24 17:27:53 +08:00
861b4af2d2 Revert "fix(gui): 更新 FFmpeg 调色板生成参数以改善 GIF 创建质量"
This reverts commit 302dbc805f49d1403923369e7f31283309abd973.
2025-05-24 17:26:00 +08:00
0f8751a2f2 fix(gui): 更新 FFmpeg 调色板生成参数并改进错误处理 2025-05-22 10:51:24 +08:00
302dbc805f fix(gui): 更新 FFmpeg 调色板生成参数以改善 GIF 创建质量 2025-05-22 10:39:30 +08:00
6d8fb24807 fix: update FFmpeg quality settings for palette generation and GIF creation 2025-05-22 10:12:32 +08:00
wood chen
06ba7c7cea
Create dependabot.yml 2024-11-16 12:27:56 +08:00
7647d15c52 fix(gui): hide CMD window on Windows for subprocess calls 2024-11-16 10:47:25 +08:00
f0844c2284 refactor: optimize FFmpeg path settings and update build process
Update FFmpeg path settings and optimize the build process in release.yml and gui.py
2024-11-16 10:39:20 +08:00
5 changed files with 104 additions and 100 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@ -62,7 +62,7 @@ jobs:
- name: Build with PyInstaller - name: Build with PyInstaller
shell: cmd shell: cmd
run: | run: |
pyinstaller --clean video2gif.spec pyinstaller --name video2gif --onefile --windowed --hidden-import ffmpeg --add-data "ffmpeg/ffmpeg.exe;ffmpeg" --add-data "ffmpeg/ffprobe.exe;ffmpeg" --add-data "README.md;." --clean --noconfirm --log-level=INFO gui.py
- name: Upload Release Asset - name: Upload Release Asset

1
.gitignore vendored
View File

@ -1 +1,2 @@
__pycache__/gui.cpython-311.pyc __pycache__/gui.cpython-311.pyc
ffmpeg/ffmpeg.exe

189
gui.py
View File

@ -8,41 +8,17 @@ import webbrowser
from threading import Thread from threading import Thread
import traceback import traceback
# 在类定义前添加 FFmpeg 路径设置 # 设置 FFmpeg 路径
if getattr(sys, "frozen", False): if getattr(sys, "frozen", False):
# 运行在 PyInstaller 打包后的环境 # 运行在 PyInstaller 打包后的环境
ffmpeg_path = os.path.join( ffmpeg_path = os.path.join(sys._MEIPASS, "ffmpeg")
sys._MEIPASS,
"ffmpeg",
"ffmpeg.exe" if platform.system().lower() == "windows" else "ffmpeg",
)
ffprobe_path = os.path.join(
sys._MEIPASS,
"ffmpeg",
"ffprobe.exe" if platform.system().lower() == "windows" else "ffprobe",
)
else: else:
# 运行在开发环境 # 运行在开发环境
ffmpeg_path = os.path.join( ffmpeg_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ffmpeg")
os.path.dirname(os.path.abspath(__file__)),
"ffmpeg",
"ffmpeg.exe" if platform.system().lower() == "windows" else "ffmpeg",
)
ffprobe_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"ffmpeg",
"ffprobe.exe" if platform.system().lower() == "windows" else "ffprobe",
)
# 设置 ffmpeg-python 包使用的 FFmpeg 路径
import ffmpeg
ffmpeg._run.DEFAULT_FFMPEG_PATH = ffmpeg_path
ffmpeg._run.DEFAULT_FFPROBE_PATH = ffprobe_path
# 添加到系统 PATH # 添加到系统 PATH
if os.path.dirname(ffmpeg_path) not in os.environ["PATH"]: if ffmpeg_path not in os.environ["PATH"]:
os.environ["PATH"] = os.path.dirname(ffmpeg_path) + os.pathsep + os.environ["PATH"] os.environ["PATH"] = ffmpeg_path + os.pathsep + os.environ["PATH"]
class VideoToGifConverter: class VideoToGifConverter:
@ -164,22 +140,6 @@ class VideoToGifConverter:
self.duration_entry.pack(side="left", padx=5) self.duration_entry.pack(side="left", padx=5)
ttk.Label(duration_frame, text="(留空表示全部)").pack(side="left", padx=5) ttk.Label(duration_frame, text="(留空表示全部)").pack(side="left", padx=5)
# 质量设置
quality_frame = ttk.Frame(self.settings_frame)
quality_frame.pack(fill="x", padx=5, pady=5)
ttk.Label(quality_frame, text="质量设置:").pack(side="left", padx=5)
self.quality_var = tk.StringVar(value="medium")
ttk.Radiobutton(
quality_frame, text="高质量", variable=self.quality_var, value="high"
).pack(side="left", padx=5)
ttk.Radiobutton(
quality_frame, text="中等", variable=self.quality_var, value="medium"
).pack(side="left", padx=5)
ttk.Radiobutton(
quality_frame, text="低质量", variable=self.quality_var, value="low"
).pack(side="left", padx=5)
# 输出设置 # 输出设置
output_frame = ttk.Frame(self.settings_frame) output_frame = ttk.Frame(self.settings_frame)
output_frame.pack(fill="x", padx=5, pady=5) output_frame.pack(fill="x", padx=5, pady=5)
@ -244,16 +204,6 @@ class VideoToGifConverter:
for file in files: for file in files:
self.files_list.insert(tk.END, file) self.files_list.insert(tk.END, file)
def get_quality_settings(self):
"""根据质量设置返回 FFmpeg 参数"""
quality = self.quality_var.get()
if quality == "high":
return ["-quality", "100"]
elif quality == "medium":
return ["-quality", "75"]
else:
return ["-quality", "50"]
def validate_inputs(self): def validate_inputs(self):
"""验证输入参数""" """验证输入参数"""
try: try:
@ -291,19 +241,10 @@ class VideoToGifConverter:
def convert_video_to_gif(self, video_path): def convert_video_to_gif(self, video_path):
try: try:
# 打印环境信息
print(f"FFmpeg path: {ffmpeg_path}")
print(f"FFprobe path: {ffprobe_path}")
print(f"System PATH: {os.environ['PATH']}")
# 验证 FFmpeg 是否可用
try:
version_info = ffmpeg.probe(ffmpeg._run.DEFAULT_FFMPEG_PATH)
print(f"FFmpeg version info: {version_info}")
except Exception as e:
print(f"FFmpeg probe error: {e}")
# 验证输入 # 验证输入
if not self.validate_inputs(): if not self.validate_inputs():
return False return False
# 确定输出路径 # 确定输出路径
if self.output_var.get() == "same": if self.output_var.get() == "same":
output_dir = os.path.dirname(video_path) output_dir = os.path.dirname(video_path)
@ -328,65 +269,117 @@ class VideoToGifConverter:
cpu_count = os.cpu_count() or 1 cpu_count = os.cpu_count() or 1
threads = max(1, min(cpu_count - 1, 8)) # 留一个核心给系统用 threads = max(1, min(cpu_count - 1, 8)) # 留一个核心给系统用
# 获取ffmpeg路径
ffmpeg_exe = os.path.join(
ffmpeg_path,
"ffmpeg.exe" if platform.system().lower() == "windows" else "ffmpeg",
)
# 构建基本命令
filter_complex = f"fps={fps}" # 开始构建滤镜链
# 添加尺寸控制到滤镜链
if self.size_var.get() == "custom":
width = int(self.width_var.get())
height = self.height_var.get()
height = -1 if height == "auto" else int(height)
filter_complex += f",scale={width}:{height}"
# 更新状态显示 # 更新状态显示
self.status_label.config( self.status_label.config(
text=f"正在生成调色板... {os.path.basename(video_path)}" text=f"正在生成调色板... {os.path.basename(video_path)}"
) )
self.root.update() self.root.update()
# 第一步:生成调色板(添加线程参数)
stream = ffmpeg.input(video_path)
# 构建调色板生成命令
palette_cmd = [ffmpeg_exe, "-y", "-threads", str(threads)] # 覆盖输出文件
# 添加时间控制
if start_time > 0: if start_time > 0:
stream = ffmpeg.filter(stream, "setpts", f"PTS-{start_time}/TB") palette_cmd.extend(["-ss", str(start_time)])
palette_cmd.extend(["-i", video_path])
if duration: if duration:
stream = ffmpeg.filter(stream, "t", duration=float(duration)) palette_cmd.extend(["-t", str(float(duration))])
stream = ffmpeg.filter(stream, "fps", fps=fps) # 添加滤镜和输出
palette_cmd.extend(["-vf", f"{filter_complex},palettegen", palette_path])
if self.size_var.get() == "custom": # 打印命令用于调试
width = int(self.width_var.get()) print("调色板生成命令:", " ".join(palette_cmd))
height = self.height_var.get()
height = -1 if height == "auto" else int(height)
stream = ffmpeg.filter(stream, "scale", width=width, height=height)
stream = ffmpeg.filter(stream, "palettegen") # 创建 startupinfo 对象(用于隐藏 CMD 窗口)
# 添加线程参数 startupinfo = None
ffmpeg.run( if platform.system().lower() == "windows":
ffmpeg.output(stream, palette_path, threads=threads), startupinfo = subprocess.STARTUPINFO()
overwrite_output=True, startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 运行调色板生成命令
process = subprocess.Popen(
palette_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=startupinfo,
creationflags=(
subprocess.CREATE_NO_WINDOW
if platform.system().lower() == "windows"
else 0
),
) )
_, stderr = process.communicate()
if process.returncode != 0:
raise RuntimeError(f"调色板生成失败: {stderr.decode()}")
# 更新状态显示 # 更新状态显示
self.status_label.config( self.status_label.config(
text=f"正在生成GIF... {os.path.basename(video_path)}" text=f"正在生成GIF... {os.path.basename(video_path)}"
) )
self.root.update() self.root.update()
# 第二步使用调色板生成GIF添加线程参数
stream = ffmpeg.input(video_path)
palette = ffmpeg.input(palette_path)
# 构建GIF生成命令
gif_cmd = [ffmpeg_exe, "-y", "-threads", str(threads)]
# 添加时间控制
if start_time > 0: if start_time > 0:
stream = ffmpeg.filter(stream, "setpts", f"PTS-{start_time}/TB") gif_cmd.extend(["-ss", str(start_time)])
gif_cmd.extend(["-i", video_path])
if duration: if duration:
stream = ffmpeg.filter(stream, "t", duration=float(duration)) gif_cmd.extend(["-t", str(float(duration))])
stream = ffmpeg.filter(stream, "fps", fps=fps) gif_cmd.extend(
[
if self.size_var.get() == "custom": "-i",
width = int(self.width_var.get()) palette_path,
height = self.height_var.get() "-lavfi",
height = -1 if height == "auto" else int(height) f"{filter_complex} [x]; [x][1:v] paletteuse",
stream = ffmpeg.filter(stream, "scale", width=width, height=height) output_path,
]
stream = ffmpeg.filter([stream, palette], "paletteuse")
# 添加线程参数
ffmpeg.run(
ffmpeg.output(stream, output_path, threads=threads),
overwrite_output=True,
) )
# 打印命令用于调试
print("GIF生成命令:", " ".join(gif_cmd))
# 运行GIF生成命令
process = subprocess.Popen(
gif_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
startupinfo=startupinfo,
creationflags=(
subprocess.CREATE_NO_WINDOW
if platform.system().lower() == "windows"
else 0
),
)
_, stderr = process.communicate()
if process.returncode != 0:
raise RuntimeError(f"GIF生成失败: {stderr.decode()}")
# 删除临时调色板文件 # 删除临时调色板文件
if os.path.exists(palette_path): if os.path.exists(palette_path):
os.remove(palette_path) os.remove(palette_path)

View File

@ -1,3 +1,2 @@
# 核心依赖 # 核心依赖
ffmpeg-python>=0.2.0
Pillow>=9.0.0 Pillow>=9.0.0