Repository navigation
Expand file tree
/
Copy pathVideoMetricsLab.spec
More file actions
287 lines (266 loc) · 11.9 KB
/
Copy pathVideoMetricsLab.spec
File metadata and controls
287 lines (266 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# PyInstaller spec for a self-contained Windows build.
#
# Everything the app needs at runtime is inside the output folder except
# FFmpeg itself (ffmpeg.exe, ffprobe.exe), which is deliberately not bundled
# -- see docs/BUILD.md.
#
# Build it with scripts/build_release.ps1 rather than calling pyinstaller
# directly: the tone-map DLL has to exist before this file is read.
import sys
import sysconfig
from pathlib import Path
PROJECT = Path(SPECPATH)
SITE_PACKAGES = Path(sysconfig.get_paths()["purelib"])
# --- GStreamer -------------------------------------------------------------
# The wheels are ordinary directories whose own paths are computed from
# __file__ (see gstreamer_libs.environment), so every shipped file keeps its
# path relative to site-packages and each wheel still finds its own bin/,
# plugin and typelib directories.
#
# Not every file is shipped. The wheels are a complete media framework (302
# MB); the app plays two videos through D3D11 and one soundtrack, which
# needs about a sixth of that. scripts/gstreamer_bundle.py
# holds the allow-list of plugins and computes their dependencies from the
# DLL import tables; scripts/verify_gstreamer_bundle.py then proves the
# packaged runtime can still do everything the app asks, as part of the
# build. Change what is shipped there, not here.
#
# The packages are shipped as DATA and excluded from analysis on purpose.
# Frozen as modules their __file__ would point inside the archive, and every
# path they derive from it -- GST_PLUGIN_PATH, GI_TYPELIB_PATH, the plugin
# scanner executable -- would point at files that are not there.
sys.path.insert(0, str(PROJECT))
from scripts.gstreamer_bundle import PACKAGES as GSTREAMER_PACKAGES # noqa: E402
from scripts.gstreamer_bundle import collect as collect_gstreamer # noqa: E402
datas, gstreamer_report = collect_gstreamer(SITE_PACKAGES)
print(
"GStreamer: shipping %d of the wheels' files, %.0f of %.0f MB"
% (len(datas), gstreamer_report["bundled_bytes"] / 1e6, gstreamer_report["original_bytes"] / 1e6)
)
# --- the GPU HDR->SDR shader ----------------------------------------------
# vmaf_app.core.d3d11_tonemap resolves this as <package>/native/<name>, so it
# has to land in the same place relative to vmaf_app.
TONEMAP_DLL = PROJECT / "vmaf_app" / "native" / "d3d11_tonemap.dll"
if TONEMAP_DLL.is_file():
datas.append((str(TONEMAP_DLL), "vmaf_app/native"))
else:
print("WARNING: d3d11_tonemap.dll is missing; the build will fall back "
"to FFmpeg tone mapping. Run scripts/build_d3d11_tonemap.ps1 first.")
# Video Compare's film grain synthesis for VVC (vmaf_app.core.d3d11_film_grain
# finds it beside the tone mapper), with the notice of the VTM tables it holds.
FILM_GRAIN_DLL = PROJECT / "vmaf_app" / "native" / "d3d11_film_grain.dll"
if FILM_GRAIN_DLL.is_file():
datas.append((str(FILM_GRAIN_DLL), "vmaf_app/native"))
datas.append((str(PROJECT / "native" / "h274" / "LICENSE.vtm.txt"), "vmaf_app/native/licenses"))
else:
print("WARNING: d3d11_film_grain.dll is missing; Video Compare will play VVC "
"without its film grain. Run scripts/build_d3d11_film_grain.ps1 first.")
# Video Compare's Vulkan presenter, which draws on Windows only when
# VIDEOMETRICSLAB_PRESENTER=vulkan asks for it (vmaf_app.core.vulkan_presenter
# finds it beside the Direct3D 11 one), with the notice of the Vulkan headers
# compiled into it.
VULKAN_PRESENTER_DLL = PROJECT / "vmaf_app" / "native" / "vulkan_presenter.dll"
if VULKAN_PRESENTER_DLL.is_file():
datas.append((str(VULKAN_PRESENTER_DLL), "vmaf_app/native"))
datas.append((str(PROJECT / "native" / "vulkan" / "LICENSE.vulkan-headers.txt"), "vmaf_app/native/licenses"))
else:
print("WARNING: vulkan_presenter.dll is missing; VIDEOMETRICSLAB_PRESENTER=vulkan will draw "
"with Direct3D 11. Run scripts/build_vulkan_presenter.ps1 first.")
# The decoders the GPU metrics read their frames from -- NVIDIA's, Intel's
# and AMD's, and the software decoder for what FFmpeg decodes in software
# (scripts/build_gpu_frames.ps1) -- with the notices of the headers compiled
# into them. Without one, FFmpeg decodes those videos, as before.
for name, notices in (("nvdec_frames", ("ffnvcodec/LICENSE.nv-codec-headers.txt",)),
("vpl_frames", ("onevpl/LICENSE.onevpl.txt",)),
("amf_frames", ("amf/LICENSE.amf.txt", "vulkan/LICENSE.vulkan-headers.txt")),
("mf_frames", ("vulkan/LICENSE.vulkan-headers.txt",)),
("software_frames", ())):
dll = PROJECT / "vmaf_app" / "native" / f"{name}.dll"
if dll.is_file():
datas.append((str(dll), "vmaf_app/native"))
for notice in notices:
datas.append((str(PROJECT / "native" / notice), "vmaf_app/native/licenses"))
else:
print(f"WARNING: {name}.dll is missing; FFmpeg will decode the GPU metrics' "
"videos on that GPU. Run scripts/build_gpu_frames.ps1 first.")
# Netflix's VMAF v1 models are data files, not part of the external FFmpeg
# installation. Keep them beside the package so the app can pass a portable
# path= model to any compatible libvmaf build.
VMAF_MODELS = PROJECT / "vmaf_app" / "models"
datas.append((str(VMAF_MODELS), "vmaf_app/models"))
# The window's text in each language (vmaf_app.i18n reads them beside the
# package); Qt's own dialogs use PySide6's qtbase_*.qm, which
# scripts/qt_bundle.py keeps for the same languages.
datas.append((str(PROJECT / "vmaf_app" / "translations"), "vmaf_app/translations"))
# SSIMULACRA2 and Butteraugli are standalone CPU image tools from the
# official libjxl Windows static distribution. Keep only the two tools and
# their notices; perceptual_cpu.py finds them relative to this directory.
LIBJXL_TOOLS = PROJECT / "vmaf_app" / "tools" / "libjxl"
if LIBJXL_TOOLS.is_dir():
datas.append((str(LIBJXL_TOOLS), "vmaf_app/tools/libjxl"))
else:
print("WARNING: bundled libjxl perceptual tools are missing")
# Vship's MIT-licensed metric library only (not FFVship/FFMS2), in three
# builds: CUDA (self-contained apart from the NVIDIA driver), HIP (uses the
# AMD HIP runtime when available) and Vulkan (needs only the GPU driver's
# vulkan-1.dll). Runtime failures select libjxl CPU.
VSHIP_TOOLS = PROJECT / "vmaf_app" / "tools" / "vship"
if VSHIP_TOOLS.is_dir():
datas.append((str(VSHIP_TOOLS), "vmaf_app/tools/vship"))
else:
print("WARNING: bundled Vship GPU libraries are missing; perceptual metrics will use CPU")
# libvmaf with CUDA, for VMAF and VMAF NEG on NVIDIA GPUs (libvmaf-fast's;
# libvmaf-fast.json beside it names the build, a release in a release of the
# app, scripts/fetch_libvmaf_fast.ps1): needs only Windows and the NVIDIA driver.
# Without it VMAF is calculated by FFmpeg's libvmaf, as on other GPUs.
# FFmpeg's decoders for the software frame decoder (vmaf_app/core/gpu_frames.py):
# libavcodec and libavutil with six decoders, LGPL, built by
# scripts/build_ffmpeg_decoders.ps1 and committed.
FFMPEG_DECODERS = PROJECT / "vmaf_app" / "tools" / "ffmpeg"
if (FFMPEG_DECODERS / "avcodec-63.dll").is_file():
datas.append((str(FFMPEG_DECODERS), "vmaf_app/tools/ffmpeg"))
else:
print("WARNING: FFmpeg's decoders are missing; FFmpeg's pipes carry the videos it decodes in software")
LIBVMAF_TOOLS = PROJECT / "vmaf_app" / "tools" / "libvmaf"
if LIBVMAF_TOOLS.is_dir():
datas.append((str(LIBVMAF_TOOLS), "vmaf_app/tools/libvmaf"))
else:
print("WARNING: bundled libvmaf (CUDA) is missing; VMAF will be calculated on the CPU")
# VMAF's features with Vulkan, for VMAF and VMAF NEG on any other GPU
# (libvmaf-fast's release too): needs only Windows and a Vulkan driver, and
# the libvmaf above to predict the score. Without it VMAF is calculated on the
# GPU with an NVIDIA GPU only.
VMAF_VULKAN_TOOLS = PROJECT / "vmaf_app" / "tools" / "vmaf_vulkan"
if VMAF_VULKAN_TOOLS.is_dir():
datas.append((str(VMAF_VULKAN_TOOLS), "vmaf_app/tools/vmaf_vulkan"))
else:
print("WARNING: bundled vmaf_vulkan is missing; VMAF will be calculated on the GPU with NVIDIA GPUs only")
a = Analysis(
[str(PROJECT / "vmaf_app" / "main.py")],
pathex=[str(PROJECT)],
binaries=[],
datas=datas,
# `gi` is shipped as data, so nothing traced its imports and the stdlib
# modules it reaches for were left out of the bundle. The first symptom
# was GStreamer failing to load with "No module named 'optparse'" --
# which, because playback falls back to FFmpeg, would otherwise have been
# invisible until someone tried to play a video. Collected by scanning
# the gi tree for stdlib imports; see docs/BUILD.md.
hiddenimports=[
"asyncio",
"collections",
"contextlib",
"ctypes",
"functools",
"importlib",
"inspect",
"optparse",
"pkgutil",
"platform",
"random",
"re",
"selectors",
"signal",
"socket",
"threading",
"types",
"typing",
"warnings",
"weakref",
],
hookspath=[],
runtime_hooks=[str(PROJECT / "scripts" / "pyi_rth_gstreamer.py")],
excludes=[
# Shipped as data above; freezing them too would shadow those copies
# with ones whose __file__ points into the archive.
*GSTREAMER_PACKAGES,
"gstreamer_bundle",
"gi",
# Never imported by this app, and each drags in a lot.
"tkinter",
"PySide6.QtWebEngineCore",
"PySide6.QtWebEngineWidgets",
"PySide6.QtQuick",
"PySide6.QtQml",
"PySide6.Qt3DCore",
"PySide6.QtCharts",
"PySide6.QtDataVisualization",
"PySide6.QtMultimedia",
"PySide6.QtMultimediaWidgets",
"PySide6.QtBluetooth",
"PySide6.QtNfc",
"PySide6.QtPositioning",
"PySide6.QtWebSockets",
"PySide6.QtWebChannel",
"PySide6.QtDesigner",
"PySide6.QtHelp",
"PySide6.QtTest",
"PySide6.QtSql",
],
noarchive=False,
optimize=0,
)
# --- Qt --------------------------------------------------------------------
# PyInstaller's Qt hooks bundle by category -- a software OpenGL renderer,
# QtMultimedia's FFmpeg, the QML stack, PDF, networking and 96 translations
# for an app that imports QtCore, QtGui and QtWidgets. scripts/qt_bundle.py
# keeps those three modules, the plugins the app needs and their import
# closure, and drops the rest of the PySide6 folder after the analysis.
from scripts.qt_bundle import prune as prune_qt # noqa: E402
a.binaries, a.datas, qt_report = prune_qt(a.binaries, a.datas)
print(
"Qt: keeping %d of PySide6's files, %.0f of %.0f MB"
% (len(qt_report["kept"]), qt_report["qt_bytes_after"] / 1e6, qt_report["qt_bytes_before"] / 1e6)
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="VideoMetricsLab",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
# A GUI app: no console window. Startup failures are still reachable by
# running it from a terminal, which is what docs/BUILD.md says to do.
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
# The command line (vmaf_app/cli.py): the same program with a console, beside
# the app in the same folder and sharing all of it. A windowed program cannot
# print to the terminal that started it -- the prompt returns before its
# output -- so the command line is a program of its own; main.py runs the CLI
# when started under this name.
cli = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="VideoMetricsLab-cli",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
cli,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="VideoMetricsLab",
)