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.
Files changed (2) hide show
  1. package/kernel/bin/ffmpeg.js +370 -231
  2. package/package.json +1 -1
@@ -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
- const decompress = require("decompress")
8
-
9
- const RELEASE_VERSION = "8.1"
10
- const RELEASE_RANGE = ">=8.1.0 <8.2.0"
11
-
12
- const ARTIFACTS = {
13
- win32: {
14
- x64: {
15
- version: RELEASE_VERSION,
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 standalone FFmpeg and FFprobe binaries with MP3 export support."
18
+ description = "Installs FFmpeg for audio and video processing."
103
19
 
104
- artifact() {
105
- const platform = this.kernel.platform
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
- rootPath() {
115
- return this.kernel.bin.path("ffmpeg")
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
- binDir() {
119
- return this.kernel.bin.path("ffmpeg", "bin")
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
- tempDir() {
123
- return this.kernel.bin.path("ffmpeg-tmp")
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
- manifestPath() {
127
- return this.kernel.bin.path("ffmpeg", "INSTALL.json")
51
+ legacyStandalonePaths() {
52
+ return [
53
+ this.kernel.bin.path("ffmpeg"),
54
+ this.kernel.bin.path("ffmpeg-tmp")
55
+ ]
128
56
  }
129
57
 
130
- binaryFilename(tool) {
131
- return this.kernel.platform === "win32" ? `${tool}.exe` : tool
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
- binaryPath(tool) {
135
- return path.resolve(this.binDir(), this.binaryFilename(tool))
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
- env() {
139
- return {
140
- PATH: [this.binDir()],
141
- FFMPEG_PATH: this.binaryPath("ffmpeg"),
142
- FFPROBE_PATH: this.binaryPath("ffprobe")
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
- hasLegacyCondaFfmpeg() {
147
- const condaInstalled = this.kernel.bin.installed && this.kernel.bin.installed.conda
148
- return !!(condaInstalled && condaInstalled.has("ffmpeg"))
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 install(req, ondata) {
152
- const spec = this.artifact()
153
- const rootPath = this.rootPath()
154
- const tempDir = this.tempDir()
155
- const binDir = this.binDir()
156
- const downloads = []
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
- ondata({ raw: `preparing standalone ffmpeg ${spec.version} (${this.kernel.platform}/${this.kernel.arch})...\r\n` })
159
- await rimraf(tempDir)
160
- await rimraf(rootPath)
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
- try {
163
- await fs.promises.mkdir(tempDir, { recursive: true })
164
- await fs.promises.mkdir(binDir, { recursive: true })
165
-
166
- for (let index = 0; index < spec.archives.length; index++) {
167
- const archive = spec.archives[index]
168
- const filename = `${this.kernel.platform}-${this.kernel.arch}-${index}-${path.basename(new URL(archive.url).pathname)}`
169
- const archivePath = this.kernel.bin.path(filename)
170
- const extractDir = path.resolve(tempDir, `archive-${index}`)
171
-
172
- downloads.push(archivePath)
173
- await this.kernel.bin.download(archive.url, filename, ondata)
174
- await this.verifyChecksum(archivePath, archive.sha256)
175
-
176
- ondata({ raw: `extracting ${filename}...\r\n` })
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
- await this.writeManifest(spec)
194
- await this.selfTest(ondata)
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
- await fs.promises.access(this.binaryPath("ffmpeg"))
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("standalone ffmpeg installed check failed", error && error.message ? error.message : error)
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
- ondata({ raw: "cleaning up\r\n" })
226
- await rimraf(this.rootPath())
227
- await rimraf(this.tempDir())
228
- ondata({ raw: "finished cleaning up\r\n" })
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 binaries...\r\n" })
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 ensureLegacyCondaFfmpegRemoved(ondata) {
257
- await this.kernel.bin.refreshInstalled()
258
- if (!this.hasLegacyCondaFfmpeg()) {
259
- return
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
- ondata({ raw: "removing legacy conda ffmpeg from the base environment...\r\n" })
263
- await this.kernel.bin.exec({
264
- message: "conda remove -y ffmpeg"
265
- }, ondata)
266
- await this.kernel.bin.refreshInstalled()
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
- async verifyChecksum(filePath, expectedHash) {
273
- const actualHash = await this.sha256(filePath)
274
- if (actualHash.toLowerCase() !== expectedHash.toLowerCase()) {
275
- throw new Error(`Checksum mismatch for ${path.basename(filePath)}: expected ${expectedHash}, got ${actualHash}`)
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
- async writeManifest(spec) {
285
- const manifest = {
286
- version: spec.version,
287
- platform: this.kernel.platform,
288
- arch: this.kernel.arch,
289
- source: spec.source,
290
- installed_at: new Date().toISOString(),
291
- binaries: {
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
- async findFileByName(root, targetName) {
300
- const entries = await fs.promises.readdir(root, { withFileTypes: true })
301
- for (const entry of entries) {
302
- const fullPath = path.resolve(root, entry.name)
303
- if (entry.isDirectory()) {
304
- const found = await this.findFileByName(fullPath, targetName)
305
- if (found) {
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.2.1",
3
+ "version": "7.2.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {