@meframe/server 0.0.6 → 0.0.8
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/dist/cjs/ServerExporterBase.js +203 -41
- package/dist/cjs/ServerExporterBase.js.map +1 -1
- package/dist/cjs/postprocess/ffmpeg-audio-mix.js +100 -30
- package/dist/cjs/postprocess/ffmpeg-audio-mix.js.map +1 -1
- package/dist/cjs/runner/runner.js +2 -1
- package/dist/cjs/runner/runner.js.map +1 -1
- package/dist/cjs/utils/local-spool-server.js +173 -0
- package/dist/cjs/utils/local-spool-server.js.map +1 -0
- package/dist/cjs/utils/multipart-upload.js +5 -1
- package/dist/cjs/utils/multipart-upload.js.map +1 -1
- package/dist/esm/ServerExporterBase.d.ts.map +1 -1
- package/dist/esm/ServerExporterBase.js +204 -42
- package/dist/esm/ServerExporterBase.js.map +1 -1
- package/dist/esm/postprocess/ffmpeg-audio-mix.d.ts +35 -1
- package/dist/esm/postprocess/ffmpeg-audio-mix.d.ts.map +1 -1
- package/dist/esm/postprocess/ffmpeg-audio-mix.js +98 -30
- package/dist/esm/postprocess/ffmpeg-audio-mix.js.map +1 -1
- package/dist/esm/runner/runner.d.ts +2 -1
- package/dist/esm/runner/runner.d.ts.map +1 -1
- package/dist/esm/runner/runner.js +2 -1
- package/dist/esm/runner/runner.js.map +1 -1
- package/dist/esm/types.d.ts +35 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/local-spool-server.d.ts +17 -0
- package/dist/esm/utils/local-spool-server.d.ts.map +1 -0
- package/dist/esm/utils/local-spool-server.js +167 -0
- package/dist/esm/utils/local-spool-server.js.map +1 -0
- package/dist/esm/utils/multipart-upload.d.ts +1 -0
- package/dist/esm/utils/multipart-upload.d.ts.map +1 -1
- package/dist/esm/utils/multipart-upload.js +5 -1
- package/dist/esm/utils/multipart-upload.js.map +1 -1
- package/dist/runner-browser/runner.mjs +645 -644
- package/docs/INTEGRATION.md +71 -4
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runExportInPage = runExportInPage;
|
|
4
4
|
async function runExportInPage(page, bridge, params) {
|
|
5
5
|
const { model, exportOptions, partSizeBytes, uploadId, key, // reserved for future use (e.g., logging)
|
|
6
|
-
runnerModuleCode, pageUrl, workerPath, } = params;
|
|
6
|
+
runnerModuleCode, pageUrl, workerPath, fonts, } = params;
|
|
7
7
|
let settled = false;
|
|
8
8
|
let doneResolve;
|
|
9
9
|
let doneReject;
|
|
@@ -45,6 +45,7 @@ async function runExportInPage(page, bridge, params) {
|
|
|
45
45
|
uploadId,
|
|
46
46
|
key,
|
|
47
47
|
workerPath,
|
|
48
|
+
fonts,
|
|
48
49
|
};
|
|
49
50
|
if (pageUrl) {
|
|
50
51
|
// Ensure a trustworthy origin so Web APIs like WebCodecs are available.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/runner/runner.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/runner/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLocalSpoolServer = createLocalSpoolServer;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
10
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const SPOOL_TOKEN_HEADER = 'x-meframe-spool-token';
|
|
13
|
+
function readRequestBody(req) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const chunks = [];
|
|
16
|
+
req.on('data', (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
|
|
17
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
18
|
+
req.on('error', reject);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function sha1Hex(buffer) {
|
|
22
|
+
return node_crypto_1.default.createHash('sha1').update(buffer).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
function setCorsHeaders(input) {
|
|
25
|
+
const origin = String(input.req.headers.origin ?? '');
|
|
26
|
+
if (origin) {
|
|
27
|
+
input.res.setHeader('Access-Control-Allow-Origin', origin);
|
|
28
|
+
input.res.setHeader('Vary', 'Origin');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
input.res.setHeader('Access-Control-Allow-Origin', '*');
|
|
32
|
+
}
|
|
33
|
+
input.res.setHeader('Access-Control-Allow-Methods', input.allowedMethods.join(', '));
|
|
34
|
+
const reqHeaders = String(input.req.headers['access-control-request-headers'] ?? '');
|
|
35
|
+
input.res.setHeader('Access-Control-Allow-Headers', reqHeaders || '*');
|
|
36
|
+
input.res.setHeader('Access-Control-Max-Age', '600');
|
|
37
|
+
input.res.setHeader('Access-Control-Expose-Headers', 'ETag');
|
|
38
|
+
// Chrome Private Network Access (PNA) preflight.
|
|
39
|
+
// When a public origin fetches a private network resource (e.g. 127.0.0.1),
|
|
40
|
+
// browsers may include `Access-Control-Request-Private-Network: true`.
|
|
41
|
+
// We must explicitly allow it.
|
|
42
|
+
if (String(input.req.headers['access-control-request-private-network'] ?? '') === 'true') {
|
|
43
|
+
input.res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
input.res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function createLocalSpoolServer(input) {
|
|
50
|
+
const uploads = new Map();
|
|
51
|
+
const logger = input.logger;
|
|
52
|
+
const token = node_crypto_1.default.randomBytes(16).toString('hex');
|
|
53
|
+
const server = node_http_1.default.createServer(async (req, res) => {
|
|
54
|
+
try {
|
|
55
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? input.host}`);
|
|
56
|
+
if (req.method === 'OPTIONS') {
|
|
57
|
+
setCorsHeaders({ req, res, allowedMethods: ['PUT', 'GET', 'OPTIONS'] });
|
|
58
|
+
res.writeHead(204);
|
|
59
|
+
res.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (req.method === 'GET' && url.pathname === '/') {
|
|
63
|
+
setCorsHeaders({ req, res, allowedMethods: ['PUT', 'GET', 'OPTIONS'] });
|
|
64
|
+
res.writeHead(200, {
|
|
65
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
66
|
+
'Cache-Control': 'no-store',
|
|
67
|
+
});
|
|
68
|
+
res.end('<!doctype html><html><body>meframe spool</body></html>');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const m = /^\/upload\/([^/]+)\/(\d+)$/.exec(url.pathname);
|
|
72
|
+
if (req.method === 'PUT' && m) {
|
|
73
|
+
setCorsHeaders({ req, res, allowedMethods: ['PUT', 'GET', 'OPTIONS'] });
|
|
74
|
+
const reqToken = String(req.headers[SPOOL_TOKEN_HEADER] ?? '');
|
|
75
|
+
if (!reqToken || reqToken !== token) {
|
|
76
|
+
res.writeHead(403);
|
|
77
|
+
res.end('Forbidden');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const uploadId = m[1];
|
|
81
|
+
const partNumber = Number(m[2]);
|
|
82
|
+
if (!Number.isFinite(partNumber) || partNumber <= 0) {
|
|
83
|
+
res.writeHead(400);
|
|
84
|
+
res.end('Invalid partNumber');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const upload = uploads.get(uploadId);
|
|
88
|
+
if (!upload) {
|
|
89
|
+
res.writeHead(404);
|
|
90
|
+
res.end('Unknown uploadId');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const body = await readRequestBody(req);
|
|
94
|
+
const etag = sha1Hex(body);
|
|
95
|
+
const partPath = node_path_1.default.join(upload.dir, `part-${String(partNumber).padStart(6, '0')}.bin`);
|
|
96
|
+
await promises_1.default.writeFile(partPath, body);
|
|
97
|
+
upload.parts.set(partNumber, { etag, path: partPath });
|
|
98
|
+
res.writeHead(200, { ETag: etag });
|
|
99
|
+
res.end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
res.writeHead(404);
|
|
103
|
+
res.end('Not found');
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
logger.warn('[local-spool] request failed', e);
|
|
107
|
+
res.writeHead(500);
|
|
108
|
+
res.end(String(e?.message ?? e));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
await new Promise((resolve) => server.listen(0, input.host, () => resolve()));
|
|
112
|
+
const address = server.address();
|
|
113
|
+
if (!address || typeof address === 'string') {
|
|
114
|
+
throw new Error('Failed to start local spool server');
|
|
115
|
+
}
|
|
116
|
+
const baseUrl = `http://${address.address}:${address.port}`;
|
|
117
|
+
const store = {
|
|
118
|
+
async createMultipartUpload({ key, contentType, metadata }) {
|
|
119
|
+
const uploadId = node_crypto_1.default.randomUUID();
|
|
120
|
+
const dir = await promises_1.default.mkdtemp(node_path_1.default.join(node_path_1.default.dirname(input.outFilePath), 'spool-upload-'));
|
|
121
|
+
uploads.set(uploadId, {
|
|
122
|
+
key,
|
|
123
|
+
contentType,
|
|
124
|
+
metadata,
|
|
125
|
+
dir,
|
|
126
|
+
parts: new Map(),
|
|
127
|
+
});
|
|
128
|
+
return { uploadId };
|
|
129
|
+
},
|
|
130
|
+
async getUploadPartUrl({ uploadId, partNumber }) {
|
|
131
|
+
if (!uploads.has(uploadId)) {
|
|
132
|
+
throw new Error('Unknown uploadId');
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
url: `${baseUrl}/upload/${uploadId}/${partNumber}`,
|
|
136
|
+
headers: { [SPOOL_TOKEN_HEADER]: token },
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
async completeMultipartUpload({ uploadId, parts }) {
|
|
140
|
+
const upload = uploads.get(uploadId);
|
|
141
|
+
if (!upload)
|
|
142
|
+
throw new Error('Unknown uploadId');
|
|
143
|
+
const out = (0, node_fs_1.createWriteStream)(input.outFilePath);
|
|
144
|
+
for (const p of parts) {
|
|
145
|
+
const record = upload.parts.get(p.partNumber);
|
|
146
|
+
if (!record)
|
|
147
|
+
throw new Error(`Missing part ${p.partNumber}`);
|
|
148
|
+
await new Promise((resolve, reject) => {
|
|
149
|
+
const s = (0, node_fs_1.createReadStream)(record.path);
|
|
150
|
+
s.on('error', reject);
|
|
151
|
+
s.on('end', () => resolve());
|
|
152
|
+
s.pipe(out, { end: false });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
out.end();
|
|
156
|
+
await new Promise((resolve, reject) => out.on('close', () => resolve()).on('error', reject));
|
|
157
|
+
await promises_1.default.rm(upload.dir, { recursive: true, force: true });
|
|
158
|
+
uploads.delete(uploadId);
|
|
159
|
+
},
|
|
160
|
+
async abortMultipartUpload({ uploadId }) {
|
|
161
|
+
const upload = uploads.get(uploadId);
|
|
162
|
+
if (!upload)
|
|
163
|
+
return;
|
|
164
|
+
await promises_1.default.rm(upload.dir, { recursive: true, force: true });
|
|
165
|
+
uploads.delete(uploadId);
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
const close = async () => {
|
|
169
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
170
|
+
};
|
|
171
|
+
return { baseUrl, store, close };
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=local-spool-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-spool-server.js","sourceRoot":"","sources":["../../../src/utils/local-spool-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,11 @@ async function uploadFileToMultipartStore(input) {
|
|
|
16
16
|
throw new Error('partSizeBytes must be >= 5 MiB for multipart upload');
|
|
17
17
|
}
|
|
18
18
|
(0, abort_gate_js_1.throwIfAborted)(abortSignal);
|
|
19
|
-
const { uploadId } = await store.createMultipartUpload({
|
|
19
|
+
const { uploadId } = await store.createMultipartUpload({
|
|
20
|
+
key,
|
|
21
|
+
contentType,
|
|
22
|
+
metadata: input.metadata,
|
|
23
|
+
});
|
|
20
24
|
if (!uploadId) {
|
|
21
25
|
throw new Error('Failed to create multipart upload');
|
|
22
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipart-upload.js","sourceRoot":"","sources":["../../../src/utils/multipart-upload.ts"],"names":[],"mappings":";;;;;AASA,
|
|
1
|
+
{"version":3,"file":"multipart-upload.js","sourceRoot":"","sources":["../../../src/utils/multipart-upload.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerExporterBase.d.ts","sourceRoot":"","sources":["../../src/ServerExporterBase.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"ServerExporterBase.d.ts","sourceRoot":"","sources":["../../src/ServerExporterBase.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAepB,8BAAsB,kBAAkB;IACtC,SAAS,CAAC,QAAQ,CAAC,mBAAmB,IAAI,MAAM,GAAG,GAAG;IAEtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAKP;IACR,OAAO,CAAC,uBAAuB,CAAgC;gBAEnD,OAAO,EAAE,qBAAqB;IAS1C,OAAO,CAAC,gBAAgB;YAIV,8BAA8B;YAa9B,WAAW;IAyBzB,OAAO,CAAC,WAAW;IAWb,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoZ5E,OAAO,CAAC,gBAAgB;YA0BV,mBAAmB;IA2BjC,OAAO,CAAC,kBAAkB;CAI3B"}
|
|
@@ -3,9 +3,11 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { launch } from 'puppeteer-core';
|
|
6
|
-
import { runFfmpegAudioMixPostProcess } from './postprocess/ffmpeg-audio-mix.js';
|
|
6
|
+
import { runFfmpegAudioMixPostProcess, runFfmpegAudioMixToFile, } from './postprocess/ffmpeg-audio-mix.js';
|
|
7
7
|
import { runExportInPage } from './runner/runner.js';
|
|
8
8
|
import { AbortError, createAbortError, createAbortGate, throwIfAborted, } from './utils/abort-gate.js';
|
|
9
|
+
import { createLocalSpoolServer } from './utils/local-spool-server.js';
|
|
10
|
+
import { uploadFileToMultipartStore } from './utils/multipart-upload.js';
|
|
9
11
|
export class ServerExporterBase {
|
|
10
12
|
store;
|
|
11
13
|
options;
|
|
@@ -95,10 +97,14 @@ export class ServerExporterBase {
|
|
|
95
97
|
let browser = null;
|
|
96
98
|
let page = null;
|
|
97
99
|
let uploadId = null;
|
|
100
|
+
let spoolServer = null;
|
|
101
|
+
let spoolDir = null;
|
|
102
|
+
let spoolFilePath = null;
|
|
98
103
|
let userDataDir = null;
|
|
99
104
|
let abortedByUser = false;
|
|
100
105
|
let audioSkipped = false;
|
|
101
106
|
let audioSkipMessage = null;
|
|
107
|
+
const runnerOutputTarget = this.options.runnerOutput?.target ?? 'spool';
|
|
102
108
|
const { abortPromise, dispose: disposeAbortGate } = createAbortGate(input.abortSignal, () => {
|
|
103
109
|
abortedByUser = true;
|
|
104
110
|
// Hard-stop the page so the job can exit quickly (runner promise rejects on page close).
|
|
@@ -110,21 +116,51 @@ export class ServerExporterBase {
|
|
|
110
116
|
partSizeBytes,
|
|
111
117
|
headless: this.options.browser?.headless ?? true,
|
|
112
118
|
executablePath: this.options.browser?.executablePath,
|
|
119
|
+
runnerOutputTarget,
|
|
113
120
|
});
|
|
114
121
|
throwIfAborted(input.abortSignal);
|
|
115
122
|
userDataDir = await fs.mkdtemp(path.join(this.options.browser?.userDataDirBase ?? os.tmpdir(), 'meframe-server-'));
|
|
116
123
|
log('Created userDataDir', { userDataDir });
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
if (runnerOutputTarget === 'store') {
|
|
125
|
+
throwIfAborted(input.abortSignal);
|
|
126
|
+
const uploadInit = await this.store.createMultipartUpload({
|
|
127
|
+
key,
|
|
128
|
+
contentType,
|
|
129
|
+
metadata: input.metadata,
|
|
130
|
+
});
|
|
131
|
+
uploadId = uploadInit.uploadId;
|
|
132
|
+
if (!uploadId) {
|
|
133
|
+
throw new Error('Failed to create multipart upload');
|
|
134
|
+
}
|
|
135
|
+
log('Multipart upload created', { uploadId });
|
|
136
|
+
}
|
|
137
|
+
else if (runnerOutputTarget === 'spool') {
|
|
138
|
+
const spoolOpts = this.options.runnerOutput?.spool ?? {};
|
|
139
|
+
const tmpDirBase = spoolOpts.tmpDirBase ?? os.tmpdir();
|
|
140
|
+
const host = spoolOpts.host ?? '127.0.0.1';
|
|
141
|
+
throwIfAborted(input.abortSignal);
|
|
142
|
+
spoolDir = await fs.mkdtemp(path.join(tmpDirBase, 'meframe-spool-'));
|
|
143
|
+
spoolFilePath = path.join(spoolDir, 'video-only.mp4');
|
|
144
|
+
spoolServer = await createLocalSpoolServer({
|
|
145
|
+
outFilePath: spoolFilePath,
|
|
146
|
+
host,
|
|
147
|
+
logger: logger,
|
|
148
|
+
});
|
|
149
|
+
// The runner uploads MP4 parts to this temporary localhost store.
|
|
150
|
+
const uploadInit = await spoolServer.store.createMultipartUpload({
|
|
151
|
+
key: 'spool/video-only.mp4',
|
|
152
|
+
contentType,
|
|
153
|
+
metadata: input.metadata,
|
|
154
|
+
});
|
|
155
|
+
uploadId = uploadInit.uploadId;
|
|
156
|
+
if (!uploadId) {
|
|
157
|
+
throw new Error('Failed to create local spool upload');
|
|
158
|
+
}
|
|
159
|
+
log('Local spool server ready', { baseUrl: spoolServer.baseUrl, uploadId, spoolDir });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
throw new Error(`Unknown runnerOutput.target: ${String(runnerOutputTarget)}`);
|
|
126
163
|
}
|
|
127
|
-
log('Multipart upload created', { uploadId });
|
|
128
164
|
const parts = [];
|
|
129
165
|
const onProgress = (evt) => {
|
|
130
166
|
if (typeof evt?.message === 'string' &&
|
|
@@ -140,10 +176,18 @@ export class ServerExporterBase {
|
|
|
140
176
|
log('Runner bundle loaded', { bytes: runnerModuleCode.length });
|
|
141
177
|
log('Launching browser...');
|
|
142
178
|
throwIfAborted(input.abortSignal);
|
|
179
|
+
const launchArgs = [...(this.options.browser?.launchArgs ?? [])];
|
|
180
|
+
if (runnerOutputTarget === 'spool' && spoolServer) {
|
|
181
|
+
// Allow https page to fetch(PUT) to http://127.0.0.1 (mixed content + private network access).
|
|
182
|
+
// This is restricted to the dedicated export Chromium instance.
|
|
183
|
+
launchArgs.push('--allow-running-insecure-content');
|
|
184
|
+
launchArgs.push(`--unsafely-treat-insecure-origin-as-secure=${spoolServer.baseUrl}`);
|
|
185
|
+
launchArgs.push('--disable-features=BlockInsecurePrivateNetworkRequests');
|
|
186
|
+
}
|
|
143
187
|
browser = await launch({
|
|
144
188
|
headless: this.options.browser?.headless ?? true,
|
|
145
189
|
executablePath: this.options.browser?.executablePath,
|
|
146
|
-
args:
|
|
190
|
+
args: launchArgs,
|
|
147
191
|
userDataDir,
|
|
148
192
|
});
|
|
149
193
|
log('Browser launched');
|
|
@@ -168,12 +212,18 @@ export class ServerExporterBase {
|
|
|
168
212
|
log('Running export in page...');
|
|
169
213
|
await Promise.race([
|
|
170
214
|
runExportInPage(page, {
|
|
171
|
-
requestPartUrl: async (partNumber) =>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
215
|
+
requestPartUrl: async (partNumber) => runnerOutputTarget === 'store'
|
|
216
|
+
? this.store.getUploadPartUrl({
|
|
217
|
+
key,
|
|
218
|
+
uploadId: uploadId,
|
|
219
|
+
partNumber,
|
|
220
|
+
expiresInSeconds: partUrlExpiresInSeconds,
|
|
221
|
+
})
|
|
222
|
+
: spoolServer.store.getUploadPartUrl({
|
|
223
|
+
key: 'spool/video-only.mp4',
|
|
224
|
+
uploadId: uploadId,
|
|
225
|
+
partNumber,
|
|
226
|
+
}),
|
|
177
227
|
reportPartDone: async (partNumber, etag) => {
|
|
178
228
|
const normalizedEtag = etag.replaceAll('"', '');
|
|
179
229
|
parts.push({ partNumber, etag: normalizedEtag });
|
|
@@ -194,15 +244,27 @@ export class ServerExporterBase {
|
|
|
194
244
|
runnerModuleCode,
|
|
195
245
|
pageUrl: input.pageUrl,
|
|
196
246
|
workerPath: input.workerPath,
|
|
247
|
+
fonts: input.fonts ?? [],
|
|
197
248
|
}),
|
|
198
249
|
abortPromise,
|
|
199
250
|
]);
|
|
200
251
|
log('Runner resolved');
|
|
201
252
|
parts.sort((a, b) => a.partNumber - b.partNumber);
|
|
202
253
|
this.assertPartsValid(parts);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
254
|
+
if (runnerOutputTarget === 'store') {
|
|
255
|
+
log('Completing multipart upload...', { parts: parts.length });
|
|
256
|
+
await this.store.completeMultipartUpload({ key, uploadId: uploadId, parts });
|
|
257
|
+
log('Export completed', { key });
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
log('Completing local spool upload...', { parts: parts.length });
|
|
261
|
+
await spoolServer.store.completeMultipartUpload({
|
|
262
|
+
key: 'spool/video-only.mp4',
|
|
263
|
+
uploadId: uploadId,
|
|
264
|
+
parts,
|
|
265
|
+
});
|
|
266
|
+
log('Local spool completed', { spoolFilePath });
|
|
267
|
+
}
|
|
206
268
|
const post = this.options.postProcess?.ffmpegAudioMix;
|
|
207
269
|
if (post?.enabled && audioSkipped) {
|
|
208
270
|
log('Postprocess: ffmpegAudioMix enabled and audio was skipped', {
|
|
@@ -215,41 +277,104 @@ export class ServerExporterBase {
|
|
|
215
277
|
const ffprobePath = post.ffprobePath ?? 'ffprobe';
|
|
216
278
|
const onFailure = post.onFailure ?? 'fallback';
|
|
217
279
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
280
|
+
if (runnerOutputTarget === 'store') {
|
|
281
|
+
const result = await runFfmpegAudioMixPostProcess({
|
|
282
|
+
store: this.store,
|
|
283
|
+
model: input.model,
|
|
284
|
+
originalKey: key,
|
|
285
|
+
outputKey,
|
|
286
|
+
partSizeBytes,
|
|
287
|
+
contentType,
|
|
288
|
+
ffmpegPath,
|
|
289
|
+
ffprobePath,
|
|
290
|
+
audioBitrate,
|
|
291
|
+
resolveUploadedInput: post.resolveUploadedInput,
|
|
292
|
+
abortSignal: input.abortSignal,
|
|
293
|
+
logger,
|
|
294
|
+
onProgress: (evt) => {
|
|
295
|
+
onProgress({ progress: evt.progress, stage: evt.stage, message: evt.message });
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
if (!result.skipped && result.key !== key) {
|
|
299
|
+
log('Postprocess completed', { key: result.key });
|
|
300
|
+
}
|
|
301
|
+
else if (!result.skipped) {
|
|
302
|
+
log('Postprocess completed (overwritten)', { key: result.key });
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
log('Postprocess skipped', { key: result.key });
|
|
306
|
+
}
|
|
307
|
+
return { key: result.key };
|
|
308
|
+
}
|
|
309
|
+
// spool mode: run FFmpeg to a local file, then upload once
|
|
310
|
+
const finalFilePath = path.join(spoolDir, 'final.mp4');
|
|
311
|
+
const result = await runFfmpegAudioMixToFile({
|
|
220
312
|
model: input.model,
|
|
221
|
-
|
|
222
|
-
outputKey,
|
|
223
|
-
partSizeBytes,
|
|
224
|
-
contentType,
|
|
313
|
+
videoOnlyInput: { kind: 'file', filePath: spoolFilePath },
|
|
225
314
|
ffmpegPath,
|
|
226
315
|
ffprobePath,
|
|
227
316
|
audioBitrate,
|
|
228
|
-
|
|
317
|
+
outFile: finalFilePath,
|
|
229
318
|
abortSignal: input.abortSignal,
|
|
230
319
|
logger,
|
|
231
|
-
onProgress: (evt) => {
|
|
232
|
-
onProgress({ progress: evt.progress, stage: evt.stage, message: evt.message });
|
|
233
|
-
},
|
|
320
|
+
onProgress: (evt) => onProgress({ progress: evt.progress, stage: evt.stage, message: evt.message }),
|
|
234
321
|
});
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
322
|
+
const fileToUpload = result.skipped ? spoolFilePath : finalFilePath;
|
|
323
|
+
const keyToUpload = result.skipped ? key : outputKey;
|
|
324
|
+
onProgress({ stage: 'upload', progress: 0.8, message: 'Uploading final MP4' });
|
|
325
|
+
await uploadFileToMultipartStore({
|
|
326
|
+
store: this.store,
|
|
327
|
+
key: keyToUpload,
|
|
328
|
+
filePath: fileToUpload,
|
|
329
|
+
contentType,
|
|
330
|
+
partSizeBytes,
|
|
331
|
+
metadata: input.metadata,
|
|
332
|
+
abortSignal: input.abortSignal,
|
|
333
|
+
logger: logger,
|
|
334
|
+
});
|
|
335
|
+
onProgress({ stage: 'upload', progress: 1, message: 'Upload completed' });
|
|
336
|
+
return { key: keyToUpload };
|
|
245
337
|
}
|
|
246
338
|
catch (e) {
|
|
247
339
|
logger.error('[ServerExporter] postprocess failed', e);
|
|
248
340
|
if (onFailure === 'throw')
|
|
249
341
|
throw e;
|
|
342
|
+
if (runnerOutputTarget === 'spool') {
|
|
343
|
+
// fallback: upload video-only once
|
|
344
|
+
onProgress({
|
|
345
|
+
stage: 'upload',
|
|
346
|
+
progress: 0.8,
|
|
347
|
+
message: 'Uploading video-only MP4 (fallback)',
|
|
348
|
+
});
|
|
349
|
+
await uploadFileToMultipartStore({
|
|
350
|
+
store: this.store,
|
|
351
|
+
key,
|
|
352
|
+
filePath: spoolFilePath,
|
|
353
|
+
contentType,
|
|
354
|
+
partSizeBytes,
|
|
355
|
+
metadata: input.metadata,
|
|
356
|
+
abortSignal: input.abortSignal,
|
|
357
|
+
logger: logger,
|
|
358
|
+
});
|
|
359
|
+
onProgress({ stage: 'upload', progress: 1, message: 'Upload completed (fallback)' });
|
|
360
|
+
}
|
|
250
361
|
return { key };
|
|
251
362
|
}
|
|
252
363
|
}
|
|
364
|
+
if (runnerOutputTarget === 'spool') {
|
|
365
|
+
onProgress({ stage: 'upload', progress: 0.8, message: 'Uploading video-only MP4' });
|
|
366
|
+
await uploadFileToMultipartStore({
|
|
367
|
+
store: this.store,
|
|
368
|
+
key,
|
|
369
|
+
filePath: spoolFilePath,
|
|
370
|
+
contentType,
|
|
371
|
+
partSizeBytes,
|
|
372
|
+
metadata: input.metadata,
|
|
373
|
+
abortSignal: input.abortSignal,
|
|
374
|
+
logger: logger,
|
|
375
|
+
});
|
|
376
|
+
onProgress({ stage: 'upload', progress: 1, message: 'Upload completed' });
|
|
377
|
+
}
|
|
253
378
|
return { key };
|
|
254
379
|
}
|
|
255
380
|
catch (error) {
|
|
@@ -264,7 +389,22 @@ export class ServerExporterBase {
|
|
|
264
389
|
else {
|
|
265
390
|
logger.error('[ServerExporter] export failed', error);
|
|
266
391
|
}
|
|
267
|
-
|
|
392
|
+
if (runnerOutputTarget === 'store') {
|
|
393
|
+
await this.abortMultipartUploadBestEffort({ key, uploadId, logger });
|
|
394
|
+
}
|
|
395
|
+
else if (runnerOutputTarget === 'spool') {
|
|
396
|
+
try {
|
|
397
|
+
if (spoolServer && uploadId) {
|
|
398
|
+
await spoolServer.store.abortMultipartUpload({
|
|
399
|
+
key: 'spool/video-only.mp4',
|
|
400
|
+
uploadId,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch (e) {
|
|
405
|
+
logger.warn('[ServerExporter] abort failed', e);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
268
408
|
if (isAbort) {
|
|
269
409
|
throw this.createAbortError();
|
|
270
410
|
}
|
|
@@ -290,6 +430,28 @@ export class ServerExporterBase {
|
|
|
290
430
|
logger.warn('[ServerExporter] cleanup failed', e);
|
|
291
431
|
}
|
|
292
432
|
}
|
|
433
|
+
if (spoolServer) {
|
|
434
|
+
try {
|
|
435
|
+
await spoolServer.close();
|
|
436
|
+
}
|
|
437
|
+
catch (e) {
|
|
438
|
+
logger.warn('[ServerExporter] cleanup failed', e);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (spoolDir) {
|
|
442
|
+
const keep = this.options.runnerOutput?.spool?.keepIntermediate === true;
|
|
443
|
+
if (!keep) {
|
|
444
|
+
try {
|
|
445
|
+
await fs.rm(spoolDir, { recursive: true, force: true });
|
|
446
|
+
}
|
|
447
|
+
catch (e) {
|
|
448
|
+
logger.warn('[ServerExporter] cleanup failed', e);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
logger.info('[ServerExporter] keepIntermediate enabled', { spoolDir });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
293
455
|
this.releaseSlot();
|
|
294
456
|
}
|
|
295
457
|
}
|