@twick/browser-render 0.15.8 → 0.15.10
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/README.md +41 -0
- package/dist/index.d.mts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +530 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +529 -58
- package/dist/index.mjs.map +1 -1
- package/dist/vite-plugin-ffmpeg.d.mts +18 -0
- package/dist/vite-plugin-ffmpeg.d.ts +18 -0
- package/dist/vite-plugin-ffmpeg.js +156 -0
- package/dist/vite-plugin-ffmpeg.js.map +1 -0
- package/dist/vite-plugin-ffmpeg.mjs +120 -0
- package/dist/vite-plugin-ffmpeg.mjs.map +1 -0
- package/package.json +14 -5
- package/scripts/copy-public-assets.js +124 -0
- package/scripts/copy-wasm.js +19 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// src/vite-plugin-ffmpeg.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
function twickBrowserRenderPlugin() {
|
|
6
|
+
let publicDir;
|
|
7
|
+
let root;
|
|
8
|
+
function copyIfNewer(src, dest) {
|
|
9
|
+
if (!fs.existsSync(src)) return false;
|
|
10
|
+
if (!fs.existsSync(dest) || fs.statSync(src).mtime > fs.statSync(dest).mtime) {
|
|
11
|
+
fs.copyFileSync(src, dest);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
name: "twick-browser-render-ffmpeg",
|
|
18
|
+
configResolved(config) {
|
|
19
|
+
root = config.root;
|
|
20
|
+
publicDir = config.publicDir || path.join(root, "public");
|
|
21
|
+
},
|
|
22
|
+
buildStart() {
|
|
23
|
+
const copied = [];
|
|
24
|
+
const ffmpegCorePathBases = [
|
|
25
|
+
path.join(root, "node_modules", "@ffmpeg", "core"),
|
|
26
|
+
path.join(root, "..", "..", "node_modules", "@ffmpeg", "core"),
|
|
27
|
+
path.join(root, "..", "node_modules", "@ffmpeg", "core"),
|
|
28
|
+
path.join(root, "..", "..", "..", "node_modules", "@ffmpeg", "core")
|
|
29
|
+
];
|
|
30
|
+
const ffmpegCorePaths = [];
|
|
31
|
+
for (const base of ffmpegCorePathBases) {
|
|
32
|
+
ffmpegCorePaths.push(path.join(base, "dist", "esm"));
|
|
33
|
+
ffmpegCorePaths.push(path.join(base, "dist"));
|
|
34
|
+
}
|
|
35
|
+
let ffmpegSource = null;
|
|
36
|
+
for (const candidate of ffmpegCorePaths) {
|
|
37
|
+
if (fs.existsSync(path.join(candidate, "ffmpeg-core.js")) && fs.existsSync(path.join(candidate, "ffmpeg-core.wasm"))) {
|
|
38
|
+
ffmpegSource = candidate;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!ffmpegSource) {
|
|
43
|
+
const pluginDir = path.dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
const browserRenderRoot = path.dirname(pluginDir);
|
|
45
|
+
const coreBases = [
|
|
46
|
+
path.join(browserRenderRoot, "..", "@ffmpeg", "core"),
|
|
47
|
+
path.join(browserRenderRoot, "..", "node_modules", "@ffmpeg", "core"),
|
|
48
|
+
path.join(browserRenderRoot, "..", "..", "node_modules", "@ffmpeg", "core")
|
|
49
|
+
];
|
|
50
|
+
for (const base of coreBases) {
|
|
51
|
+
for (const sub of ["dist/esm", "dist"]) {
|
|
52
|
+
const coreDist = path.join(base, sub);
|
|
53
|
+
if (fs.existsSync(path.join(coreDist, "ffmpeg-core.js")) && fs.existsSync(path.join(coreDist, "ffmpeg-core.wasm"))) {
|
|
54
|
+
ffmpegSource = coreDist;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (ffmpegSource) break;
|
|
59
|
+
}
|
|
60
|
+
if (!ffmpegSource && fs.existsSync(path.join(root, "..", "..", "node_modules", ".pnpm"))) {
|
|
61
|
+
const pnpmDir = path.join(root, "..", "..", "node_modules", ".pnpm");
|
|
62
|
+
const entries = fs.readdirSync(pnpmDir, { withFileTypes: true });
|
|
63
|
+
for (const e of entries) {
|
|
64
|
+
if (e.isDirectory() && e.name.startsWith("@ffmpeg+core@")) {
|
|
65
|
+
const coreDist = path.join(pnpmDir, e.name, "node_modules", "@ffmpeg", "core", "dist", "esm");
|
|
66
|
+
if (fs.existsSync(path.join(coreDist, "ffmpeg-core.js")) && fs.existsSync(path.join(coreDist, "ffmpeg-core.wasm"))) {
|
|
67
|
+
ffmpegSource = coreDist;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
const coreDistLegacy = path.join(pnpmDir, e.name, "node_modules", "@ffmpeg", "core", "dist");
|
|
71
|
+
if (fs.existsSync(path.join(coreDistLegacy, "ffmpeg-core.js")) && fs.existsSync(path.join(coreDistLegacy, "ffmpeg-core.wasm"))) {
|
|
72
|
+
ffmpegSource = coreDistLegacy;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (ffmpegSource) {
|
|
80
|
+
const ffmpegDir = path.join(publicDir, "ffmpeg");
|
|
81
|
+
if (!fs.existsSync(ffmpegDir)) fs.mkdirSync(ffmpegDir, { recursive: true });
|
|
82
|
+
if (copyIfNewer(path.join(ffmpegSource, "ffmpeg-core.js"), path.join(ffmpegDir, "ffmpeg-core.js"))) copied.push("ffmpeg-core.js");
|
|
83
|
+
if (copyIfNewer(path.join(ffmpegSource, "ffmpeg-core.wasm"), path.join(ffmpegDir, "ffmpeg-core.wasm"))) copied.push("ffmpeg-core.wasm");
|
|
84
|
+
} else {
|
|
85
|
+
console.warn("[twick-browser-render] @ffmpeg/core not found; FFmpeg features may not work.");
|
|
86
|
+
}
|
|
87
|
+
const mp4WasmCandidates = [
|
|
88
|
+
path.join(root, "node_modules", "mp4-wasm", "dist", "mp4-wasm.wasm"),
|
|
89
|
+
path.join(root, "node_modules", "mp4-wasm", "build", "mp4.wasm"),
|
|
90
|
+
path.join(root, "node_modules", "@twick", "browser-render", "public", "mp4-wasm.wasm"),
|
|
91
|
+
path.join(root, "..", "node_modules", "@twick", "browser-render", "public", "mp4-wasm.wasm")
|
|
92
|
+
];
|
|
93
|
+
const mp4Dest = path.join(publicDir, "mp4-wasm.wasm");
|
|
94
|
+
for (const src of mp4WasmCandidates) {
|
|
95
|
+
if (copyIfNewer(src, mp4Dest)) {
|
|
96
|
+
copied.push("mp4-wasm.wasm");
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const audioWorkerCandidates = [
|
|
101
|
+
path.join(root, "node_modules", "@twick", "browser-render", "public", "audio-worker.js"),
|
|
102
|
+
path.join(root, "..", "node_modules", "@twick", "browser-render", "public", "audio-worker.js")
|
|
103
|
+
];
|
|
104
|
+
const audioDest = path.join(publicDir, "audio-worker.js");
|
|
105
|
+
for (const src of audioWorkerCandidates) {
|
|
106
|
+
if (copyIfNewer(src, audioDest)) {
|
|
107
|
+
copied.push("audio-worker.js");
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (copied.length > 0) {
|
|
112
|
+
console.log(`[twick-browser-render] Copied to public: ${copied.join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
twickBrowserRenderPlugin
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=vite-plugin-ffmpeg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite-plugin-ffmpeg.ts"],"sourcesContent":["import type { Plugin, ResolvedConfig } from 'vite';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\n/**\n * Vite plugin that automatically copies browser-render assets from node_modules\n * to the project's public directory:\n * - public/ffmpeg/ffmpeg-core.js, ffmpeg-core.wasm (from @ffmpeg/core)\n * - public/mp4-wasm.wasm (from mp4-wasm or @twick/browser-render)\n * - public/audio-worker.js (from @twick/browser-render)\n *\n * Usage in vite.config.ts:\n * ```ts\n * import { twickBrowserRenderPlugin } from '@twick/browser-render/vite-plugin-ffmpeg';\n * export default defineConfig({ plugins: [twickBrowserRenderPlugin(), ...] });\n * ```\n */\nexport function twickBrowserRenderPlugin(): Plugin {\n let publicDir: string;\n let root: string;\n\n function copyIfNewer(src: string, dest: string): boolean {\n if (!fs.existsSync(src)) return false;\n if (!fs.existsSync(dest) || fs.statSync(src).mtime > fs.statSync(dest).mtime) {\n fs.copyFileSync(src, dest);\n return true;\n }\n return false;\n }\n\n return {\n name: 'twick-browser-render-ffmpeg',\n\n configResolved(config: ResolvedConfig) {\n root = config.root;\n publicDir = config.publicDir || path.join(root, 'public');\n },\n\n buildStart() {\n const copied: string[] = [];\n\n // 1. Copy FFmpeg core files to public/ffmpeg/\n const ffmpegCorePathBases = [\n path.join(root, 'node_modules', '@ffmpeg', 'core'),\n path.join(root, '..', '..', 'node_modules', '@ffmpeg', 'core'),\n path.join(root, '..', 'node_modules', '@ffmpeg', 'core'),\n path.join(root, '..', '..', '..', 'node_modules', '@ffmpeg', 'core'),\n ];\n const ffmpegCorePaths: string[] = [];\n for (const base of ffmpegCorePathBases) {\n ffmpegCorePaths.push(path.join(base, 'dist', 'esm')); // @ffmpeg/core 0.12+ uses dist/esm\n ffmpegCorePaths.push(path.join(base, 'dist'));\n }\n let ffmpegSource: string | null = null;\n for (const candidate of ffmpegCorePaths) {\n if (fs.existsSync(path.join(candidate, 'ffmpeg-core.js')) && fs.existsSync(path.join(candidate, 'ffmpeg-core.wasm'))) {\n ffmpegSource = candidate;\n break;\n }\n }\n // Resolve via plugin's package (browser-render depends on @ffmpeg/core — so it's available next to it or in pnpm store)\n if (!ffmpegSource) {\n const pluginDir = path.dirname(fileURLToPath(import.meta.url));\n const browserRenderRoot = path.dirname(pluginDir);\n const coreBases = [\n path.join(browserRenderRoot, '..', '@ffmpeg', 'core'),\n path.join(browserRenderRoot, '..', 'node_modules', '@ffmpeg', 'core'),\n path.join(browserRenderRoot, '..', '..', 'node_modules', '@ffmpeg', 'core'),\n ];\n for (const base of coreBases) {\n for (const sub of ['dist/esm', 'dist']) {\n const coreDist = path.join(base, sub);\n if (fs.existsSync(path.join(coreDist, 'ffmpeg-core.js')) && fs.existsSync(path.join(coreDist, 'ffmpeg-core.wasm'))) {\n ffmpegSource = coreDist;\n break;\n }\n }\n if (ffmpegSource) break;\n }\n // pnpm workspace: @ffmpeg/core lives in .pnpm store, not in workspace root node_modules\n if (!ffmpegSource && fs.existsSync(path.join(root, '..', '..', 'node_modules', '.pnpm'))) {\n const pnpmDir = path.join(root, '..', '..', 'node_modules', '.pnpm');\n const entries = fs.readdirSync(pnpmDir, { withFileTypes: true });\n for (const e of entries) {\n if (e.isDirectory() && e.name.startsWith('@ffmpeg+core@')) {\n const coreDist = path.join(pnpmDir, e.name, 'node_modules', '@ffmpeg', 'core', 'dist', 'esm');\n if (fs.existsSync(path.join(coreDist, 'ffmpeg-core.js')) && fs.existsSync(path.join(coreDist, 'ffmpeg-core.wasm'))) {\n ffmpegSource = coreDist;\n break;\n }\n const coreDistLegacy = path.join(pnpmDir, e.name, 'node_modules', '@ffmpeg', 'core', 'dist');\n if (fs.existsSync(path.join(coreDistLegacy, 'ffmpeg-core.js')) && fs.existsSync(path.join(coreDistLegacy, 'ffmpeg-core.wasm'))) {\n ffmpegSource = coreDistLegacy;\n break;\n }\n }\n }\n }\n }\n if (ffmpegSource) {\n const ffmpegDir = path.join(publicDir, 'ffmpeg');\n if (!fs.existsSync(ffmpegDir)) fs.mkdirSync(ffmpegDir, { recursive: true });\n if (copyIfNewer(path.join(ffmpegSource, 'ffmpeg-core.js'), path.join(ffmpegDir, 'ffmpeg-core.js'))) copied.push('ffmpeg-core.js');\n if (copyIfNewer(path.join(ffmpegSource, 'ffmpeg-core.wasm'), path.join(ffmpegDir, 'ffmpeg-core.wasm'))) copied.push('ffmpeg-core.wasm');\n } else {\n console.warn('[twick-browser-render] @ffmpeg/core not found; FFmpeg features may not work.');\n }\n\n // 2. Copy mp4-wasm.wasm to public/\n const mp4WasmCandidates = [\n path.join(root, 'node_modules', 'mp4-wasm', 'dist', 'mp4-wasm.wasm'),\n path.join(root, 'node_modules', 'mp4-wasm', 'build', 'mp4.wasm'),\n path.join(root, 'node_modules', '@twick', 'browser-render', 'public', 'mp4-wasm.wasm'),\n path.join(root, '..', 'node_modules', '@twick', 'browser-render', 'public', 'mp4-wasm.wasm'),\n ];\n const mp4Dest = path.join(publicDir, 'mp4-wasm.wasm');\n for (const src of mp4WasmCandidates) {\n if (copyIfNewer(src, mp4Dest)) {\n copied.push('mp4-wasm.wasm');\n break;\n }\n }\n\n // 3. Copy audio-worker.js from @twick/browser-render/public\n const audioWorkerCandidates = [\n path.join(root, 'node_modules', '@twick', 'browser-render', 'public', 'audio-worker.js'),\n path.join(root, '..', 'node_modules', '@twick', 'browser-render', 'public', 'audio-worker.js'),\n ];\n const audioDest = path.join(publicDir, 'audio-worker.js');\n for (const src of audioWorkerCandidates) {\n if (copyIfNewer(src, audioDest)) {\n copied.push('audio-worker.js');\n break;\n }\n }\n\n if (copied.length > 0) {\n console.log(`[twick-browser-render] Copied to public: ${copied.join(', ')}`);\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twick/browser-render",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.10",
|
|
4
4
|
"license": "https://github.com/ncounterspecialist/twick/blob/main/LICENSE.md",
|
|
5
5
|
"description": "Browser-native video rendering for Twick using WebCodecs API",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,11 +11,17 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.mjs",
|
|
13
13
|
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./vite-plugin-ffmpeg": {
|
|
16
|
+
"types": "./dist/vite-plugin-ffmpeg.d.ts",
|
|
17
|
+
"import": "./dist/vite-plugin-ffmpeg.mjs",
|
|
18
|
+
"require": "./dist/vite-plugin-ffmpeg.js"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
17
22
|
"dist",
|
|
18
|
-
"public"
|
|
23
|
+
"public",
|
|
24
|
+
"scripts"
|
|
19
25
|
],
|
|
20
26
|
"scripts": {
|
|
21
27
|
"build": "tsup && node scripts/copy-wasm.js",
|
|
@@ -28,8 +34,10 @@
|
|
|
28
34
|
"access": "public"
|
|
29
35
|
},
|
|
30
36
|
"dependencies": {
|
|
31
|
-
"@twick/core": "^0.15.
|
|
32
|
-
"@twick/
|
|
37
|
+
"@twick/core": "^0.15.10",
|
|
38
|
+
"@twick/media-utils": "0.15.10",
|
|
39
|
+
"@twick/visualizer": "0.15.10",
|
|
40
|
+
"mediabunny": "^1.31.0",
|
|
33
41
|
"mp4-wasm": "^1.0.6",
|
|
34
42
|
"mp4box": "^0.5.2",
|
|
35
43
|
"@ffmpeg/ffmpeg": "^0.12.10",
|
|
@@ -52,7 +60,8 @@
|
|
|
52
60
|
"tsx": "^4.7.0",
|
|
53
61
|
"typescript": "5.4.2",
|
|
54
62
|
"typedoc": "^0.25.8",
|
|
55
|
-
"typedoc-plugin-markdown": "^3.17.1"
|
|
63
|
+
"typedoc-plugin-markdown": "^3.17.1",
|
|
64
|
+
"vite": "^6.0.0"
|
|
56
65
|
},
|
|
57
66
|
"engines": {
|
|
58
67
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copy browser-render public assets (FFmpeg core, mp4-wasm.wasm, audio-worker.js)
|
|
4
|
+
* to your app's public directory. Use this for Create React App or any non-Vite setup.
|
|
5
|
+
*
|
|
6
|
+
* Usage (from your app directory):
|
|
7
|
+
* node node_modules/@twick/browser-render/scripts/copy-public-assets.js
|
|
8
|
+
* # or with custom output dir:
|
|
9
|
+
* node node_modules/@twick/browser-render/scripts/copy-public-assets.js ./public
|
|
10
|
+
*
|
|
11
|
+
* Then add to package.json:
|
|
12
|
+
* "prestart": "node node_modules/@twick/browser-render/scripts/copy-public-assets.js",
|
|
13
|
+
* "prebuild": "node node_modules/@twick/browser-render/scripts/copy-public-assets.js"
|
|
14
|
+
*/
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const targetDir = path.resolve(process.cwd(), process.argv[2] || 'public');
|
|
19
|
+
const appRoot = process.cwd();
|
|
20
|
+
const scriptDir = __dirname;
|
|
21
|
+
const browserRenderRoot = path.resolve(scriptDir, '..');
|
|
22
|
+
|
|
23
|
+
function copyIfNewer(src, dest) {
|
|
24
|
+
if (!fs.existsSync(src)) return false;
|
|
25
|
+
const destDir = path.dirname(dest);
|
|
26
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
27
|
+
if (!fs.existsSync(dest) || fs.statSync(src).mtime > fs.statSync(dest).mtime) {
|
|
28
|
+
fs.copyFileSync(src, dest);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const copied = [];
|
|
35
|
+
|
|
36
|
+
// Resolve paths: when run from app (e.g. examples-cra), node_modules is app/node_modules
|
|
37
|
+
// When run from workspace, app might be packages/examples-cra
|
|
38
|
+
const nm = path.join(appRoot, 'node_modules');
|
|
39
|
+
const nmParent = path.join(appRoot, '..', 'node_modules');
|
|
40
|
+
const nmGrand = path.join(appRoot, '..', '..', 'node_modules');
|
|
41
|
+
|
|
42
|
+
// 1. FFmpeg core -> public/ffmpeg/
|
|
43
|
+
let ffmpegSource = null;
|
|
44
|
+
const ffmpegBases = [
|
|
45
|
+
path.join(nm, '@ffmpeg', 'core'),
|
|
46
|
+
path.join(nmGrand, '@ffmpeg', 'core'),
|
|
47
|
+
path.join(nmParent, '@ffmpeg', 'core'),
|
|
48
|
+
path.join(nmGrand, '..', 'node_modules', '@ffmpeg', 'core'),
|
|
49
|
+
];
|
|
50
|
+
for (const base of ffmpegBases) {
|
|
51
|
+
for (const sub of ['dist/esm', 'dist']) {
|
|
52
|
+
const d = path.join(base, sub);
|
|
53
|
+
if (fs.existsSync(path.join(d, 'ffmpeg-core.js')) && fs.existsSync(path.join(d, 'ffmpeg-core.wasm'))) {
|
|
54
|
+
ffmpegSource = d;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (ffmpegSource) break;
|
|
59
|
+
}
|
|
60
|
+
// pnpm store
|
|
61
|
+
if (!ffmpegSource && fs.existsSync(path.join(nmGrand, '.pnpm'))) {
|
|
62
|
+
const pnpmDir = path.join(nmGrand, '.pnpm');
|
|
63
|
+
try {
|
|
64
|
+
const entries = fs.readdirSync(pnpmDir, { withFileTypes: true });
|
|
65
|
+
for (const e of entries) {
|
|
66
|
+
if (e.isDirectory() && e.name.startsWith('@ffmpeg+core@')) {
|
|
67
|
+
const d = path.join(pnpmDir, e.name, 'node_modules', '@ffmpeg', 'core', 'dist', 'esm');
|
|
68
|
+
if (fs.existsSync(path.join(d, 'ffmpeg-core.js')) && fs.existsSync(path.join(d, 'ffmpeg-core.wasm'))) {
|
|
69
|
+
ffmpegSource = d;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch (_) {}
|
|
75
|
+
}
|
|
76
|
+
// From browser-render's own node_modules (when script runs from browser-render package)
|
|
77
|
+
if (!ffmpegSource) {
|
|
78
|
+
const brNm = path.join(browserRenderRoot, 'node_modules', '@ffmpeg', 'core');
|
|
79
|
+
for (const sub of ['dist/esm', 'dist']) {
|
|
80
|
+
const d = path.join(brNm, sub);
|
|
81
|
+
if (fs.existsSync(path.join(d, 'ffmpeg-core.js')) && fs.existsSync(path.join(d, 'ffmpeg-core.wasm'))) {
|
|
82
|
+
ffmpegSource = d;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (ffmpegSource) {
|
|
89
|
+
const ffmpegDir = path.join(targetDir, 'ffmpeg');
|
|
90
|
+
if (copyIfNewer(path.join(ffmpegSource, 'ffmpeg-core.js'), path.join(ffmpegDir, 'ffmpeg-core.js'))) copied.push('ffmpeg-core.js');
|
|
91
|
+
if (copyIfNewer(path.join(ffmpegSource, 'ffmpeg-core.wasm'), path.join(ffmpegDir, 'ffmpeg-core.wasm'))) copied.push('ffmpeg-core.wasm');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. mp4-wasm.wasm -> public/
|
|
95
|
+
const mp4Candidates = [
|
|
96
|
+
path.join(nm, 'mp4-wasm', 'dist', 'mp4-wasm.wasm'),
|
|
97
|
+
path.join(nm, 'mp4-wasm', 'build', 'mp4.wasm'),
|
|
98
|
+
path.join(nm, '@twick', 'browser-render', 'public', 'mp4-wasm.wasm'),
|
|
99
|
+
path.join(nmParent, '@twick', 'browser-render', 'public', 'mp4-wasm.wasm'),
|
|
100
|
+
path.join(browserRenderRoot, 'public', 'mp4-wasm.wasm'),
|
|
101
|
+
];
|
|
102
|
+
for (const src of mp4Candidates) {
|
|
103
|
+
if (copyIfNewer(src, path.join(targetDir, 'mp4-wasm.wasm'))) {
|
|
104
|
+
copied.push('mp4-wasm.wasm');
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 3. audio-worker.js -> public/
|
|
110
|
+
const audioCandidates = [
|
|
111
|
+
path.join(nm, '@twick', 'browser-render', 'public', 'audio-worker.js'),
|
|
112
|
+
path.join(nmParent, '@twick', 'browser-render', 'public', 'audio-worker.js'),
|
|
113
|
+
path.join(browserRenderRoot, 'public', 'audio-worker.js'),
|
|
114
|
+
];
|
|
115
|
+
for (const src of audioCandidates) {
|
|
116
|
+
if (copyIfNewer(src, path.join(targetDir, 'audio-worker.js'))) {
|
|
117
|
+
copied.push('audio-worker.js');
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (copied.length > 0) {
|
|
123
|
+
console.log(`[twick-browser-render] Copied to ${targetDir}: ${copied.join(', ')}`);
|
|
124
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const root = path.resolve(__dirname, '..');
|
|
6
|
+
const dest = path.join(root, 'public', 'mp4-wasm.wasm');
|
|
7
|
+
const candidates = [
|
|
8
|
+
path.join(root, 'node_modules', 'mp4-wasm', 'dist', 'mp4-wasm.wasm'),
|
|
9
|
+
path.join(root, 'node_modules', 'mp4-wasm', 'build', 'mp4.wasm'),
|
|
10
|
+
path.resolve(root, '..', 'examples', 'public', 'mp4-wasm.wasm'),
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
14
|
+
for (const src of candidates) {
|
|
15
|
+
if (fs.existsSync(src)) {
|
|
16
|
+
fs.cpSync(src, dest);
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
}
|