Compare commits

...

16 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
91fe9bc3da chore(workflow): remove paths-ignore for markdown files in release workflow 2024-11-16 10:18:45 +08:00
0decff023e chore(workflow): update build process with PyInstaller spec file
Update the GitHub Actions workflow to use the PyInstaller spec file for building the application, ensuring proper inclusion of FFmpeg executables and improving the build process.
2024-11-16 10:17:36 +08:00
93a01d21cf chore(workflow): ignore md files in release workflow
update README for FFmpeg built-in and Python version requirement
2024-11-16 10:04:43 +08:00
e809a2e6f7 chore(workflow): simplify PyInstaller build command in release workflow 2024-11-16 09:57:32 +08:00
7 changed files with 109 additions and 82 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

@ -60,19 +60,10 @@ jobs:
pip install pyinstaller
- name: Build with PyInstaller
shell: cmd
run: |
pyinstaller --name video2gif ^
--onefile ^
--windowed ^
--icon=icon.ico ^
--add-data "ffmpeg/ffmpeg.exe;ffmpeg" ^
--add-data "ffmpeg/ffprobe.exe;ffmpeg" ^
--add-data "README.md;." ^
--clean ^
--noconfirm ^
--noupx ^
--log-level=INFO ^
gui.py
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
uses: softprops/action-gh-release@v1

1
.gitignore vendored
View File

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

View File

@ -5,8 +5,8 @@
## 功能
- 支持拖放视频文件到应用程序窗口进行转换。
- 自动检测FFmpeg是否安装如未安装则提供安装向导
- 提供多种系统平台的安装和使用说明
- 内置FFmpeg工具进行视频转码, 不需要在系统安装FFmpeg
- 多平台, 多线程
## 安装
@ -32,7 +32,7 @@
### 环境搭建
1. 安装Python 3.7及以上版本。
1. 安装Python 3.10及以上版本。
2. 安装依赖库:
```bash
pip install -r requirements.txt

151
gui.py
View File

@ -6,10 +6,9 @@ import platform
import os
import webbrowser
from threading import Thread
import ffmpeg
import traceback
# 在类定义前添加 FFmpeg 路径设置
# 设置 FFmpeg 路径
if getattr(sys, "frozen", False):
# 运行在 PyInstaller 打包后的环境
ffmpeg_path = os.path.join(sys._MEIPASS, "ffmpeg")
@ -17,6 +16,7 @@ else:
# 运行在开发环境
ffmpeg_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ffmpeg")
# 添加到系统 PATH
if ffmpeg_path not in os.environ["PATH"]:
os.environ["PATH"] = ffmpeg_path + os.pathsep + os.environ["PATH"]
@ -140,22 +140,6 @@ class VideoToGifConverter:
self.duration_entry.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.pack(fill="x", padx=5, pady=5)
@ -220,16 +204,6 @@ class VideoToGifConverter:
for file in files:
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):
"""验证输入参数"""
try:
@ -270,6 +244,7 @@ class VideoToGifConverter:
# 验证输入
if not self.validate_inputs():
return False
# 确定输出路径
if self.output_var.get() == "same":
output_dir = os.path.dirname(video_path)
@ -294,65 +269,117 @@ class VideoToGifConverter:
cpu_count = os.cpu_count() or 1
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(
text=f"正在生成调色板... {os.path.basename(video_path)}"
)
self.root.update()
# 第一步:生成调色板(添加线程参数)
stream = ffmpeg.input(video_path)
# 构建调色板生成命令
palette_cmd = [ffmpeg_exe, "-y", "-threads", str(threads)] # 覆盖输出文件
# 添加时间控制
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:
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())
height = self.height_var.get()
height = -1 if height == "auto" else int(height)
stream = ffmpeg.filter(stream, "scale", width=width, height=height)
# 打印命令用于调试
print("调色板生成命令:", " ".join(palette_cmd))
stream = ffmpeg.filter(stream, "palettegen")
# 添加线程参数
ffmpeg.run(
ffmpeg.output(stream, palette_path, threads=threads),
overwrite_output=True,
# 创建 startupinfo 对象(用于隐藏 CMD 窗口)
startupinfo = None
if platform.system().lower() == "windows":
startupinfo = subprocess.STARTUPINFO()
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(
text=f"正在生成GIF... {os.path.basename(video_path)}"
)
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:
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:
stream = ffmpeg.filter(stream, "t", duration=float(duration))
gif_cmd.extend(["-t", str(float(duration))])
stream = ffmpeg.filter(stream, "fps", 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)
stream = ffmpeg.filter(stream, "scale", width=width, height=height)
stream = ffmpeg.filter([stream, palette], "paletteuse")
# 添加线程参数
ffmpeg.run(
ffmpeg.output(stream, output_path, threads=threads),
overwrite_output=True,
gif_cmd.extend(
[
"-i",
palette_path,
"-lavfi",
f"{filter_complex} [x]; [x][1:v] paletteuse",
output_path,
]
)
# 打印命令用于调试
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):
os.remove(palette_path)
@ -362,6 +389,8 @@ class VideoToGifConverter:
except Exception as e:
error_msg = str(e)
print(f"Conversion error: {error_msg}")
print(f"Error type: {type(e)}")
traceback.print_exc()
messagebox.showerror("错误", f"转换失败:\n{error_msg}")
return False

View File

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

View File

@ -1,7 +1,4 @@
# video2gif.spec
import sys
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
a = Analysis(
@ -13,7 +10,7 @@ a = Analysis(
('ffmpeg/ffprobe.exe', 'ffmpeg'),
('README.md', '.')
],
hiddenimports=['tkinter', 'tkinter.ttk', 'tkinterdnd2'],
hiddenimports=['ffmpeg', 'ffmpeg-python'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
@ -45,5 +42,4 @@ exe = EXE(
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='icon.ico' # 如果你有图标的话
)