pinokiod 7.2.4 → 7.2.5

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 +355 -25
  2. package/package.json +1 -1
@@ -1,13 +1,16 @@
1
+ const crypto = require("crypto")
1
2
  const fs = require("fs")
2
3
  const path = require("path")
3
4
  const { execFile } = require("child_process")
4
5
  const ParcelWatcher = require("@parcel/watcher")
5
6
  const semver = require("semver")
6
7
  const { rimraf } = require("rimraf")
8
+ const Util = require("../util")
7
9
 
8
10
  const RELEASE_VERSION = "8.0.1"
9
11
  const RELEASE_RANGE = ">=8.0.1 <8.1.0"
10
12
  const CONDA_SPEC = `ffmpeg=${RELEASE_VERSION}`
13
+ const CONDA_CHANNEL_FLAGS = "--override-channels -c conda-forge"
11
14
 
12
15
  const WINDOWS_GDK_PIXBUF_POST_LINK_NOOP = `@echo off
13
16
  rem Pinokio intentionally skips gdk-pixbuf loader cache generation for FFmpeg installs.
@@ -27,25 +30,36 @@ class Ffmpeg {
27
30
  FFMPEG_PATH: this.binaryPath("ffmpeg", activeKernel),
28
31
  FFPROBE_PATH: this.binaryPath("ffprobe", activeKernel)
29
32
  }
33
+ if (activeKernel.platform === "win32") {
34
+ env.PATH = [this.libraryDir(activeKernel)]
35
+ }
30
36
  if (activeKernel.platform === "linux") {
31
37
  env.LD_LIBRARY_PATH = [this.libraryDir(activeKernel)]
32
38
  }
33
39
  return env
34
40
  }
35
41
 
42
+ ffmpegPrefix(kernel = this.kernel) {
43
+ return kernel.bin.path("ffmpeg-env")
44
+ }
45
+
46
+ ffmpegPkgsDir(kernel = this.kernel) {
47
+ return kernel.bin.path("ffmpeg-pkgs")
48
+ }
49
+
36
50
  binaryPath(tool, kernel = this.kernel) {
37
51
  const filename = kernel.platform === "win32" ? `${tool}.exe` : tool
38
52
  if (kernel.platform === "win32") {
39
- return kernel.bin.path("miniconda", "Library", "bin", filename)
53
+ return path.resolve(this.ffmpegPrefix(kernel), "Library", "bin", filename)
40
54
  }
41
- return kernel.bin.path("miniconda", "bin", filename)
55
+ return path.resolve(this.ffmpegPrefix(kernel), "bin", filename)
42
56
  }
43
57
 
44
58
  libraryDir(kernel = this.kernel) {
45
59
  if (kernel.platform === "win32") {
46
- return kernel.bin.path("miniconda", "Library", "bin")
60
+ return path.resolve(this.ffmpegPrefix(kernel), "Library", "bin")
47
61
  }
48
- return kernel.bin.path("miniconda", "lib")
62
+ return path.resolve(this.ffmpegPrefix(kernel), "lib")
49
63
  }
50
64
 
51
65
  legacyStandalonePaths() {
@@ -59,9 +73,11 @@ class Ffmpeg {
59
73
  if (this.kernel.platform !== "darwin") {
60
74
  return
61
75
  }
62
- if (!this.isInstalledVersion()) {
76
+ if (!(await this.hasInstalledBinaryPaths())) {
77
+ await this.removeBaseActivationHooks()
63
78
  return
64
79
  }
80
+ await this.ensureBaseActivationHooks()
65
81
  await this.syncMacUvLibraryShims()
66
82
  await this.startMacUvLibraryWatcher()
67
83
  }
@@ -73,36 +89,53 @@ class Ffmpeg {
73
89
  } else {
74
90
  await this.installStandard(ondata)
75
91
  }
76
- await this.syncMacUvLibraryShims(ondata)
77
92
  await this.selfTest(ondata)
93
+ await this.ensureBaseActivationHooks()
94
+ await this.syncMacUvLibraryShims(ondata)
78
95
  }
79
96
 
80
97
  async installStandard(ondata) {
98
+ await this.resetInstallPrefix()
81
99
  await this.kernel.bin.exec({
100
+ env: {
101
+ CONDA_PKGS_DIRS: this.ffmpegPkgsDir()
102
+ },
82
103
  message: [
83
104
  "conda clean -y --all",
84
- `conda install -y -c conda-forge ${this.cmd()}`
105
+ `conda create -y -p "${this.ffmpegPrefix()}" ${CONDA_CHANNEL_FLAGS} ${this.cmd()}`
85
106
  ]
86
107
  }, ondata)
87
108
  }
88
109
 
89
110
  async installWindows(ondata) {
111
+ await this.resetInstallPrefix()
112
+ const env = {
113
+ CONDA_PKGS_DIRS: this.ffmpegPkgsDir()
114
+ }
115
+
90
116
  await this.kernel.bin.exec({
117
+ env,
91
118
  message: [
92
119
  "conda clean -y --all",
93
- `conda install -y --download-only -c conda-forge ${this.cmd()}`
120
+ `conda create -y --download-only -p "${this.ffmpegPrefix()}" ${CONDA_CHANNEL_FLAGS} ${this.cmd()}`
94
121
  ]
95
122
  }, ondata)
96
123
 
97
- await this.patchWindowsGdkPixbufPostLink(ondata)
124
+ await this.patchWindowsGdkPixbufPostLink(this.ffmpegPkgsDir(), ondata)
98
125
 
99
126
  await this.kernel.bin.exec({
100
- message: `conda install -y --offline -c conda-forge ${this.cmd()}`
127
+ env,
128
+ message: `conda create -y --offline -p "${this.ffmpegPrefix()}" ${CONDA_CHANNEL_FLAGS} ${this.cmd()}`
101
129
  }, ondata)
102
130
  }
103
131
 
104
- async patchWindowsGdkPixbufPostLink(ondata) {
105
- const pkgsDir = this.kernel.bin.path("miniconda", "pkgs")
132
+ async resetInstallPrefix() {
133
+ await rimraf(this.ffmpegPrefix())
134
+ await rimraf(this.ffmpegPkgsDir())
135
+ await fs.promises.mkdir(this.ffmpegPkgsDir(), { recursive: true })
136
+ }
137
+
138
+ async patchWindowsGdkPixbufPostLink(pkgsDir, ondata) {
106
139
  const entries = await fs.promises.readdir(pkgsDir, { withFileTypes: true })
107
140
  const packageDirs = entries
108
141
  .filter((entry) => entry.isDirectory() && /^gdk-pixbuf-/.test(entry.name))
@@ -113,17 +146,31 @@ class Ffmpeg {
113
146
  }
114
147
 
115
148
  let patchedCount = 0
149
+ let metadataCount = 0
116
150
  for (const packageDir of packageDirs) {
117
151
  const scripts = [
118
- path.resolve(packageDir, "Scripts", ".gdk-pixbuf-post-link.bat"),
119
- path.resolve(packageDir, "info", "recipe", "post-link.bat")
152
+ {
153
+ relativePath: "Scripts/.gdk-pixbuf-post-link.bat",
154
+ metadataRequired: true
155
+ },
156
+ {
157
+ relativePath: "info/recipe/post-link.bat",
158
+ metadataRequired: false
159
+ }
120
160
  ]
121
161
 
122
- for (const script of scripts) {
162
+ for (const { relativePath, metadataRequired } of scripts) {
163
+ const script = path.resolve(packageDir, ...relativePath.split("/"))
123
164
  try {
124
165
  await fs.promises.access(script)
125
166
  await fs.promises.writeFile(script, WINDOWS_GDK_PIXBUF_POST_LINK_NOOP)
126
167
  patchedCount += 1
168
+ const updatedMetadata = await this.updateCondaPathsJson(packageDir, relativePath, WINDOWS_GDK_PIXBUF_POST_LINK_NOOP)
169
+ if (updatedMetadata) {
170
+ metadataCount += 1
171
+ } else if (metadataRequired) {
172
+ throw new Error(`Patched ${relativePath} in ${packageDir}, but did not find a matching info/paths.json entry`)
173
+ }
127
174
  } catch (error) {
128
175
  if (error && error.code !== "ENOENT") {
129
176
  throw error
@@ -136,17 +183,62 @@ class Ffmpeg {
136
183
  throw new Error("Found gdk-pixbuf in the Conda cache, but did not find any post-link scripts to patch")
137
184
  }
138
185
 
139
- ondata({ raw: `patched ${patchedCount} gdk-pixbuf post-link script(s) in the Conda cache...\r\n` })
186
+ if (ondata) {
187
+ ondata({
188
+ raw: `patched ${patchedCount} gdk-pixbuf post-link script(s) in the Conda cache and refreshed ${metadataCount} paths.json entr${metadataCount === 1 ? "y" : "ies"}...\r\n`
189
+ })
190
+ }
140
191
  }
141
192
 
142
- async installed() {
193
+ async updateCondaPathsJson(packageDir, relativePath, contents) {
194
+ const pathsJsonPath = path.resolve(packageDir, "info", "paths.json")
195
+ let pathsJson
196
+
143
197
  try {
144
- if (!this.isInstalledVersion()) {
198
+ pathsJson = JSON.parse(await fs.promises.readFile(pathsJsonPath, "utf8"))
199
+ } catch (error) {
200
+ if (error && error.code === "ENOENT") {
145
201
  return false
146
202
  }
203
+ throw error
204
+ }
147
205
 
206
+ if (!pathsJson || !Array.isArray(pathsJson.paths)) {
207
+ return false
208
+ }
209
+
210
+ const normalizedPath = relativePath.replace(/\\/g, "/")
211
+ const entry = pathsJson.paths.find((item) => item && item._path === normalizedPath)
212
+ if (!entry) {
213
+ return false
214
+ }
215
+
216
+ const buffer = Buffer.isBuffer(contents) ? contents : Buffer.from(String(contents), "utf8")
217
+ entry.sha256 = crypto.createHash("sha256").update(buffer).digest("hex")
218
+ entry.size_in_bytes = buffer.length
219
+
220
+ await fs.promises.writeFile(pathsJsonPath, `${JSON.stringify(pathsJson, null, 2)}\n`)
221
+ return true
222
+ }
223
+
224
+ async hasInstalledBinaryPaths() {
225
+ try {
148
226
  await fs.promises.access(this.binaryPath("ffmpeg"))
149
227
  await fs.promises.access(this.binaryPath("ffprobe"))
228
+ return true
229
+ } catch (error) {
230
+ return false
231
+ }
232
+ }
233
+
234
+ async installed() {
235
+ try {
236
+ if (!(await this.hasInstalledBinaryPaths())) {
237
+ await this.removeBaseActivationHooks()
238
+ return false
239
+ }
240
+
241
+ await this.ensureBaseActivationHooks()
150
242
  await this.syncMacUvLibraryShims()
151
243
  await this.startMacUvLibraryWatcher()
152
244
  await this.selfTest()
@@ -160,17 +252,255 @@ class Ffmpeg {
160
252
  async uninstall(req, ondata) {
161
253
  await this.stopMacUvLibraryWatcher()
162
254
  await this.removeMacUvLibraryShims(ondata)
163
- await this.kernel.bin.exec({
164
- message: "conda remove -y ffmpeg"
165
- }, ondata)
255
+ await this.removeBaseActivationHooks()
256
+ const prefix = this.ffmpegPrefix()
257
+ const exists = await fs.promises.access(prefix).then(() => true).catch(() => false)
258
+ if (exists) {
259
+ try {
260
+ await this.kernel.bin.exec({
261
+ env: {
262
+ CONDA_PKGS_DIRS: this.ffmpegPkgsDir()
263
+ },
264
+ message: `conda remove -y -p "${prefix}" --all`
265
+ }, ondata)
266
+ } catch (error) {
267
+ await rimraf(prefix)
268
+ }
269
+ }
270
+ await rimraf(prefix)
271
+ await rimraf(this.ffmpegPkgsDir())
166
272
  await this.cleanupLegacyStandalone(ondata)
167
273
  }
168
274
 
169
- isInstalledVersion() {
170
- if (!this.kernel.bin.installed?.conda?.has("ffmpeg")) {
171
- return false
275
+ activationDirs() {
276
+ return {
277
+ activate: this.kernel.bin.path("miniconda", "etc", "conda", "activate.d"),
278
+ deactivate: this.kernel.bin.path("miniconda", "etc", "conda", "deactivate.d")
172
279
  }
173
- return this.kernel.bin.installed?.conda_versions?.ffmpeg === RELEASE_VERSION
280
+ }
281
+
282
+ activationHookFiles() {
283
+ const { activate, deactivate } = this.activationDirs()
284
+ const files = [
285
+ {
286
+ path: path.resolve(activate, "zz_pinokio_ffmpeg.sh"),
287
+ content: this.posixActivateSh(this.kernel.platform === "win32")
288
+ },
289
+ {
290
+ path: path.resolve(deactivate, "zz_pinokio_ffmpeg.sh"),
291
+ content: this.posixDeactivateSh(this.kernel.platform === "win32")
292
+ }
293
+ ]
294
+
295
+ if (this.kernel.platform !== "win32") {
296
+ return files
297
+ }
298
+
299
+ return files.concat([
300
+ {
301
+ path: path.resolve(activate, "zz_pinokio_ffmpeg.bat"),
302
+ content: this.windowsActivateBat()
303
+ },
304
+ {
305
+ path: path.resolve(deactivate, "zz_pinokio_ffmpeg.bat"),
306
+ content: this.windowsDeactivateBat()
307
+ },
308
+ {
309
+ path: path.resolve(activate, "zz_pinokio_ffmpeg.ps1"),
310
+ content: this.windowsActivatePs1()
311
+ },
312
+ {
313
+ path: path.resolve(deactivate, "zz_pinokio_ffmpeg.ps1"),
314
+ content: this.windowsDeactivatePs1()
315
+ }
316
+ ])
317
+ }
318
+
319
+ async ensureBaseActivationHooks() {
320
+ const dirs = this.activationDirs()
321
+ await fs.promises.mkdir(dirs.activate, { recursive: true }).catch(() => {})
322
+ await fs.promises.mkdir(dirs.deactivate, { recursive: true }).catch(() => {})
323
+ for (const file of this.activationHookFiles()) {
324
+ await fs.promises.writeFile(file.path, file.content)
325
+ }
326
+ }
327
+
328
+ async removeBaseActivationHooks() {
329
+ for (const file of this.activationHookFiles()) {
330
+ await fs.promises.rm(file.path, { force: true }).catch(() => {})
331
+ }
332
+ }
333
+
334
+ windowsActivateBat() {
335
+ const prefix = this.ffmpegPrefix()
336
+ const runtime = this.libraryDir()
337
+ return `@echo off
338
+ set "PINOKIO_FFMPEG_PREFIX=${prefix}"
339
+ set "PINOKIO_FFMPEG_RUNTIME=${runtime}"
340
+ set "FFMPEG_PATH=%PINOKIO_FFMPEG_RUNTIME%\\ffmpeg.exe"
341
+ set "FFPROBE_PATH=%PINOKIO_FFMPEG_RUNTIME%\\ffprobe.exe"
342
+ call :pinokio_ffmpeg_remove_from_path "%PINOKIO_FFMPEG_RUNTIME%"
343
+ set "PATH=%PINOKIO_FFMPEG_RUNTIME%;%PATH%"
344
+ goto :eof
345
+
346
+ :pinokio_ffmpeg_remove_from_path
347
+ setlocal EnableDelayedExpansion
348
+ set "_pinokio_target=%~1"
349
+ set "_pinokio_path=;%PATH%;"
350
+ set "_pinokio_path=!_pinokio_path:;%_pinokio_target%;=;!"
351
+ set "_pinokio_path=!_pinokio_path:;%_pinokio_target%\\;=;!"
352
+ if "!_pinokio_path:~0,1!"==";" set "_pinokio_path=!_pinokio_path:~1!"
353
+ if "!_pinokio_path:~-1!"==";" set "_pinokio_path=!_pinokio_path:~0,-1!"
354
+ endlocal & set "PATH=%_pinokio_path%"
355
+ exit /b 0
356
+ `
357
+ }
358
+
359
+ windowsDeactivateBat() {
360
+ return `@echo off
361
+ if defined PINOKIO_FFMPEG_RUNTIME call :pinokio_ffmpeg_remove_from_path "%PINOKIO_FFMPEG_RUNTIME%"
362
+ set "FFMPEG_PATH="
363
+ set "FFPROBE_PATH="
364
+ set "PINOKIO_FFMPEG_PREFIX="
365
+ set "PINOKIO_FFMPEG_RUNTIME="
366
+ goto :eof
367
+
368
+ :pinokio_ffmpeg_remove_from_path
369
+ setlocal EnableDelayedExpansion
370
+ set "_pinokio_target=%~1"
371
+ set "_pinokio_path=;%PATH%;"
372
+ set "_pinokio_path=!_pinokio_path:;%_pinokio_target%;=;!"
373
+ set "_pinokio_path=!_pinokio_path:;%_pinokio_target%\\;=;!"
374
+ if "!_pinokio_path:~0,1!"==";" set "_pinokio_path=!_pinokio_path:~1!"
375
+ if "!_pinokio_path:~-1!"==";" set "_pinokio_path=!_pinokio_path:~0,-1!"
376
+ endlocal & set "PATH=%_pinokio_path%"
377
+ exit /b 0
378
+ `
379
+ }
380
+
381
+ windowsActivatePs1() {
382
+ const prefix = this.ffmpegPrefix().replace(/\\/g, "\\\\")
383
+ const runtime = this.libraryDir().replace(/\\/g, "\\\\")
384
+ return `$Env:PINOKIO_FFMPEG_PREFIX = "${prefix}"
385
+ $Env:PINOKIO_FFMPEG_RUNTIME = "${runtime}"
386
+ $Env:FFMPEG_PATH = Join-Path $Env:PINOKIO_FFMPEG_RUNTIME "ffmpeg.exe"
387
+ $Env:FFPROBE_PATH = Join-Path $Env:PINOKIO_FFMPEG_RUNTIME "ffprobe.exe"
388
+ $pinokioParts = @()
389
+ if ($Env:Path) {
390
+ $pinokioParts = @($Env:Path -split ';' | Where-Object { $_ -and $_ -ne $Env:PINOKIO_FFMPEG_RUNTIME })
391
+ }
392
+ $Env:Path = (@($Env:PINOKIO_FFMPEG_RUNTIME) + $pinokioParts) -join ';'
393
+ `
394
+ }
395
+
396
+ windowsDeactivatePs1() {
397
+ return `if ($Env:PINOKIO_FFMPEG_RUNTIME) {
398
+ $pinokioParts = @()
399
+ if ($Env:Path) {
400
+ $pinokioParts = @($Env:Path -split ';' | Where-Object { $_ -and $_ -ne $Env:PINOKIO_FFMPEG_RUNTIME })
401
+ }
402
+ $Env:Path = $pinokioParts -join ';'
403
+ }
404
+ Remove-Item -Path Env:\\FFMPEG_PATH -ErrorAction SilentlyContinue
405
+ Remove-Item -Path Env:\\FFPROBE_PATH -ErrorAction SilentlyContinue
406
+ Remove-Item -Path Env:\\PINOKIO_FFMPEG_PREFIX -ErrorAction SilentlyContinue
407
+ Remove-Item -Path Env:\\PINOKIO_FFMPEG_RUNTIME -ErrorAction SilentlyContinue
408
+ `
409
+ }
410
+
411
+ posixActivateSh(forceWindowsPaths = false) {
412
+ const prefix = forceWindowsPaths ? Util.p2u(this.ffmpegPrefix()) : this.ffmpegPrefix()
413
+ const binDir = forceWindowsPaths ? Util.p2u(path.resolve(this.ffmpegPrefix(), "Library", "bin")) : path.resolve(this.ffmpegPrefix(), "bin")
414
+ const libDir = forceWindowsPaths ? "" : this.libraryDir()
415
+ return `pinokio_ffmpeg_prepend_path() {
416
+ local target="$1"
417
+ local current="\${2-}"
418
+ local result=""
419
+ local part
420
+ local old_ifs="$IFS"
421
+ IFS=':'
422
+ for part in $current; do
423
+ [ -n "$part" ] || continue
424
+ [ "$part" = "$target" ] && continue
425
+ if [ -n "$result" ]; then
426
+ result="$result:$part"
427
+ else
428
+ result="$part"
429
+ fi
430
+ done
431
+ IFS="$old_ifs"
432
+ if [ -n "$result" ]; then
433
+ printf '%s:%s' "$target" "$result"
434
+ else
435
+ printf '%s' "$target"
436
+ fi
437
+ }
438
+ pinokio_ffmpeg_remove_path() {
439
+ local target="$1"
440
+ local current="\${2-}"
441
+ local result=""
442
+ local part
443
+ local old_ifs="$IFS"
444
+ IFS=':'
445
+ for part in $current; do
446
+ [ -n "$part" ] || continue
447
+ [ "$part" = "$target" ] && continue
448
+ if [ -n "$result" ]; then
449
+ result="$result:$part"
450
+ else
451
+ result="$part"
452
+ fi
453
+ done
454
+ IFS="$old_ifs"
455
+ printf '%s' "$result"
456
+ }
457
+ export PINOKIO_FFMPEG_PREFIX="${prefix}"
458
+ export PINOKIO_FFMPEG_BIN="${binDir}"
459
+ export FFMPEG_PATH="$PINOKIO_FFMPEG_BIN/${forceWindowsPaths ? "ffmpeg.exe" : "ffmpeg"}"
460
+ export FFPROBE_PATH="$PINOKIO_FFMPEG_BIN/${forceWindowsPaths ? "ffprobe.exe" : "ffprobe"}"
461
+ export PATH="$(pinokio_ffmpeg_prepend_path "$PINOKIO_FFMPEG_BIN" "$PATH")"
462
+ ${forceWindowsPaths ? "" : `if [ "$(uname -s)" = "Linux" ]; then
463
+ export LD_LIBRARY_PATH="$(pinokio_ffmpeg_prepend_path "${libDir}" "\${LD_LIBRARY_PATH-}")"
464
+ fi
465
+ `}
466
+ unset -f pinokio_ffmpeg_prepend_path
467
+ unset -f pinokio_ffmpeg_remove_path
468
+ `
469
+ }
470
+
471
+ posixDeactivateSh(forceWindowsPaths = false) {
472
+ return `pinokio_ffmpeg_remove_path() {
473
+ local target="$1"
474
+ local current="\${2-}"
475
+ local result=""
476
+ local part
477
+ local old_ifs="$IFS"
478
+ IFS=':'
479
+ for part in $current; do
480
+ [ -n "$part" ] || continue
481
+ [ "$part" = "$target" ] && continue
482
+ if [ -n "$result" ]; then
483
+ result="$result:$part"
484
+ else
485
+ result="$part"
486
+ fi
487
+ done
488
+ IFS="$old_ifs"
489
+ printf '%s' "$result"
490
+ }
491
+ if [ -n "\${PINOKIO_FFMPEG_BIN-}" ]; then
492
+ export PATH="$(pinokio_ffmpeg_remove_path "$PINOKIO_FFMPEG_BIN" "$PATH")"
493
+ fi
494
+ ${forceWindowsPaths ? "" : `if [ "$(uname -s)" = "Linux" ] && [ -n "\${PINOKIO_FFMPEG_PREFIX-}" ]; then
495
+ export LD_LIBRARY_PATH="$(pinokio_ffmpeg_remove_path "${this.libraryDir()}" "\${LD_LIBRARY_PATH-}")"
496
+ fi
497
+ `}
498
+ unset FFMPEG_PATH
499
+ unset FFPROBE_PATH
500
+ unset PINOKIO_FFMPEG_PREFIX
501
+ unset PINOKIO_FFMPEG_BIN
502
+ unset -f pinokio_ffmpeg_remove_path
503
+ `
174
504
  }
175
505
 
176
506
  async cleanupLegacyStandalone(ondata) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.2.4",
3
+ "version": "7.2.5",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {