当前位置:首页>python>图片格式转换(python开发)

图片格式转换(python开发)

  • 2026-08-21 19:28:12
图片格式转换(python开发)

此工具是女朋友做图片设计用到图片转换而用ai开发出来的工具,仅供参考。软件我会放到下载里面。 方便的可以自己用。 效果图:

产物位于 `dist\图片格式转换工具.exe`,约 18 MB。---## ❓ 常见问题**Q:双击 exe 没反应?**A:首次启动单文件 exe 需要解压内置资源到临时目录,可能需要数秒。请稍等;如仍未启动,检查是否被杀毒软件拦截。**Q:AVIF 转换失败?**A:源码运行需要 Pillow 11+(内置 libavif)。运行 `python -c "from PIL import features; print(features.version('avif'))"` 确认;exe 版本已内嵌,无需关心。**Q:转换后图片颜色变了 / 出现白底?**A:JPEG / BMP / GIF 等格式不支持透明通道(alpha),程序会自动把透明区域合成到白底。如需保留透明,请选择 PNG / WebP / AVIF / TIFF 等支持透明的格式。**Q:为什么有些格式不在下拉列表里?**A:下拉列表只显示当前 Pillow **可写入**的格式。某些格式(如 EPS、PSD、HEIC 等)只能读取不能写入,因此不会出现在输出选项中。**Q:能不能保留原图的 EXIF 信息?**A:当前版本未保留 EXIF(聚焦格式转换本身)。如需保留,可自行在 `convert_one` 中用 `pillow-heif` 或 `piexif` 补充。---## 📄 依赖| 包         | 用途                  | 说明                    || ---------- | --------------------- | ----------------------- || Pillow     | 图片编解码            | 11+ 推荐(含 AVIF 支持)|| tkinter   | GUI 界面              | Python 标准库自带       || PyInstaller| 打包为 exe(可选)    | 仅打包时需要            |---## ⚠️ 免责声明本工具仅供个人/学习使用。批量处理前请先备份原文件,作者不对因使用本工具导致的任何数据损失负责。
# -*- coding: utf-8 -*-"""图片格式转换工具功能:1. AVIF -> JPG/JPEG 专用快速转换(支持批量、文件夹递归、质量调整)2. 通用图片格式互转(所有 Pillow 支持的读取/写入格式之间互转)依赖:Pillow >= 9.1(需带 AVIF 支持,Pillow 11+ 默认内置 libavif)"""from __future__ import annotationsimport osimport sysimport threadingimport tracebackfrom pathlib import Pathfrom typing import Iterablefrom PIL import Image, ImageFile# 允许 Pillow 处理截断的图片ImageFile.LOAD_TRUNCATED_IMAGES = Truetry:    import tkinter as tk    from tkinter import filedialog, messagebox, ttkexcept ImportError as e:  # pragma: no cover    raise SystemExit("未找到 tkinter,请安装 Python 的 Tk 包。"from e# ---------------------------------------------------------------------------# 格式探测# ---------------------------------------------------------------------------def _norm_ext(ext: str) -> str:    """统一扩展名表示,去掉前导点并小写。"""    return ext.lstrip(".").lower()def discover_formats() -> dict:    """    探测当前 Pillow 支持的扩展名。    返回 dict:        {            "read":   {ext_lower: format_name, ...},   # 可读取的扩展名 -> 格式名            "write":  {ext_lower: format_name, ...},  # 可写入的扩展名 -> 格式名        }    说明:        Pillow 内部用格式名(如 "JPEG")注册 OPEN/SAVE 函数,        而 registered_extensions() 返回 {ext: ImageFormat},        ImageFormat 的 .name 就是格式名。    """    read_map: dict[strstr] = {}    write_map: dict[strstr] = {}    for ext, fmt in Image.registered_extensions().items():        key = _norm_ext(ext)        # registered_extensions 的 fmt 可能是 ImageFormat 对象或字符串        if isinstance(fmt, str):            fmt_name = fmt        else:            fmt_name = getattr(fmt, "name"str(fmt))        # 用格式名查 OPEN/SAVE(Pillow 内部字典以格式名为键)        if fmt_name in Image.OPEN:            read_map[key] = fmt_name        if fmt_name in Image.SAVE:            write_map[key] = fmt_name    # 同义扩展名补全(registered_extensions 通常已包含,这里兜底)    synonyms = {        "jpg""jpeg",        "avifs""avif",        "tif""tiff",        "tff""tiff",        "jfif""jpeg",    }    for short, long_ in synonyms.items():        if long_ in read_map and short not in read_map:            read_map[short] = read_map[long_]        if long_ in write_map and short not in write_map:            write_map[short] = write_map[long_]    return {"read": read_map, "write": write_map}# 一些格式在保存时需要特殊处理(模式转换、扩展名匹配等)_FORMAT_SAVE_HINTS: dict[strdict] = {    # JPEG/JPG 不支持 alpha 通道    "jpeg": {"modes": ("RGB",)},    "jpg": {"modes": ("RGB",)},    # BMP 仅支持有限模式    "bmp": {"modes": ("RGB""RGBA""P""L")},    # GIF 限制调色板    "gif": {"modes": ("P""L")},    # EPS / PDF 等向量/复合格式,转过去需要 RGBA->RGB 等处理    "pdf": {"modes": ("RGB",)},    "eps": {"save_kwargs": {"scale"1.0}},    # ICO / ICNS 尺寸集合有限制    "ico": {"sizes": ((1616), (3232), (4848), (6464), (128128), (256256))},    "icns": {"sizes": ((1616), (3232), (6464), (128128), (256256), (512512))},    # WebP 保留透明    "webp": {},    "avif": {},    "png": {},    "tiff": {},}def _save_image(img: Image.Image, target: Path, fmt_ext: str, quality: int | None = None) -> None:    """根据目标扩展名调用 Image.save,处理模式转换与默认参数。"""    fmt_ext = _norm_ext(fmt_ext)    target.parent.mkdir(parents=True, exist_ok=True)    hint = _FORMAT_SAVE_HINTS.get(fmt_ext, {})    save_kwargs: dict = {}    # 目标格式名(PIL 接受 jpeg/png/webp 等格式名)    fmt_name_map = discover_formats()["write"]    fmt_id = fmt_name_map.get(fmt_ext) or fmt_ext.upper()    # 模式转换    allowed_modes = hint.get("modes")    if allowed_modes:        if img.mode not in allowed_modes:            if "RGB" in allowed_modes and img.mode in ("RGBA""LA""P"):                bg = Image.new("RGB", img.size, (255255255))                if img.mode == "P":                    img = img.convert("RGBA")                if img.mode in ("RGBA""LA"):                    bg.paste(img, mask=img.split()[-1])                else:                    bg.paste(img)                img = bg            else:                img = img.convert(allowed_modes[0])    # JPEG 系列质量    if fmt_ext in ("jpeg""jpg"):        save_kwargs.update({"quality": quality if quality is not None else 95,                            "subsampling"0"optimize"True})        if img.mode in ("RGBA""LA""P"):            bg = Image.new("RGB", img.size, (255255255))            if img.mode == "P":                img = img.convert("RGBA")            if img.mode == "RGBA":                bg.paste(img, mask=img.split()[-1])            else:                bg.paste(img)            img = bg    elif fmt_ext == "webp":        save_kwargs.update({"quality": quality if quality is not None else 90"method"6})    elif fmt_ext == "avif":        # quality 0-100,Pillow 把它转给 libavif        save_kwargs.update({"quality": quality if quality is not None else 80})    elif fmt_ext == "png":        save_kwargs.update({"optimize"True})    elif fmt_ext == "gif":        save_kwargs.update({"optimize"True"disposal"2})    # ICO/ICNS 尺寸约束    sizes = hint.get("sizes")    if sizes:        save_kwargs["sizes"] = sizes    save_kwargs.update(hint.get("save_kwargs", {}))    # 用 format=fmt_id 显式指定,避免仅靠扩展名歧义    img.save(target, format=fmt_id, **save_kwargs)# ---------------------------------------------------------------------------# 转换核心# ---------------------------------------------------------------------------def _iter_files(paths: Iterable[str], recursive: bool, exts: set[str]) -> list[Path]:    """从路径列表展开为文件列表,按扩展名过滤。"""    out: list[Path] = []    for p in paths:        path = Path(p)        if path.is_file():            if _norm_ext(path.suffix) in exts:                out.append(path)        elif path.is_dir():            glob = "**/*" if recursive else "*"            for f in path.glob(glob):                if f.is_file() and _norm_ext(f.suffix) in exts:                    out.append(f)    # 去重保序    seen = set()    deduped = []    for f in out:        rp = f.resolve()        if rp not in seen:            seen.add(rp)            deduped.append(f)    return dedupeddef convert_one(src: Path, dst_ext: str, out_dir: Path | None,               quality: int | None, keep_dir: bool, src_root: Path | None,               overwrite: bool) -> tuple[boolstr]:    """转换单个文件。返回 (success, message)。"""    try:        with Image.open(src) as img:            # 处理多帧(GIF/APNG/动画 WebP):取首帧            try:                img.seek(0)            except (EOFError, AttributeError):                pass            # 计算输出路径            if out_dir is None:                target_dir = src.parent            else:                if keep_dir and src_root is not None:                    rel = src.relative_to(src_root).parent                    target_dir = out_dir / rel                else:                    target_dir = out_dir            target = target_dir / f"{src.stem}.{_norm_ext(dst_ext)}"            if target.exists() and not overwrite:                return Falsef"已存在(跳过): {target}"            _save_image(img, target, dst_ext, quality)        return Truestr(target)    except Exception as e:        return Falsef"失败 {src.name}{e}\n{traceback.format_exc()}"# ---------------------------------------------------------------------------# GUI# ---------------------------------------------------------------------------class ConverterApp(tk.Tk):    def __init__(self):        super().__init__()        self.title("图片格式转换工具  (AVIF→JPG/JPEG · 通用互转)")        self.geometry("900x680")        self.minsize(780600)        self.fmts = discover_formats()        self._build_ui()    # ------------------------------------------------------------------ UI    def _build_ui(self):        nb = ttk.Notebook(self)        nb.pack(fill="both", expand=True, padx=8, pady=8)        self.avif_tab = ttk.Frame(nb)        self.generic_tab = ttk.Frame(nb)        nb.add(self.avif_tab, text="AVIF → JPG/JPEG")        nb.add(self.generic_tab, text="通用格式互转")        self._build_avif_tab()        self._build_generic_tab()        self.status_var = tk.StringVar(value="就绪。")        bar = ttk.Frame(self)        bar.pack(fill="x", side="bottom")        ttk.Label(bar, textvariable=self.status_var, anchor="w").pack(            fill="x", padx=8, pady=4)    # -------- AVIF 专用 --------    def _build_avif_tab(self):        t = self.avif_tab        t.columnconfigure(1, weight=1)        # 输入        ttk.Label(t, text="输入 AVIF 文件/文件夹:").grid(            row=0, column=0, sticky="w", padx=8, pady=8)        self.avif_input_var = tk.StringVar()        ttk.Entry(t, textvariable=self.avif_input_var).grid(            row=0, column=1, sticky="we", padx=8, pady=8)        ttk.Button(t, text="添加文件…",                   command=lambdaself._pick_files(self.avif_input_var)).grid(            row=0, column=2, padx=4, pady=8)        ttk.Button(t, text="选文件夹…",                   command=lambdaself._pick_dir(self.avif_input_var)).grid(            row=0, column=3, padx=4, pady=8)        # 输出目录        ttk.Label(t, text="输出目录(留空=源目录):").grid(            row=1, column=0, sticky="w", padx=8, pady=8)        self.avif_out_var = tk.StringVar()        ttk.Entry(t, textvariable=self.avif_out_var).grid(            row=1, column=1, sticky="we", padx=8, pady=8)        ttk.Button(t, text="选目录…",                   command=lambdaself._pick_dir(self.avif_out_var, is_dir=True)).grid(            row=1, column=2, columnspan=2, sticky="we", padx=4, pady=8)        # 目标格式        ttk.Label(t, text="目标格式:").grid(            row=2, column=0, sticky="w", padx=8, pady=8)        self.avif_tgt_var = tk.StringVar(value="jpg")        ft = ttk.Frame(t)        ft.grid(row=2, column=1, sticky="w", padx=8, pady=8)        ttk.Radiobutton(ft, text="JPG (.jpg)", value="jpg",                        variable=self.avif_tgt_var).pack(side="left")        ttk.Radiobutton(ft, text="JPEG (.jpeg)", value="jpeg",                        variable=self.avif_tgt_var).pack(side="left", padx=12)        # 选项        opts = ttk.Frame(t)        opts.grid(row=3, column=0, columnspan=4, sticky="w", padx=8, pady=4)        self.avif_recursive = tk.BooleanVar(value=True)        self.avif_keepdir = tk.BooleanVar(value=True)        self.avif_overwrite = tk.BooleanVar(value=False)        ttk.Checkbutton(opts, text="文件夹递归子目录",                        variable=self.avif_recursive).pack(side="left")        ttk.Checkbutton(opts, text="保留相对目录结构",                        variable=self.avif_keepdir).pack(side="left", padx=12)        ttk.Checkbutton(opts, text="覆盖已存在文件",                        variable=self.avif_overwrite).pack(side="left")        # 质量        qf = ttk.Frame(t)        qf.grid(row=4, column=0, columnspan=4, sticky="we", padx=8, pady=4)        ttk.Label(qf, text="JPEG 质量:").pack(side="left")        self.avif_quality = tk.IntVar(value=95)        self.avif_qscale = ttk.Scale(qf, from_=10, to=100,                                     variable=self.avif_quality,                                     orient="horizontal",                                     command=lambda v: self.avif_quality.set(int(float(v))))        self.avif_qscale.pack(side="left", fill="x", expand=True, padx=8)        self.avif_qlbl = ttk.Label(qf, textvariable=self.avif_quality, width=4)        self.avif_qlbl.pack(side="left")        # 按钮        bf = ttk.Frame(t)        bf.grid(row=5, column=0, columnspan=4, sticky="we", padx=8, pady=8)        bf.columnconfigure(0, weight=1)        self.avif_btn = ttk.Button(bf, text="开始转换 AVIF → JPG/JPEG",                                  command=self._run_avif)        self.avif_btn.pack(fill="x")        # 日志        ttk.Label(t, text="日志:").grid(row=6, column=0, sticky="nw", padx=8, pady=4)        log_frame = ttk.Frame(t)        log_frame.grid(row=6, column=1, columnspan=3, sticky="nsew",                       padx=8, pady=4)        t.rowconfigure(6, weight=1)        log_frame.columnconfigure(0, weight=1)        log_frame.rowconfigure(0, weight=1)        self.avif_log = tk.Text(log_frame, height=12, wrap="none",                                font=("Consolas"9))        self.avif_log.grid(row=0, column=0, sticky="nsew")        sb = ttk.Scrollbar(log_frame, command=self.avif_log.yview)        sb.grid(row=0, column=1, sticky="ns")        self.avif_log.configure(yscrollcommand=sb.set)    # -------- 通用互转 --------    def _build_generic_tab(self):        t = self.generic_tab        t.columnconfigure(1, weight=1)        # 输入        ttk.Label(t, text="输入文件/文件夹:").grid(            row=0, column=0, sticky="w", padx=8, pady=8)        self.gen_input_var = tk.StringVar()        ttk.Entry(t, textvariable=self.gen_input_var).grid(            row=0, column=1, sticky="we", padx=8, pady=8)        ttk.Button(t, text="添加文件…",                   command=lambdaself._pick_files(self.gen_input_var)).grid(            row=0, column=2, padx=4, pady=8)        ttk.Button(t, text="选文件夹…",                   command=lambdaself._pick_dir(self.gen_input_var)).grid(            row=0, column=3, padx=4, pady=8)        # 输出目录        ttk.Label(t, text="输出目录(留空=源目录):").grid(            row=1, column=0, sticky="w", padx=8, pady=8)        self.gen_out_var = tk.StringVar()        ttk.Entry(t, textvariable=self.gen_out_var).grid(            row=1, column=1, sticky="we", padx=8, pady=8)        ttk.Button(t, text="选目录…",                   command=lambdaself._pick_dir(self.gen_out_var, is_dir=True)).grid(            row=1, column=2, columnspan=2, sticky="we", padx=4, pady=8)        # 目标格式下拉(仅显示可写入的扩展名)        ttk.Label(t, text="目标格式:").grid(            row=2, column=0, sticky="w", padx=8, pady=8)        write_exts = sorted(self.fmts["write"].keys())        self.gen_tgt_var = tk.StringVar(value="png" if "png" in write_exts else (write_exts[0if write_exts else "png"))        self.gen_tgt_combo = ttk.Combobox(            t, textvariable=self.gen_tgt_var, values=write_exts,            state="readonly", width=12)        self.gen_tgt_combo.grid(row=2, column=1, sticky="w", padx=8, pady=8)        ttk.Label(t, text=f"(共 {len(write_exts)} 种可写格式)").grid(            row=2, column=2, columnspan=2, sticky="w", padx=8)        # 源格式说明        ttk.Label(            t,            text=f"支持的读取格式: {', '.join(sorted(self.fmts['read'].keys()))}",            wraplength=760, justify="left", foreground="#666").grid(            row=3, column=0, columnspan=4, sticky="we", padx=8, pady=4)        # 选项        opts = ttk.Frame(t)        opts.grid(row=4, column=0, columnspan=4, sticky="w", padx=8, pady=4)        self.gen_recursive = tk.BooleanVar(value=True)        self.gen_keepdir = tk.BooleanVar(value=True)        self.gen_overwrite = tk.BooleanVar(value=False)        self.gen_skip_same = tk.BooleanVar(value=True)        ttk.Checkbutton(opts, text="文件夹递归子目录",                        variable=self.gen_recursive).pack(side="left")        ttk.Checkbutton(opts, text="保留相对目录结构",                        variable=self.gen_keepdir).pack(side="left", padx=12)        ttk.Checkbutton(opts, text="覆盖已存在文件",                        variable=self.gen_overwrite).pack(side="left")        ttk.Checkbutton(opts, text="源=目标扩展名则跳过",                        variable=self.gen_skip_same).pack(side="left", padx=12)        # 质量        qf = ttk.Frame(t)        qf.grid(row=5, column=0, columnspan=4, sticky="we", padx=8, pady=4)        ttk.Label(qf, text="质量(对 JPG/WEBP/AVIF 有效):").pack(side="left")        self.gen_quality = tk.IntVar(value=95)        self.gen_qscale = ttk.Scale(qf, from_=10, to=100,                                    variable=self.gen_quality,                                    orient="horizontal",                                    command=lambda v: self.gen_quality.set(int(float(v))))        self.gen_qscale.pack(side="left", fill="x", expand=True, padx=8)        self.gen_qlbl = ttk.Label(qf, textvariable=self.gen_quality, width=4)        self.gen_qlbl.pack(side="left")        # 按钮        bf = ttk.Frame(t)        bf.grid(row=6, column=0, columnspan=4, sticky="we", padx=8, pady=8)        bf.columnconfigure(0, weight=1)        self.gen_btn = ttk.Button(bf, text="开始通用转换",                                  command=self._run_generic)        self.gen_btn.pack(fill="x")        # 日志        ttk.Label(t, text="日志:").grid(row=7, column=0, sticky="nw", padx=8, pady=4)        log_frame = ttk.Frame(t)        log_frame.grid(row=7, column=1, columnspan=3, sticky="nsew",                       padx=8, pady=4)        t.rowconfigure(7, weight=1)        log_frame.columnconfigure(0, weight=1)        log_frame.rowconfigure(0, weight=1)        self.gen_log = tk.Text(log_frame, height=12, wrap="none",                               font=("Consolas"9))        self.gen_log.grid(row=0, column=0, sticky="nsew")        sb = ttk.Scrollbar(log_frame, command=self.gen_log.yview)        sb.grid(row=0, column=1, sticky="ns")        self.gen_log.configure(yscrollcommand=sb.set)    # -------------------------------------------------------------- 工具    def _pick_files(self, var: tk.StringVar):        files = filedialog.askopenfilenames(            title="选择图片文件",            filetypes=[("图片""." + " *.".join(sorted(self.fmts["read"].keys()))),                       ("所有文件""*.*")])        if files:            # 多文件用换行分隔显示            existing = var.get().strip()            new = "\n".join(files)            var.set(new if not existing else existing + "\n" + new)    def _pick_dir(self, var: tk.StringVar, is_dir: bool = True):        if is_dir:            d = filedialog.askdirectory(title="选择文件夹")            if d:                var.set(d)        else:            f = filedialog.askopenfilename(title="选择文件")            if f:                var.set(f)    def _log(self, widget: tk.Text, msg: str):        # 线程安全:通过 after 调度到主线程        self.after(0self._do_log, widget, msg)    def _do_log(self, widget: tk.Text, msg: str):        widget.insert("end", msg + "\n")        widget.see("end")    def _set_busy(self, busy: bool):        self.after(0self._do_set_busy, busy)    def _do_set_busy(self, busy: bool):        for btn in (self.avif_btn, self.gen_btn):            btn.config(state="disabled" if busy else "normal")        self.status_var.set("处理中…" if busy else "就绪。")    def _set_status(self, text: str):        self.after(0self.status_var.set, text)    # -------------------------------------------------------------- 运行    def _parse_paths(self, text: str) -> list[str]:        # 支持换行或分号分隔        parts = [p.strip() for p in text.replace(";""\n").split("\n"if p.strip()]        return parts    def _run_avif(self):        inputs = self._parse_paths(self.avif_input_var.get())        if not inputs:            messagebox.showwarning("提示""请先选择 AVIF 文件或文件夹。")            return        tgt = self.avif_tgt_var.get()        out_dir = self.avif_out_var.get().strip()        out_dir_p = Path(out_dir) if out_dir else None        recursive = self.avif_recursive.get()        keep_dir = self.avif_keepdir.get()        overwrite = self.avif_overwrite.get()        quality = int(self.avif_quality.get())        # 源扩展名集合:avif 及其变体        src_exts = {"avif""avifs""heif""heic"} & self.fmts["read"].keys()        if not src_exts:            messagebox.showerror("错误""当前 Pillow 不支持读取 AVIF。")            return        files = _iter_files(inputs, recursive, src_exts)        self.avif_log.delete("1.0""end")        if not files:            self._log(self.avif_log, "未找到任何 AVIF 文件。")            return        src_root = self._common_root([Path(p) for p in inputs])        self._set_busy(True)        threading.Thread(            target=self._worker,            args=(files, tgt, out_dir_p, quality, keep_dir, overwrite,                  self.avif_log, src_root),            daemon=True).start()    def _run_generic(self):        inputs = self._parse_paths(self.gen_input_var.get())        if not inputs:            messagebox.showwarning("提示""请先选择输入文件或文件夹。")            return        tgt = _norm_ext(self.gen_tgt_var.get())        out_dir = self.gen_out_var.get().strip()        out_dir_p = Path(out_dir) if out_dir else None        recursive = self.gen_recursive.get()        keep_dir = self.gen_keepdir.get()        overwrite = self.gen_overwrite.get()        skip_same = self.gen_skip_same.get()        quality = int(self.gen_quality.get())        src_exts = set(self.fmts["read"].keys())        files = _iter_files(inputs, recursive, src_exts)        self.gen_log.delete("1.0""end")        if not files:            self._log(self.gen_log, "未找到任何可识别的图片文件。")            return        src_root = self._common_root([Path(p) for p in inputs])        self._set_busy(True)        threading.Thread(            target=self._worker,            args=(files, tgt, out_dir_p, quality, keep_dir, overwrite,                  self.gen_log, src_root, skip_same),            daemon=True).start()    def _worker(self, files, tgt, out_dir, quality, keep_dir, overwrite,                log_widget, src_root=None, skip_same=False):        try:            total = len(files)            ok = 0            fail = 0            for i, f in enumerate(files, 1):                self._set_status(f"[{i}/{total}{f.name}")                # 跳过同扩展名                if skip_same and _norm_ext(f.suffix) == _norm_ext(tgt):                    self._log(log_widget, f"[跳过-同格式] {f}")                    continue                success, msg = convert_one(                    f, tgt, out_dir, quality, keep_dir, src_root, overwrite)                if success:                    ok += 1                    self._log(log_widget, f"[OK] {f.name} -> {msg}")                else:                    fail += 1                    self._log(log_widget, f"[FAIL] {msg}")            self._log(log_widget,                      f"\n=== 完成:成功 {ok},失败 {fail},共 {total} ===")        except Exception as e:            self._log(log_widget, f"严重错误: {e}\n{traceback.format_exc()}")        finally:            self._set_busy(False)    @staticmethod    def _common_root(paths: list[Path]) -> Path | None:        if not paths:            return None        if len(paths) == 1:            p = paths[0]            return p if p.is_dir() else p.parent        # 计算公共父目录        common = list(paths[0].resolve().parts)        for p in paths[1:]:            parts = list(p.resolve().parts)            new_common = []            for a, b in zip(common, parts):                if a == b:                    new_common.append(a)                else:                    break            common = new_common            if not common:                break        if common:            return Path(*common)        return Nonedef main():    app = ConverterApp()    app.mainloop()if __name__ == "__main__":    main()

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 22:52:00 HTTP/2.0 GET : https://f.mffb.com.cn/a/511620.html
  2. 运行时间 : 0.296362s [ 吞吐率:3.37req/s ] 内存消耗:4,866.77kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=0bf1ac2913b301f06ccbf360b4a81305
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001050s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001487s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000834s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000788s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001558s ]
  6. SELECT * FROM `set` [ RunTime:0.000748s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001710s ]
  8. SELECT * FROM `article` WHERE `id` = 511620 LIMIT 1 [ RunTime:0.001264s ]
  9. UPDATE `article` SET `lasttime` = 1787323920 WHERE `id` = 511620 [ RunTime:0.002554s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000877s ]
  11. SELECT * FROM `article` WHERE `id` < 511620 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.019063s ]
  12. SELECT * FROM `article` WHERE `id` > 511620 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.002263s ]
  13. SELECT * FROM `article` WHERE `id` < 511620 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.057225s ]
  14. SELECT * FROM `article` WHERE `id` < 511620 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.032942s ]
  15. SELECT * FROM `article` WHERE `id` < 511620 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002806s ]
0.300134s