import json import os import sys import tkinter as tk from tkinter import ttk, messagebox, filedialog import main as main_program from pathlib import Path CONFIG_NAME = Path(sys.path[0])/"config.json" DEFAULT_CONFIG = { "crf": 18, "codec": "h264", # could be h264, h264_qsv, h264_nvenc … etc. "ffmpeg": "ffmpeg", "video_ext": [".mp4", ".mkv"], "extra": [], "manual": None, "train": False, } HW_SUFFIXES = ["amf", "qsv", "nvenc"] CODECS_BASE = ["h264", "hevc"] preset_options = { "不使用":["","ultrafast","superfast","veryfast","faster","fast","medium","slow","slower","veryslow",], "AMD": ["","speed","balanced","quality",], "Intel": ["","veryfast","faster","fast","medium","slow",], "NVIDIA": ["","default","slow","medium","fast","hp","hq",] } def config_path() -> str: """Return path of config file next to the executable / script.""" if getattr(sys, "frozen", False): # PyInstaller executable base = os.path.dirname(sys.executable) else: base = os.path.dirname(os.path.abspath(__file__)) return os.path.join(base, CONFIG_NAME) def load_config() -> dict: try: with open(config_path(), "r", encoding="utf-8") as fp: data = json.load(fp) if not isinstance(data, dict): raise ValueError("config.json root must be an object") return {**DEFAULT_CONFIG, **data} except FileNotFoundError: return DEFAULT_CONFIG.copy() except Exception as exc: messagebox.showwarning("FFmpeg Config", f"Invalid config.json – using defaults.\n{exc}") return DEFAULT_CONFIG.copy() def save_config(cfg: dict): try: with open(config_path(), "w", encoding="utf-8") as fp: json.dump(cfg, fp, ensure_ascii=False, indent=4) except Exception as exc: messagebox.showerror("配置中心", f"保存失败:\n{exc}") else: messagebox.showinfo("配置中心", "保存成功。") class ConfigApp(tk.Tk): def __init__(self): super().__init__() # 设置现代化主题和统一内边距 style = ttk.Style(self) style.theme_use('clam') self.title("配置中心") self.resizable(False, False) self.cfg = load_config() if "-preset" in self.cfg["extra"]: idx = self.cfg["extra"].index("-preset") self.preset = self.cfg["extra"][idx+1] self.cfg["extra"].pop(idx) self.cfg["extra"].pop(idx) else: self.preset = "" self._build_ui() # ── helper -------------------------------------------------------------- def _grid_label(self, row: int, text: str): tk.Label(self, text=text, anchor="w").grid(row=row, column=0, sticky="w", pady=2, padx=4) def _str_var(self, key: str): var = tk.StringVar(value=str(self.cfg.get(key, ""))) var.trace_add("write", lambda *_: self.cfg.__setitem__(key, var.get())) return var def _bool_var(self, key: str): var = tk.BooleanVar(value=bool(self.cfg.get(key))) var.trace_add("write", lambda *_: self.cfg.__setitem__(key, var.get())) return var def _list_var_entry(self, key: str, width: int = 28): """Comma‑separated list entry bound to config[key].""" var = tk.StringVar(value=",".join(self.cfg.get(key, []))) def _update(*_): self.cfg[key] = [s.strip() for s in var.get().split(",") if s.strip()] var.trace_add("write", _update) ent = tk.Entry(self, textvariable=var, width=width) return ent # ── UI ------------------------------------------------------------------ def _build_ui(self): row = 0 padx_val = 6 pady_val = 4 # 编解码器 self._grid_label(row, "编解码器 (h264 / hevc)") codec_base = tk.StringVar() codec_base.set(next((c for c in CODECS_BASE if self.cfg["codec"].startswith(c)), "h264")) codec_menu = ttk.Combobox(self, textvariable=codec_base, values=CODECS_BASE, state="readonly", width=10) codec_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 显卡品牌(硬件加速) self._grid_label(row, "GPU加速") accel = tk.StringVar() brand_vals = ["不使用", "NVIDIA", "AMD", "Intel"] def get_brand(): codec = self.cfg["codec"] if codec.endswith("_nvenc"): return "NVIDIA" elif codec.endswith("_amf"): return "AMD" elif codec.endswith("_qsv"): return "Intel" return "不使用" accel.set(get_brand()) accel_menu = ttk.Combobox(self, textvariable=accel, values=brand_vals, state="readonly", width=10) accel_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # CRF或码率选择 mode = tk.StringVar(value="crf") if "bitrate" in self.cfg: mode.set("bitrate") def _switch_mode(): if mode.get() == "crf": bitrate_ent.configure(state="disabled") crf_ent.configure(state="normal") else: crf_ent.configure(state="disabled") bitrate_ent.configure(state="normal") tk.Radiobutton(self, text="使用 CRF", variable=mode, value="crf", command=_switch_mode).grid(row=row, column=0, sticky="w", padx=padx_val, pady=pady_val) tk.Radiobutton(self, text="使用码率", variable=mode, value="bitrate", command=_switch_mode).grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # CRF输入框,并在标签中解释参数含义 self._grid_label(row, "CRF 值(质量常数,数值越低质量越高)") crf_var = self._str_var("crf") crf_ent = tk.Entry(self, textvariable=crf_var, width=6) crf_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 码率输入框 (kbit/s) self._grid_label(row, "码率 (例如6M, 200k)") bitrate_var = tk.StringVar(value=str(self.cfg.get("bitrate", ""))) def _update_bitrate(*_): if mode.get() == "bitrate": try: self.cfg["bitrate"] = bitrate_var.get().strip() except ValueError: self.cfg.pop("bitrate", None) bitrate_var.trace_add("write", _update_bitrate) bitrate_ent = tk.Entry(self, textvariable=bitrate_var, width=8) bitrate_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 预设选项 self._grid_label(row, "预设 (决定压缩的速度和质量)") preset_var = tk.StringVar(value=self.preset) def _update_preset_option(*_): preset_menu['values'] = preset_options[accel.get()] preset_menu.set("") def _update_preset(*_): print(preset_var.get()) if self.cfg["extra"].count("-preset")>0: idx = self.cfg["extra"].index("-preset") self.cfg["extra"].pop(idx) self.cfg["extra"].pop(idx) if preset_var.get(): self.cfg["extra"].extend(["-preset", preset_var.get()]) preset_var.trace_add("write", _update_preset) accel.trace_add("write",_update_preset_option) preset_menu = ttk.Combobox(self, textvariable=preset_var, values=preset_options[get_brand()], state="readonly", width=10) preset_menu.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # ffmpeg路径 self._grid_label(row, "ffmpeg 可执行文件") ffmpeg_var = self._str_var("ffmpeg") ffmpeg_ent = tk.Entry(self, textvariable=ffmpeg_var, width=28) ffmpeg_ent.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) ttk.Button(self, text="浏览", command=lambda: self._pick_ffmpeg(ffmpeg_var)).grid(row=row, column=2, padx=2, pady=pady_val) row += 1 # 视频扩展名列表 self._grid_label(row, "视频扩展名 (.x,.y)") self._list_var_entry("video_ext").grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 额外参数列表 self._grid_label(row, "额外参数列表") extra_entry = self._list_var_entry("extra") extra_entry.grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 手动参数列表 self._grid_label(row, "手动参数列表") manual_var = tk.StringVar(value="" if self.cfg.get("manual") is None else " ".join(self.cfg["manual"])) def _update_manual(*_): txt = manual_var.get().strip() self.cfg["manual"] = None if not txt else txt.split() manual_var.trace_add("write", _update_manual) tk.Entry(self, textvariable=manual_var, width=28).grid(row=row, column=1, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 训练模式复选框 train_var = self._bool_var("train") tk.Checkbutton(self, text="启用训练(实验性)", variable=train_var).grid(row=row, column=0, columnspan=2, sticky="w", padx=padx_val, pady=pady_val) row += 1 # 按钮 ttk.Button(self, text="保存", command=lambda: self._on_save(codec_base, accel, mode)).grid(row=row, column=0, pady=8, padx=padx_val) ttk.Button(self, text="退出", command=self.destroy).grid(row=row, column=1, pady=8) _switch_mode() # 初始启用/禁用 # ── callbacks ----------------------------------------------------------- def _on_save(self, codec_base_var, accel_var, mode_var): # 重构codec字符串,同时处理显卡品牌映射 base = codec_base_var.get() brand = accel_var.get() brand_map = {"NVIDIA": "nvenc", "AMD": "amf", "Intel": "qsv", "不使用": ""} if brand != "不使用": self.cfg["codec"] = f"{base}_{brand_map[brand]}" else: self.cfg["codec"] = base # 处理码率和crf的配置 if mode_var.get() == "crf": self.cfg.pop("bitrate", None) else: br = self.cfg.get("bitrate", "") if not (br.endswith("M") or br.endswith("k")): messagebox.showwarning("警告", "码率参数可能配置错误。例如:3M, 500k") tmp = self.cfg["extra"] idx = 0 if len(tmp) == 0:idx = -1 else: while True: if tmp[idx] == "-preset":break elif idx+2==len(tmp): idx = -1 break else: idx+=1 if idx!=-1: preset = self.cfg["extra"][idx+1] if preset not in preset_options[brand]: messagebox.showwarning("警告", "预设(preset)参数可能配置错误!") save_config(self.cfg) def _pick_ffmpeg(self, var): path = filedialog.askopenfilename(title="Select ffmpeg executable") if path: var.set(path) def main(): if len(sys.argv) > 1: main_program.main() else: app = ConfigApp() app.mainloop() if __name__ == "__main__": main()