From dac061da6362e55cf6729c53e9aa49ba532a0736 Mon Sep 17 00:00:00 2001 From: wood chen Date: Sat, 16 Nov 2024 09:54:45 +0800 Subject: [PATCH] fix --- .github/workflows/release.yml | 14 ++- gui.py | 230 ++++++++++++++++------------------ icon.ico | Bin 0 -> 16958 bytes requirements.txt | 13 -- video2gif.spec | 2 +- 5 files changed, 124 insertions(+), 135 deletions(-) create mode 100644 icon.ico diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b689a1b..f3436d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,12 +57,22 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install tkinterdnd2>=0.4.2 pip install pyinstaller - name: Build with PyInstaller run: | - pyinstaller --name video2gif --onefile --windowed --add-data "ffmpeg/ffmpeg.exe;ffmpeg" --add-data "ffmpeg/ffprobe.exe;ffmpeg" --add-data "README.md;." gui.py + 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 - name: Upload Release Asset uses: softprops/action-gh-release@v1 diff --git a/gui.py b/gui.py index 7efc89c..a196016 100644 --- a/gui.py +++ b/gui.py @@ -9,7 +9,7 @@ from threading import Thread import ffmpeg import traceback -# 设置 FFmpeg 路径 +# 在类定义前添加 FFmpeg 路径设置 if getattr(sys, "frozen", False): # 运行在 PyInstaller 打包后的环境 ffmpeg_path = os.path.join(sys._MEIPASS, "ffmpeg") @@ -20,29 +20,11 @@ else: if ffmpeg_path not in os.environ["PATH"]: os.environ["PATH"] = ffmpeg_path + os.pathsep + os.environ["PATH"] -# 尝试导入拖放支持 -SUPPORT_DND = False -if platform.system().lower() == "windows": - try: - from tkinterdnd2 import DND_FILES, TkinterDnD - - SUPPORT_DND = True - except ImportError: - pass - class VideoToGifConverter: def __init__(self): # 创建主窗口 - if SUPPORT_DND: - try: - self.root = TkinterDnD.Tk() - except Exception: - self.root = tk.Tk() - SUPPORT_DND = False - else: - self.root = tk.Tk() - + self.root = tk.Tk() self.root.title("视频转GIF工具") # 设置窗口大小和位置 @@ -53,92 +35,31 @@ class VideoToGifConverter: x = (screen_width - window_width) // 2 y = (screen_height - window_height) // 2 self.root.geometry(f"{window_width}x{window_height}+{x}+{y}") - - # 设置拖放支持 - if SUPPORT_DND: - try: - self.root.drop_target_register(DND_FILES) - self.root.dnd_bind("<>", self.handle_drop) - except Exception: - SUPPORT_DND = False + # 设置窗口样式 + self.root.configure(bg="#f0f0f0") + style = ttk.Style() + style.configure("TButton", padding=6) + style.configure("TLabelframe", background="#f0f0f0") # 设置UI self.setup_ui() - def setup_dnd(self): - """设置拖放支持""" - if SUPPORT_DND == "tkdnd": - # Windows with tkinterdnd2 - self.root.drop_target_register(DND_FILES) - self.root.dnd_bind("<>", self.handle_drop) - elif SUPPORT_DND == "macos": - # macOS native drag and drop - self.root.bind("<>", self.handle_macos_drop) - # 启用macOS拖放 - self.root.tk.call("tk_getOpenFile", "-setup") - else: - print("Drag and drop not supported on this platform") - - def handle_drop(self, event): - """处理Windows下的文件拖放""" - files = self.root.tk.splitlist(event.data) - self.process_dropped_files(files) - - def handle_macos_drop(self, event): - """处理macOS下的文件拖放""" - # macOS下获取拖放的文件路径 - files = self.root.tk.splitlist(self.root.tk.call("::tk::mac::GetDroppedFiles")) - self.process_dropped_files(files) - - def process_dropped_files(self, files): - """处理拖放的文件""" - # 过滤出视频文件 - valid_extensions = (".mp4", ".avi", ".mov", ".mkv") - valid_files = [f for f in files if f.lower().endswith(valid_extensions)] - - if valid_files: - self.files_list.delete(0, tk.END) - for file in valid_files: - self.files_list.insert(tk.END, file) - else: - messagebox.showwarning( - "警告", "请拖入视频文件(支持mp4, avi, mov, mkv格式)" - ) - - # 添加拖放处理方法 - def handle_drop(self, event): - """处理文件拖放""" - files = self.root.tk.splitlist(event.data) - # 过滤出视频文件 - valid_extensions = (".mp4", ".avi", ".mov", ".mkv") - valid_files = [f for f in files if f.lower().endswith(valid_extensions)] - - if valid_files: - self.files_list.delete(0, tk.END) - for file in valid_files: - self.files_list.insert(tk.END, file) - else: - messagebox.showwarning( - "警告", "请拖入视频文件(支持mp4, avi, mov, mkv格式)" - ) - def setup_ui(self): # 文件选择框 self.file_frame = ttk.LabelFrame(self.root, text="选择文件") self.file_frame.pack(padx=10, pady=5, fill="x") - # 添加拖放提示 - if SUPPORT_DND: - ttk.Label( - self.file_frame, text="可以直接拖放视频文件到此处", foreground="blue" - ).pack(pady=5) - self.files_list = tk.Listbox(self.file_frame, height=5) self.files_list.pack(padx=5, pady=5, fill="x") - # 如果是macOS,为Listbox添加拖放支持 - if SUPPORT_DND == "macos": - self.files_list.bind("<>", self.handle_macos_drop) + # 为文件列表添加右键菜单 + self.files_list_menu = tk.Menu(self.root, tearoff=0) + self.files_list_menu.add_command(label="删除选中", command=self.delete_selected) + self.files_list_menu.add_command( + label="清空列表", command=lambda: self.files_list.delete(0, tk.END) + ) + + self.files_list.bind("", self.show_context_menu) btn_frame = ttk.Frame(self.file_frame) btn_frame.pack(fill="x", padx=5, pady=5) @@ -272,14 +193,19 @@ class VideoToGifConverter: # 状态标签 self.status_label = ttk.Label(self.root, text="就绪") self.status_label.pack(pady=5) - # 如果不支持拖放,添加提示 - if not SUPPORT_DND: - ttk.Label( - self.file_frame, - text="注意:当前版本不支持拖放功能,请使用'选择视频'按钮", - wraplength=300, - foreground="red", - ).pack(pady=5) + + def show_context_menu(self, event): + """显示右键菜单""" + try: + self.files_list_menu.tk_popup(event.x_root, event.y_root) + finally: + self.files_list_menu.grab_release() + + def delete_selected(self): + """删除选中的文件""" + selection = self.files_list.curselection() + for index in reversed(selection): + self.files_list.delete(index) def browse_output(self): directory = filedialog.askdirectory() @@ -304,9 +230,46 @@ class VideoToGifConverter: else: return ["-quality", "50"] - # 修改 convert_video_to_gif 方法 + def validate_inputs(self): + """验证输入参数""" + try: + # 验证FPS + fps = int(self.fps_var.get()) + if fps <= 0: + raise ValueError("FPS必须大于0") + + # 验证时间设置 + start_time = float(self.start_time_var.get() or 0) + if start_time < 0: + raise ValueError("开始时间不能为负数") + + if self.duration_var.get(): + duration = float(self.duration_var.get()) + if duration <= 0: + raise ValueError("持续时间必须大于0") + + # 验证自定义尺寸 + if self.size_var.get() == "custom": + width = int(self.width_var.get()) + if width <= 0: + raise ValueError("宽度必须大于0") + + height = self.height_var.get() + if height != "auto": + height = int(height) + if height <= 0: + raise ValueError("高度必须大于0") + + return True + except ValueError as e: + messagebox.showerror("输入错误", str(e)) + return False + def convert_video_to_gif(self, video_path): try: + # 验证输入 + if not self.validate_inputs(): + return False # 确定输出路径 if self.output_var.get() == "same": output_dir = os.path.dirname(video_path) @@ -331,6 +294,11 @@ class VideoToGifConverter: cpu_count = os.cpu_count() or 1 threads = max(1, min(cpu_count - 1, 8)) # 留一个核心给系统用 + # 更新状态显示 + self.status_label.config( + text=f"正在生成调色板... {os.path.basename(video_path)}" + ) + self.root.update() # 第一步:生成调色板(添加线程参数) stream = ffmpeg.input(video_path) @@ -355,6 +323,11 @@ class VideoToGifConverter: overwrite_output=True, ) + # 更新状态显示 + 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) @@ -394,25 +367,44 @@ class VideoToGifConverter: def start_conversion(self): def convert(): - files = self.files_list.get(0, tk.END) - if not files: - messagebox.showwarning("警告", "请先选择要转换的视频文件") - return + try: + files = self.files_list.get(0, tk.END) + if not files: + messagebox.showwarning("警告", "请先选择要转换的视频文件") + return - total = len(files) + total = len(files) + success_count = 0 - for i, file in enumerate(files): - current = i + 1 - self.status_label.config( - text=f"正在转换: {os.path.basename(file)} ({current}/{total})" - ) - success = self.convert_video_to_gif(file) - progress = current / total * 100 - self.progress["value"] = progress + for i, file in enumerate(files): + current = i + 1 + self.status_label.config( + text=f"正在转换: {os.path.basename(file)} ({current}/{total})" + ) + if self.convert_video_to_gif(file): + success_count += 1 + progress = current / total * 100 + self.progress["value"] = progress - self.status_label.config(text=f"转换完成 ({total}/{total})") - self.convert_btn["state"] = "normal" - messagebox.showinfo("完成", f"所有文件转换完成!\n成功转换 {total} 个文件") + self.status_label.config(text=f"转换完成 ({success_count}/{total})") + self.convert_btn["state"] = "normal" + + if success_count == total: + messagebox.showinfo( + "完成", f"所有文件转换完成!\n成功转换 {total} 个文件" + ) + else: + messagebox.showwarning( + "完成", + f"转换完成,但有部分失败。\n成功:{success_count}/{total}", + ) + + except Exception as e: + print(f"Conversion error: {str(e)}") + traceback.print_exc() + messagebox.showerror("错误", f"转换过程出错:\n{str(e)}") + finally: + self.convert_btn["state"] = "normal" self.convert_btn["state"] = "disabled" self.progress["value"] = 0 diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..385421c8125e2ca9df44ef40394073fb8ffa30c0 GIT binary patch literal 16958 zcmeI4eN2^A9LFDDW{tT5Q$yswfaJI@pp_$64oK7POr26WcD3BpRW_$~qm~l|!YgSi zDJmeSBVW=i3C~E&eCr9xSG;4*xx1FFJ@b#&u>Wugr_T+Q%R@CU1nE4NUCw>raL)aF zKj)t3{QN^r>%&)Orp9->mONh5hH9FY%#$B!`8-2E|Lc+2)~N3)0hNGCKqa6OPzk66 zR01jim4He>C7=>e38(~A0xAKOfJ#6maQ`N7v2v7lu`>1eh4PfrbEPS-o!R6X|NRE1 z`&4mqQr%j|$ZuCW(@qw-vX1+mxyK3}`@YF{)_=Xi6+E2lHojcyFlyf&X^5Q1o~v1s zIG}oQ;`sv$?Z#&dY{sXr+l{^RZN{FtHe>f3i?Q<+i&5pZ8auKr#VNe+YgFzdW}kMZz!Pm+(vYrM|`~PZl{P{1Sc%zl2{N z&UH!nCHzw4B-I^!J4vfvl;DG3!Y|>M@Jsk5{1Sc%zl2}HFX5N)OZX-H5`I}SDM7+7 z;g|4B_$B-jehI&XU&1fpmwBl}<#Jc7T;`1XeW_!3c2_e9Y#SBZfxjf2KiS0Z4C8mp zL+$+Xpl`&a1B()(;J^Cs{MMWN7Fpf}{=&}u@;%4!mUkTSFLfn@z_!r?;cp4^zZw7Y z@n4GnYW&yWU*KQhU*KQhU*KQhU*KQhU)1KpeD;3`yZ>Kj|1anK zlirp0x_6%K4g5FZzX|_M?Eg*d|ChJV9R8qrJpAwG^WTL3Cj2+C|2MJ!?>6`A>Fd)a zu7U8EaR0lG|MvU;D(ARJ=l*1#HEPdXYXkg2_=E5VcVt_G+h@klG>^6E`Ghg>2RBTx z2jLIGAA~=+_L(7ebKl;$KDFLq{cf$JF6{hwiTnS+-kAGtnFIdmVf>-pvU+Ui^FU@8$gCT{Cu&)!er?uHo-X{ZrTB z{9oWabR^Fe9m)K==2(32o6rA7#SCkweE|Mi_|514{gKS~U-pGR3I5;O>Yv-^|DUM; zCLdnrx}&Lfy&4|{zYqUD_CFu~eHB?2-_{w{MDqafC&TZc;&WyL?U&p_We;xli^^d-4 zOpM#yw>PhkzVGM@|7_|%)_;fkKg2KLmv>(OTW;0=KbKCib=n7t(}x{`-(3Ii>rLK& zn(M3O?*4rCv#EcNu>Sv`{*8dYz5c)b?;lg_o$8(S>5q>68~&D+PyD}Yz?=#EgW=D@ z|7!eK{Rg=J1z7(9>i+=se}MWw!1=#+^W=mMHRrnY zAsYNutp7&VeC7=>e38(~A0xAKOfJ#6mpb}6Cs0363 zDgl*%Nsu}5oOLB6K-ldYO|g8$jzW~<58XzdCX^IC0fK(yCZgkFc*hiOC4 z(4(lWktgL1TR&c3p)I6s&TIR{jf|VEO+Ijfm#w${Fw{Bp$k+N^%cH%b^|_Wo+pP6z P4z*I#nr)%>o_hQVJkNQJ literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 6f27415..8c6245d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,3 @@ # 核心依赖 ffmpeg-python>=0.2.0 Pillow>=9.0.0 - -# Windows平台依赖 -tkinterdnd2>=0.4.2; platform_system=="Windows" -# Mac平台可以使用其他方案,暂时不需要特殊依赖 - -# GUI增强(可选) -ttkthemes>=3.2.0 # 美化GUI主题 -ttkwidgets>=0.12.0 # 额外的tkinter部件 - -# 开发工具(可选) -pytest>=7.0.0 # 测试框架 -black>=22.0.0 # 代码格式化 -flake8>=4.0.0 # 代码检查 diff --git a/video2gif.spec b/video2gif.spec index 691a852..1f61972 100644 --- a/video2gif.spec +++ b/video2gif.spec @@ -45,5 +45,5 @@ exe = EXE( target_arch=None, codesign_identity=None, entitlements_file=None, - icon='icons/favicon.ico' # 如果你有图标的话 + icon='icon.ico' # 如果你有图标的话 )