pinokiod 7.2.1 → 7.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kernel/bin/ffmpeg.js +370 -231
- package/package.json +1 -1
package/kernel/bin/ffmpeg.js
CHANGED
|
@@ -1,236 +1,389 @@
|
|
|
1
|
-
const crypto = require("crypto")
|
|
2
1
|
const fs = require("fs")
|
|
3
2
|
const path = require("path")
|
|
4
3
|
const { execFile } = require("child_process")
|
|
4
|
+
const ParcelWatcher = require("@parcel/watcher")
|
|
5
5
|
const semver = require("semver")
|
|
6
6
|
const { rimraf } = require("rimraf")
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
source: "Gyan Windows essentials build",
|
|
17
|
-
archives: [{
|
|
18
|
-
url: "https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-8.1-essentials_build.zip",
|
|
19
|
-
sha256: "8748283d821613d930b0e7be685aaa9df4ca6f0ad4d0c42fd02622b3623463c6",
|
|
20
|
-
binaries: {
|
|
21
|
-
ffmpeg: "ffmpeg.exe",
|
|
22
|
-
ffprobe: "ffprobe.exe"
|
|
23
|
-
}
|
|
24
|
-
}]
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
darwin: {
|
|
28
|
-
x64: {
|
|
29
|
-
version: RELEASE_VERSION,
|
|
30
|
-
source: "Martin Riedl macOS amd64 release",
|
|
31
|
-
archives: [{
|
|
32
|
-
url: "https://ffmpeg.martin-riedl.de/download/macos/amd64/1774556648_8.1/ffmpeg.zip",
|
|
33
|
-
sha256: "eaa8aa619f8eccc7f548a730097f5d299cbf2d418888421c137557344d821130",
|
|
34
|
-
binaries: {
|
|
35
|
-
ffmpeg: "ffmpeg"
|
|
36
|
-
}
|
|
37
|
-
}, {
|
|
38
|
-
url: "https://ffmpeg.martin-riedl.de/download/macos/amd64/1774556648_8.1/ffprobe.zip",
|
|
39
|
-
sha256: "221bd0716dc15daf5745c5503773e5c23264c10c5ea956aa17ef492bbc0b0ac7",
|
|
40
|
-
binaries: {
|
|
41
|
-
ffprobe: "ffprobe"
|
|
42
|
-
}
|
|
43
|
-
}]
|
|
44
|
-
},
|
|
45
|
-
arm64: {
|
|
46
|
-
version: RELEASE_VERSION,
|
|
47
|
-
source: "Martin Riedl macOS arm64 release",
|
|
48
|
-
archives: [{
|
|
49
|
-
url: "https://ffmpeg.martin-riedl.de/download/macos/arm64/1774549676_8.1/ffmpeg.zip",
|
|
50
|
-
sha256: "cc3a7e0cce36c5eca6c17eeb93830984c657637a8e710dc98f19c8051201fa3a",
|
|
51
|
-
binaries: {
|
|
52
|
-
ffmpeg: "ffmpeg"
|
|
53
|
-
}
|
|
54
|
-
}, {
|
|
55
|
-
url: "https://ffmpeg.martin-riedl.de/download/macos/arm64/1774549676_8.1/ffprobe.zip",
|
|
56
|
-
sha256: "fd2e6b7fad9c9aa2bec17c0d7211b5afcc00b4b5c9b63c120985e80c3c198af6",
|
|
57
|
-
binaries: {
|
|
58
|
-
ffprobe: "ffprobe"
|
|
59
|
-
}
|
|
60
|
-
}]
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
linux: {
|
|
64
|
-
x64: {
|
|
65
|
-
version: RELEASE_VERSION,
|
|
66
|
-
source: "Martin Riedl Linux amd64 release",
|
|
67
|
-
archives: [{
|
|
68
|
-
url: "https://ffmpeg.martin-riedl.de/download/linux/amd64/1774550169_8.1/ffmpeg.zip",
|
|
69
|
-
sha256: "49f9a3642387626f82fd70dd6a268807efe23e0560d6934a6531d6e3e668f18f",
|
|
70
|
-
binaries: {
|
|
71
|
-
ffmpeg: "ffmpeg"
|
|
72
|
-
}
|
|
73
|
-
}, {
|
|
74
|
-
url: "https://ffmpeg.martin-riedl.de/download/linux/amd64/1774550169_8.1/ffprobe.zip",
|
|
75
|
-
sha256: "422082501af33fabb3946d101d098e5105f44492e5a16357c3fac79421544b0e",
|
|
76
|
-
binaries: {
|
|
77
|
-
ffprobe: "ffprobe"
|
|
78
|
-
}
|
|
79
|
-
}]
|
|
80
|
-
},
|
|
81
|
-
arm64: {
|
|
82
|
-
version: RELEASE_VERSION,
|
|
83
|
-
source: "Martin Riedl Linux arm64 release",
|
|
84
|
-
archives: [{
|
|
85
|
-
url: "https://ffmpeg.martin-riedl.de/download/linux/arm64/1774548896_8.1/ffmpeg.zip",
|
|
86
|
-
sha256: "87000dd625a4f409a5baf71ac177c22210db04ea144e01241713ab196ed39689",
|
|
87
|
-
binaries: {
|
|
88
|
-
ffmpeg: "ffmpeg"
|
|
89
|
-
}
|
|
90
|
-
}, {
|
|
91
|
-
url: "https://ffmpeg.martin-riedl.de/download/linux/arm64/1774548896_8.1/ffprobe.zip",
|
|
92
|
-
sha256: "eb56a190dea6bdd08da2c1e63d7c7523817384eff4dff227f4b088e56205414b",
|
|
93
|
-
binaries: {
|
|
94
|
-
ffprobe: "ffprobe"
|
|
95
|
-
}
|
|
96
|
-
}]
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
7
|
+
|
|
8
|
+
const RELEASE_VERSION = "8.0.1"
|
|
9
|
+
const RELEASE_RANGE = ">=8.0.1 <8.1.0"
|
|
10
|
+
const CONDA_SPEC = `ffmpeg=${RELEASE_VERSION}`
|
|
11
|
+
|
|
12
|
+
const WINDOWS_GDK_PIXBUF_POST_LINK_NOOP = `@echo off
|
|
13
|
+
rem Pinokio intentionally skips gdk-pixbuf loader cache generation for FFmpeg installs.
|
|
14
|
+
exit /b 0
|
|
15
|
+
`
|
|
100
16
|
|
|
101
17
|
class Ffmpeg {
|
|
102
|
-
description = "Installs
|
|
18
|
+
description = "Installs FFmpeg for audio and video processing."
|
|
103
19
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const arch = this.kernel.arch
|
|
107
|
-
const spec = ARTIFACTS[platform] && ARTIFACTS[platform][arch]
|
|
108
|
-
if (!spec) {
|
|
109
|
-
throw new Error(`Standalone FFmpeg is not configured for ${platform}/${arch}`)
|
|
110
|
-
}
|
|
111
|
-
return spec
|
|
20
|
+
cmd() {
|
|
21
|
+
return CONDA_SPEC
|
|
112
22
|
}
|
|
113
23
|
|
|
114
|
-
|
|
115
|
-
|
|
24
|
+
env(kernel) {
|
|
25
|
+
const activeKernel = kernel || this.kernel
|
|
26
|
+
const env = {
|
|
27
|
+
FFMPEG_PATH: this.binaryPath("ffmpeg", activeKernel),
|
|
28
|
+
FFPROBE_PATH: this.binaryPath("ffprobe", activeKernel)
|
|
29
|
+
}
|
|
30
|
+
if (activeKernel.platform === "linux") {
|
|
31
|
+
env.LD_LIBRARY_PATH = [this.libraryDir(activeKernel)]
|
|
32
|
+
}
|
|
33
|
+
return env
|
|
116
34
|
}
|
|
117
35
|
|
|
118
|
-
|
|
119
|
-
|
|
36
|
+
binaryPath(tool, kernel = this.kernel) {
|
|
37
|
+
const filename = kernel.platform === "win32" ? `${tool}.exe` : tool
|
|
38
|
+
if (kernel.platform === "win32") {
|
|
39
|
+
return kernel.bin.path("miniconda", "Library", "bin", filename)
|
|
40
|
+
}
|
|
41
|
+
return kernel.bin.path("miniconda", "bin", filename)
|
|
120
42
|
}
|
|
121
43
|
|
|
122
|
-
|
|
123
|
-
|
|
44
|
+
libraryDir(kernel = this.kernel) {
|
|
45
|
+
if (kernel.platform === "win32") {
|
|
46
|
+
return kernel.bin.path("miniconda", "Library", "bin")
|
|
47
|
+
}
|
|
48
|
+
return kernel.bin.path("miniconda", "lib")
|
|
124
49
|
}
|
|
125
50
|
|
|
126
|
-
|
|
127
|
-
return
|
|
51
|
+
legacyStandalonePaths() {
|
|
52
|
+
return [
|
|
53
|
+
this.kernel.bin.path("ffmpeg"),
|
|
54
|
+
this.kernel.bin.path("ffmpeg-tmp")
|
|
55
|
+
]
|
|
128
56
|
}
|
|
129
57
|
|
|
130
|
-
|
|
131
|
-
|
|
58
|
+
async start() {
|
|
59
|
+
if (this.kernel.platform !== "darwin") {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
if (!this.isInstalledVersion()) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
await this.syncMacUvLibraryShims()
|
|
66
|
+
await this.startMacUvLibraryWatcher()
|
|
132
67
|
}
|
|
133
68
|
|
|
134
|
-
|
|
135
|
-
|
|
69
|
+
async install(req, ondata) {
|
|
70
|
+
await this.cleanupLegacyStandalone(ondata)
|
|
71
|
+
if (this.kernel.platform === "win32") {
|
|
72
|
+
await this.installWindows(ondata)
|
|
73
|
+
} else {
|
|
74
|
+
await this.installStandard(ondata)
|
|
75
|
+
}
|
|
76
|
+
await this.syncMacUvLibraryShims(ondata)
|
|
77
|
+
await this.selfTest(ondata)
|
|
136
78
|
}
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
80
|
+
async installStandard(ondata) {
|
|
81
|
+
await this.kernel.bin.exec({
|
|
82
|
+
message: [
|
|
83
|
+
"conda clean -y --all",
|
|
84
|
+
`conda install -y -c conda-forge ${this.cmd()}`
|
|
85
|
+
]
|
|
86
|
+
}, ondata)
|
|
144
87
|
}
|
|
145
88
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
89
|
+
async installWindows(ondata) {
|
|
90
|
+
await this.kernel.bin.exec({
|
|
91
|
+
message: [
|
|
92
|
+
"conda clean -y --all",
|
|
93
|
+
`conda install -y --download-only -c conda-forge ${this.cmd()}`
|
|
94
|
+
]
|
|
95
|
+
}, ondata)
|
|
96
|
+
|
|
97
|
+
await this.patchWindowsGdkPixbufPostLink(ondata)
|
|
98
|
+
|
|
99
|
+
await this.kernel.bin.exec({
|
|
100
|
+
message: `conda install -y --offline -c conda-forge ${this.cmd()}`
|
|
101
|
+
}, ondata)
|
|
149
102
|
}
|
|
150
103
|
|
|
151
|
-
async
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
104
|
+
async patchWindowsGdkPixbufPostLink(ondata) {
|
|
105
|
+
const pkgsDir = this.kernel.bin.path("miniconda", "pkgs")
|
|
106
|
+
const entries = await fs.promises.readdir(pkgsDir, { withFileTypes: true })
|
|
107
|
+
const packageDirs = entries
|
|
108
|
+
.filter((entry) => entry.isDirectory() && /^gdk-pixbuf-/.test(entry.name))
|
|
109
|
+
.map((entry) => path.resolve(pkgsDir, entry.name))
|
|
157
110
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
111
|
+
if (packageDirs.length === 0) {
|
|
112
|
+
throw new Error("Could not find downloaded gdk-pixbuf package in the Conda cache after --download-only")
|
|
113
|
+
}
|
|
161
114
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
await fs.promises.mkdir(extractDir, { recursive: true })
|
|
178
|
-
await decompress(archivePath, extractDir)
|
|
179
|
-
|
|
180
|
-
for (const [tool, expectedName] of Object.entries(archive.binaries)) {
|
|
181
|
-
const source = await this.findFileByName(extractDir, expectedName)
|
|
182
|
-
if (!source) {
|
|
183
|
-
throw new Error(`Could not find ${expectedName} inside ${filename}`)
|
|
184
|
-
}
|
|
185
|
-
const destination = this.binaryPath(tool)
|
|
186
|
-
await fs.promises.copyFile(source, destination)
|
|
187
|
-
if (this.kernel.platform !== "win32") {
|
|
188
|
-
await fs.promises.chmod(destination, 0o755)
|
|
115
|
+
let patchedCount = 0
|
|
116
|
+
for (const packageDir of packageDirs) {
|
|
117
|
+
const scripts = [
|
|
118
|
+
path.resolve(packageDir, "Scripts", ".gdk-pixbuf-post-link.bat"),
|
|
119
|
+
path.resolve(packageDir, "info", "recipe", "post-link.bat")
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
for (const script of scripts) {
|
|
123
|
+
try {
|
|
124
|
+
await fs.promises.access(script)
|
|
125
|
+
await fs.promises.writeFile(script, WINDOWS_GDK_PIXBUF_POST_LINK_NOOP)
|
|
126
|
+
patchedCount += 1
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error && error.code !== "ENOENT") {
|
|
129
|
+
throw error
|
|
189
130
|
}
|
|
190
131
|
}
|
|
191
132
|
}
|
|
133
|
+
}
|
|
192
134
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
await this.ensureLegacyCondaFfmpegRemoved(ondata)
|
|
196
|
-
ondata({ raw: `ffmpeg ${spec.version} installed from ${spec.source}\r\n` })
|
|
197
|
-
} catch (error) {
|
|
198
|
-
await rimraf(rootPath)
|
|
199
|
-
throw error
|
|
200
|
-
} finally {
|
|
201
|
-
for (const download of downloads) {
|
|
202
|
-
await fs.promises.rm(download, { force: true }).catch(() => {})
|
|
203
|
-
}
|
|
204
|
-
await rimraf(tempDir)
|
|
135
|
+
if (patchedCount === 0) {
|
|
136
|
+
throw new Error("Found gdk-pixbuf in the Conda cache, but did not find any post-link scripts to patch")
|
|
205
137
|
}
|
|
138
|
+
|
|
139
|
+
ondata({ raw: `patched ${patchedCount} gdk-pixbuf post-link script(s) in the Conda cache...\r\n` })
|
|
206
140
|
}
|
|
207
141
|
|
|
208
142
|
async installed() {
|
|
209
143
|
try {
|
|
210
|
-
|
|
211
|
-
await fs.promises.access(this.binaryPath("ffprobe"))
|
|
212
|
-
if (this.hasLegacyCondaFfmpeg()) {
|
|
213
|
-
console.log("standalone ffmpeg installed check failed: legacy conda ffmpeg is still present")
|
|
144
|
+
if (!this.isInstalledVersion()) {
|
|
214
145
|
return false
|
|
215
146
|
}
|
|
147
|
+
|
|
148
|
+
await fs.promises.access(this.binaryPath("ffmpeg"))
|
|
149
|
+
await fs.promises.access(this.binaryPath("ffprobe"))
|
|
150
|
+
await this.syncMacUvLibraryShims()
|
|
151
|
+
await this.startMacUvLibraryWatcher()
|
|
216
152
|
await this.selfTest()
|
|
217
153
|
return true
|
|
218
154
|
} catch (error) {
|
|
219
|
-
console.log("
|
|
155
|
+
console.log("conda ffmpeg installed check failed", error && error.message ? error.message : error)
|
|
220
156
|
return false
|
|
221
157
|
}
|
|
222
158
|
}
|
|
223
159
|
|
|
224
160
|
async uninstall(req, ondata) {
|
|
225
|
-
|
|
226
|
-
await
|
|
227
|
-
await
|
|
228
|
-
|
|
161
|
+
await this.stopMacUvLibraryWatcher()
|
|
162
|
+
await this.removeMacUvLibraryShims(ondata)
|
|
163
|
+
await this.kernel.bin.exec({
|
|
164
|
+
message: "conda remove -y ffmpeg"
|
|
165
|
+
}, ondata)
|
|
166
|
+
await this.cleanupLegacyStandalone(ondata)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
isInstalledVersion() {
|
|
170
|
+
if (!this.kernel.bin.installed?.conda?.has("ffmpeg")) {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
return this.kernel.bin.installed?.conda_versions?.ffmpeg === RELEASE_VERSION
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async cleanupLegacyStandalone(ondata) {
|
|
177
|
+
for (const target of this.legacyStandalonePaths()) {
|
|
178
|
+
const exists = await fs.promises.access(target).then(() => true).catch(() => false)
|
|
179
|
+
if (exists) {
|
|
180
|
+
if (ondata) {
|
|
181
|
+
ondata({ raw: `removing legacy standalone ffmpeg files from ${target}...\r\n` })
|
|
182
|
+
}
|
|
183
|
+
await rimraf(target)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
uvPythonRoot(kernel = this.kernel) {
|
|
189
|
+
return kernel.path("cache", "XDG_DATA_HOME", "uv", "python")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async startMacUvLibraryWatcher() {
|
|
193
|
+
if (this.kernel.platform !== "darwin" || this.macUvLibraryWatcher) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const root = this.uvPythonRoot()
|
|
198
|
+
await fs.promises.mkdir(root, { recursive: true })
|
|
199
|
+
this.macUvLibraryWatcher = await ParcelWatcher.subscribe(root, (error, events) => {
|
|
200
|
+
if (error) {
|
|
201
|
+
console.warn("ffmpeg uv library watcher error", error && error.message ? error.message : error)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
if (!events || events.length === 0) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
this.scheduleMacUvLibraryShimSync()
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async stopMacUvLibraryWatcher() {
|
|
212
|
+
if (this.macUvLibraryShimSyncTimer) {
|
|
213
|
+
clearTimeout(this.macUvLibraryShimSyncTimer)
|
|
214
|
+
this.macUvLibraryShimSyncTimer = null
|
|
215
|
+
}
|
|
216
|
+
if (this.macUvLibraryWatcher) {
|
|
217
|
+
await this.macUvLibraryWatcher.unsubscribe()
|
|
218
|
+
this.macUvLibraryWatcher = null
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
scheduleMacUvLibraryShimSync() {
|
|
223
|
+
if (this.macUvLibraryShimSyncTimer) {
|
|
224
|
+
clearTimeout(this.macUvLibraryShimSyncTimer)
|
|
225
|
+
}
|
|
226
|
+
this.macUvLibraryShimSyncTimer = setTimeout(async () => {
|
|
227
|
+
this.macUvLibraryShimSyncTimer = null
|
|
228
|
+
try {
|
|
229
|
+
await this.syncMacUvLibraryShims()
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.warn("ffmpeg uv library shim sync error", error && error.message ? error.message : error)
|
|
232
|
+
}
|
|
233
|
+
}, 250)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async uvLibraryDirs(kernel = this.kernel) {
|
|
237
|
+
if (kernel.platform !== "darwin") {
|
|
238
|
+
return []
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const root = this.uvPythonRoot(kernel)
|
|
242
|
+
const entries = await fs.promises.readdir(root, { withFileTypes: true }).catch(() => [])
|
|
243
|
+
const dirs = []
|
|
244
|
+
|
|
245
|
+
for (const entry of entries) {
|
|
246
|
+
if (!entry.isDirectory()) {
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
const libDir = path.resolve(root, entry.name, "lib")
|
|
250
|
+
const exists = await fs.promises.access(libDir).then(() => true).catch(() => false)
|
|
251
|
+
if (exists) {
|
|
252
|
+
dirs.push(libDir)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return dirs
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async ffmpegLibraryFiles(kernel = this.kernel) {
|
|
260
|
+
if (kernel.platform !== "darwin") {
|
|
261
|
+
return []
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const dir = this.libraryDir(kernel)
|
|
265
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
266
|
+
return entries
|
|
267
|
+
.filter((entry) => entry.isFile() || entry.isSymbolicLink())
|
|
268
|
+
.map((entry) => entry.name)
|
|
269
|
+
.filter((name) => /^lib(?:av|sw)[^.]+(?:\.\d+)*\.dylib$/i.test(name))
|
|
270
|
+
.sort()
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async syncMacUvLibraryShims(ondata) {
|
|
274
|
+
if (this.kernel.platform !== "darwin") {
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const [libraryDirs, libraryFiles] = await Promise.all([
|
|
279
|
+
this.uvLibraryDirs(),
|
|
280
|
+
this.ffmpegLibraryFiles()
|
|
281
|
+
])
|
|
282
|
+
|
|
283
|
+
if (libraryDirs.length === 0 || libraryFiles.length === 0) {
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let createdCount = 0
|
|
288
|
+
let refreshedCount = 0
|
|
289
|
+
const sourceDir = this.libraryDir()
|
|
290
|
+
|
|
291
|
+
for (const libDir of libraryDirs) {
|
|
292
|
+
for (const filename of libraryFiles) {
|
|
293
|
+
const source = path.resolve(sourceDir, filename)
|
|
294
|
+
const target = path.resolve(libDir, filename)
|
|
295
|
+
const desiredLink = path.relative(libDir, source)
|
|
296
|
+
|
|
297
|
+
let stat
|
|
298
|
+
try {
|
|
299
|
+
stat = await fs.promises.lstat(target)
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (!error || error.code !== "ENOENT") {
|
|
302
|
+
throw error
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!stat) {
|
|
307
|
+
await fs.promises.symlink(desiredLink, target)
|
|
308
|
+
createdCount += 1
|
|
309
|
+
continue
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!stat.isSymbolicLink()) {
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const currentLink = await fs.promises.readlink(target)
|
|
317
|
+
if (currentLink === desiredLink) {
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await fs.promises.unlink(target)
|
|
322
|
+
await fs.promises.symlink(desiredLink, target)
|
|
323
|
+
refreshedCount += 1
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (ondata && (createdCount > 0 || refreshedCount > 0)) {
|
|
328
|
+
ondata({
|
|
329
|
+
raw: `synced ${createdCount + refreshedCount} FFmpeg dylib shim(s) into uv Python runtime libraries...\r\n`
|
|
330
|
+
})
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async removeMacUvLibraryShims(ondata) {
|
|
335
|
+
if (this.kernel.platform !== "darwin") {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const [libraryDirs, libraryFiles] = await Promise.all([
|
|
340
|
+
this.uvLibraryDirs(),
|
|
341
|
+
this.ffmpegLibraryFiles()
|
|
342
|
+
])
|
|
343
|
+
|
|
344
|
+
if (libraryDirs.length === 0 || libraryFiles.length === 0) {
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const sourceDir = this.libraryDir()
|
|
349
|
+
let removedCount = 0
|
|
350
|
+
|
|
351
|
+
for (const libDir of libraryDirs) {
|
|
352
|
+
for (const filename of libraryFiles) {
|
|
353
|
+
const target = path.resolve(libDir, filename)
|
|
354
|
+
|
|
355
|
+
let stat
|
|
356
|
+
try {
|
|
357
|
+
stat = await fs.promises.lstat(target)
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (!error || error.code !== "ENOENT") {
|
|
360
|
+
throw error
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (!stat || !stat.isSymbolicLink()) {
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const currentLink = await fs.promises.readlink(target)
|
|
369
|
+
const resolved = path.resolve(libDir, currentLink)
|
|
370
|
+
if (path.dirname(resolved) !== sourceDir) {
|
|
371
|
+
continue
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
await fs.promises.unlink(target)
|
|
375
|
+
removedCount += 1
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (ondata && removedCount > 0) {
|
|
380
|
+
ondata({ raw: `removed ${removedCount} FFmpeg dylib shim(s) from uv Python runtime libraries...\r\n` })
|
|
381
|
+
}
|
|
229
382
|
}
|
|
230
383
|
|
|
231
384
|
async selfTest(ondata) {
|
|
232
385
|
if (ondata) {
|
|
233
|
-
ondata({ raw: "verifying ffmpeg
|
|
386
|
+
ondata({ raw: "verifying ffmpeg installation...\r\n" })
|
|
234
387
|
}
|
|
235
388
|
|
|
236
389
|
const ffmpegVersionOutput = await this.execBinary(this.binaryPath("ffmpeg"), ["-version"])
|
|
@@ -250,66 +403,52 @@ class Ffmpeg {
|
|
|
250
403
|
throw new Error(`Unexpected ffprobe version: ${this.firstLine(ffprobeVersionOutput)}`)
|
|
251
404
|
}
|
|
252
405
|
|
|
406
|
+
await this.assertSharedLibraries()
|
|
253
407
|
return true
|
|
254
408
|
}
|
|
255
409
|
|
|
256
|
-
async
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
410
|
+
async assertSharedLibraries() {
|
|
411
|
+
const dir = this.libraryDir()
|
|
412
|
+
const entries = await fs.promises.readdir(dir).catch(() => {
|
|
413
|
+
throw new Error(`Missing FFmpeg library directory: ${dir}`)
|
|
414
|
+
})
|
|
261
415
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (this.hasLegacyCondaFfmpeg()) {
|
|
268
|
-
throw new Error("Legacy conda ffmpeg is still installed in the base environment")
|
|
416
|
+
const patterns = this.sharedLibraryPatterns()
|
|
417
|
+
for (const pattern of patterns) {
|
|
418
|
+
if (!entries.some((name) => pattern.test(name))) {
|
|
419
|
+
throw new Error(`Missing FFmpeg shared library matching ${pattern}`)
|
|
420
|
+
}
|
|
269
421
|
}
|
|
270
422
|
}
|
|
271
423
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
424
|
+
sharedLibraryPatterns() {
|
|
425
|
+
if (this.kernel.platform === "win32") {
|
|
426
|
+
return [
|
|
427
|
+
/^avcodec-\d+\.dll$/i,
|
|
428
|
+
/^avformat-\d+\.dll$/i,
|
|
429
|
+
/^avutil-\d+\.dll$/i,
|
|
430
|
+
/^swresample-\d+\.dll$/i,
|
|
431
|
+
/^swscale-\d+\.dll$/i
|
|
432
|
+
]
|
|
276
433
|
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async sha256(filePath) {
|
|
280
|
-
const buffer = await fs.promises.readFile(filePath)
|
|
281
|
-
return crypto.createHash("sha256").update(buffer).digest("hex")
|
|
282
|
-
}
|
|
283
434
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
ffmpeg: this.binaryPath("ffmpeg"),
|
|
293
|
-
ffprobe: this.binaryPath("ffprobe")
|
|
294
|
-
}
|
|
435
|
+
if (this.kernel.platform === "darwin") {
|
|
436
|
+
return [
|
|
437
|
+
/^libavcodec(\.\d+)*\.dylib$/i,
|
|
438
|
+
/^libavformat(\.\d+)*\.dylib$/i,
|
|
439
|
+
/^libavutil(\.\d+)*\.dylib$/i,
|
|
440
|
+
/^libswresample(\.\d+)*\.dylib$/i,
|
|
441
|
+
/^libswscale(\.\d+)*\.dylib$/i
|
|
442
|
+
]
|
|
295
443
|
}
|
|
296
|
-
await fs.promises.writeFile(this.manifestPath(), JSON.stringify(manifest, null, 2))
|
|
297
|
-
}
|
|
298
444
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return found
|
|
307
|
-
}
|
|
308
|
-
} else if (entry.name === targetName) {
|
|
309
|
-
return fullPath
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return null
|
|
445
|
+
return [
|
|
446
|
+
/^libavcodec\.so(\.\d+)*$/i,
|
|
447
|
+
/^libavformat\.so(\.\d+)*$/i,
|
|
448
|
+
/^libavutil\.so(\.\d+)*$/i,
|
|
449
|
+
/^libswresample\.so(\.\d+)*$/i,
|
|
450
|
+
/^libswscale\.so(\.\d+)*$/i
|
|
451
|
+
]
|
|
313
452
|
}
|
|
314
453
|
|
|
315
454
|
execBinary(file, args) {
|