You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyInstaller打包Vispy应用本地报FT_Exception,其他机器正常

问题:Vispy项目PyInstaller打包后本地运行报FT_Exception错误

现象

  • 直接运行Python脚本(.py)时一切正常。
  • 使用PyInstaller打包为.exe后,程序在其他计算机上运行正常,但本地抛出错误:
    freetype.ft_errors.FT_Exception: FT_Exception:  (cannot open resource)
    

打包配置文件(ls2.spec)

# -*- mode: python -*-
# ls2.spec
import sys
import os  # 原代码缺少os导入,需补上
from PyInstaller.compat import is_win, is_darwin, is_linux
from PyInstaller.utils.hooks import collect_submodules,collect_dynamic_libs
import vispy.glsl
import vispy.io
import freetype

block_cipher = None

data_files = [
    (os.path.dirname(vispy.glsl.__file__), os.path.join("vispy", "glsl")),
    (os.path.join(os.path.dirname(vispy.io.__file__), "_data"), os.path.join("vispy", "io", "_data")),
    (os.path.dirname(vispy.util.__file__), os.path.join("vispy", "util")),
    (os.path.dirname(freetype.__file__), os.path.join("freetype")),
]

hidden_imports = [
    "vispy.ext._bundled.six",
    "vispy.app.backends._wx",
    "vispy.app.backends._pyqt5",
    "freetype"
]

if is_win:
    hidden_imports += collect_submodules("encodings")


a = Analysis(
    ['ls2.py'],
    pathex=[],
    binaries=[],
    datas=data_files,
    hiddenimports=hidden_imports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='ls2',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='ls2',
)

报错堆栈信息

WARNING: Traceback (most recent call last):
  File "ls.py", line 229, in <module>
  File "vispy\app\backends\_qt.py", line 626, in event
  File "vispy\app\backends\_qt.py", line 626, in event
  File "vispy\app\backends\_qt.py", line 928, in paintGL
  File "vispy\util\event.py", line 453, in __call__
    self._invoke_callback(cb, event)
  File "vispy\util\event.py", line 471, in _invoke_callback
    _handle_exception(self.ignore_callback_errors,
  << caught exception here: >>
  File "vispy\util\event.py", line 469, in _invoke_callback
    cb(event)
  File "vispy\scene\canvas.py", line 219, in on_draw
  File "vispy\scene\canvas.py", line 278, in _draw_scene
  File "vispy\scene\canvas.py", line 316, in draw_visual
  File "vispy\scene\visuals.py", line 106, in draw
  File "vispy\visuals\visual.py", line 668, in draw
  File "vispy\visuals\visual.py", line 668, in draw
  File "vispy\visuals\visual.py", line 505, in draw
  File "vispy\visuals\text\text.py", line 533, in _prepare_draw
  File "vispy\visuals\text\text.py", line 534, in <listcomp>
  File "vispy\visuals\text\text.py", line 264, in _text_to_vbo
  File "vispy\visuals\text\text.py", line 81, in __getitem__
  File "vispy\visuals\text\text.py", line 95, in _load_char
  File "vispy\util\fonts\_freetype.py", line 50, in _load_glyph
    face = _load_font(f['face'], f['bold'], f['italic'])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "vispy\util\fonts\_freetype.py", line 37, in _load_font
    font = Face(fname)
           ^^^^^^^^^^^
  File "freetype\__init__.py", line 1192, in __init__
    raise FT_Exception(error)
freetype.ft_errors.FT_Exception: FT_Exception:  (cannot open resource)

原因分析

  1. 本地系统字体异常:打包后的exe优先尝试加载本地系统字体,但你的电脑存在字体文件损坏、权限不足或路径识别问题,其他机器的系统字体环境无异常。
  2. 打包资源不完整:虽然spec包含了vispy.io._data,但Vispy默认字体的路径在打包后未被正确识别,本地运行时无法回退到打包的字体资源。
  3. 动态库版本冲突:原spec仅收集了freetype的Python模块,未包含其依赖的动态链接库,本地运行时与系统已安装的freetype库版本冲突;其他机器未预安装freetype,只能使用打包版本,反而正常。

解决方案

1. 修复spec文件,完善资源打包

  • 补上os模块导入,解决路径解析报错。
  • 用collect_dynamic_libs收集freetype的动态库,添加到binaries中。
  • 明确指定Vispy字体文件路径,确保被打包:

修改后的spec关键部分:

import os  # 新增

# 收集freetype动态库
binaries = collect_dynamic_libs('freetype')

data_files = [
    (os.path.dirname(vispy.glsl.__file__), os.path.join("vispy", "glsl")),
    (os.path.join(os.path.dirname(vispy.io.__file__), "_data"), os.path.join("vispy", "io", "_data")),
    (os.path.dirname(vispy.util.__file__), os.path.join("vispy", "util")),
    # 明确收集字体文件
    (os.path.join(os.path.dirname(vispy.io.__file__), "_data", "fonts"), os.path.join("vispy", "io", "_data", "fonts")),
]

a = Analysis(
    ['ls2.py'],
    pathex=[],
    binaries=binaries,  # 加入动态库
    datas=data_files,
    # 其他参数不变
)

2. 代码中显式指定打包后的字体

在项目代码(ls2.py)中添加逻辑,让Vispy优先使用打包后的默认字体:

import sys
import os
from vispy import visuals

def get_packed_font_path():
    # PyInstaller打包后,资源文件存放在sys._MEIPASS目录
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, 'vispy', 'io', '_data', 'fonts', 'OpenSans-Regular.ttf')
    # 未打包时使用默认路径
    else:
        from vispy.io import load_data_file
        return load_data_file('fonts/OpenSans-Regular.ttf')

# 设置全局默认字体
visuals.TextVisual.set_default_font(get_packed_font_path())

3. 排除UPX对freetype动态库的压缩

UPX压缩可能损坏freetype动态库,在spec的EXE和COLLECT部分添加排除:

exe = EXE(
    # 其他参数不变
    upx_exclude=['freetype.dll'],  # Windows下的freetype动态库文件名,按需调整
)
coll = COLLECT(
    # 其他参数不变
    upx_exclude=['freetype.dll'],
)

4. 临时排查本地系统字体问题

  • 临时重命名系统字体目录(如Windows下的C:\Windows\Fonts,注意备份),测试是否为系统字体导致的冲突。
  • 检查是否存在FREETYPE_PATH环境变量,如有则临时删除后再运行exe。

内容的提问来源于stack exchange,提问作者eehan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 05:04:50