@remotion/renderer 4.0.501 → 4.0.502

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.
@@ -1,3 +1,3 @@
1
1
  import type { AudioOrVideoAsset } from 'remotion/no-react';
2
2
  import type { Assets } from './types';
3
- export declare const calculateAssetPositions: (frames: AudioOrVideoAsset[][]) => Assets;
3
+ export declare const calculateAssetPositions: (frames: AudioOrVideoAsset[][], sourceFrames?: number[]) => Assets;
@@ -40,7 +40,7 @@ const indexUncompressedAssets = (renderAssets) => {
40
40
  }
41
41
  return assetsByReference;
42
42
  };
43
- const calculateAssetPositions = (frames) => {
43
+ const calculateAssetPositions = (frames, sourceFrames = frames.map((_, index) => index)) => {
44
44
  const assets = [];
45
45
  // Assets that have started but not yet ended, keyed by asset id.
46
46
  // Since assets are deduplicated by id within a frame, at most one
@@ -49,6 +49,12 @@ const calculateAssetPositions = (frames) => {
49
49
  const flattened = frames.flat(1);
50
50
  const uncompressedAssets = indexUncompressedAssets(flattened);
51
51
  for (let frame = 0; frame < frames.length; frame++) {
52
+ if (frame > 0 && sourceFrames[frame] !== sourceFrames[frame - 1] + 1) {
53
+ for (const openAsset of openAssets.values()) {
54
+ openAsset.duration = frame - openAsset.startInVideo;
55
+ }
56
+ openAssets.clear();
57
+ }
52
58
  const current = deduplicateAssets(frames[frame]);
53
59
  const currentIds = new Set(current.map((a) => a.id));
54
60
  for (const [id, openAsset] of openAssets) {
@@ -55,6 +55,7 @@ const path = __importStar(require("node:path"));
55
55
  const node_util_1 = require("node:util");
56
56
  const download_file_1 = require("../assets/download-file");
57
57
  const make_file_executable_1 = require("../compositor/make-file-executable");
58
+ const browser_download_lock_1 = require("./browser-download-lock");
58
59
  const extract_zip_archive_1 = require("./extract-zip-archive");
59
60
  const get_chrome_download_url_1 = require("./get-chrome-download-url");
60
61
  Object.defineProperty(exports, "TESTED_VERSION", { enumerable: true, get: function () { return get_chrome_download_url_1.TESTED_VERSION; } });
@@ -127,8 +128,6 @@ const downloadBrowser = async ({ logLevel, indent, onProgress, version, chromeMo
127
128
  if (installedVersion === expectedVersion) {
128
129
  return (0, exports.getRevisionInfo)(chromeMode);
129
130
  }
130
- // VERSION file missing or mismatched - delete and re-download
131
- fs.rmSync(outputPath, { recursive: true, force: true });
132
131
  }
133
132
  if (!(await existsAsync(downloadsFolder))) {
134
133
  await mkdirAsync(downloadsFolder, {
@@ -142,60 +141,74 @@ const downloadBrowser = async ({ logLevel, indent, onProgress, version, chromeMo
142
141
  'Chrome Headless Shell is not available for Windows for arm64 architecture.',
143
142
  ].join('\n'));
144
143
  }
145
- (0, get_chrome_download_url_1.logDownloadUrl)({ url: downloadURL, logLevel, indent });
144
+ const releaseLock = await (0, browser_download_lock_1.acquireBrowserDownloadLock)(path.join(downloadsFolder, 'download.lock'));
146
145
  try {
147
- await (0, download_file_1.downloadFile)({
148
- url: downloadURL,
149
- to: () => archivePath,
150
- onProgress: (progress) => {
151
- if (progress.totalSize === null || progress.percent === null) {
152
- throw new Error('Expected totalSize and percent to be defined');
153
- }
154
- onProgress({
155
- downloadedBytes: progress.downloaded,
156
- totalSizeInBytes: progress.totalSize,
157
- percent: progress.percent,
158
- alreadyAvailable: false,
159
- });
160
- },
161
- indent,
162
- logLevel,
163
- abortSignal: new AbortController().signal,
164
- });
165
- await (0, extract_zip_archive_1.extractZipArchive)(archivePath, outputPath);
166
- const possibleSubdirs = [
167
- 'chrome-linux',
168
- 'chrome-headless-shell-linux64',
169
- 'chromium-headless-shell-amazon-linux2023-arm64',
170
- 'chromium-headless-shell-amazon-linux2023-x64',
171
- ];
172
- for (const subdir of possibleSubdirs) {
173
- const chromeLinuxFolder = path.join(outputPath, subdir);
174
- const chromePath = path.join(chromeLinuxFolder, 'chrome');
175
- if (fs.existsSync(chromePath)) {
176
- const chromeHeadlessShellPath = path.join(chromeLinuxFolder, 'chrome-headless-shell');
177
- fs.renameSync(chromePath, chromeHeadlessShellPath);
146
+ if (await existsAsync(outputPath)) {
147
+ const installedVersion = (0, exports.readVersionFile)(chromeMode);
148
+ if (installedVersion === expectedVersion) {
149
+ return (0, exports.getRevisionInfo)(chromeMode);
178
150
  }
179
- if (fs.existsSync(chromeLinuxFolder)) {
180
- const targetFolder = path.join(outputPath, 'chrome-headless-shell-' + platform);
181
- if (chromeLinuxFolder !== targetFolder) {
182
- fs.renameSync(chromeLinuxFolder, targetFolder);
151
+ // VERSION file missing or mismatched - delete and re-download
152
+ fs.rmSync(outputPath, { recursive: true, force: true });
153
+ }
154
+ (0, get_chrome_download_url_1.logDownloadUrl)({ url: downloadURL, logLevel, indent });
155
+ try {
156
+ await (0, download_file_1.downloadFile)({
157
+ url: downloadURL,
158
+ to: () => archivePath,
159
+ onProgress: (progress) => {
160
+ if (progress.totalSize === null || progress.percent === null) {
161
+ throw new Error('Expected totalSize and percent to be defined');
162
+ }
163
+ onProgress({
164
+ downloadedBytes: progress.downloaded,
165
+ totalSizeInBytes: progress.totalSize,
166
+ percent: progress.percent,
167
+ alreadyAvailable: false,
168
+ });
169
+ },
170
+ indent,
171
+ logLevel,
172
+ abortSignal: new AbortController().signal,
173
+ });
174
+ await (0, extract_zip_archive_1.extractZipArchive)(archivePath, outputPath);
175
+ const possibleSubdirs = [
176
+ 'chrome-linux',
177
+ 'chrome-headless-shell-linux64',
178
+ 'chromium-headless-shell-amazon-linux2023-arm64',
179
+ 'chromium-headless-shell-amazon-linux2023-x64',
180
+ ];
181
+ for (const subdir of possibleSubdirs) {
182
+ const chromeLinuxFolder = path.join(outputPath, subdir);
183
+ const chromePath = path.join(chromeLinuxFolder, 'chrome');
184
+ if (fs.existsSync(chromePath)) {
185
+ const chromeHeadlessShellPath = path.join(chromeLinuxFolder, 'chrome-headless-shell');
186
+ fs.renameSync(chromePath, chromeHeadlessShellPath);
187
+ }
188
+ if (fs.existsSync(chromeLinuxFolder)) {
189
+ const targetFolder = path.join(outputPath, 'chrome-headless-shell-' + platform);
190
+ if (chromeLinuxFolder !== targetFolder) {
191
+ fs.renameSync(chromeLinuxFolder, targetFolder);
192
+ }
183
193
  }
184
194
  }
185
195
  }
186
- }
187
- catch (err) {
188
- return Promise.reject(err);
196
+ catch (err) {
197
+ return Promise.reject(err);
198
+ }
199
+ finally {
200
+ if (await existsAsync(archivePath)) {
201
+ await unlinkAsync(archivePath);
202
+ }
203
+ }
204
+ writeVersionFile(chromeMode, expectedVersion);
205
+ const revisionInfo = (0, exports.getRevisionInfo)(chromeMode);
206
+ (0, make_file_executable_1.makeFileExecutableIfItIsNot)(revisionInfo.executablePath);
207
+ return revisionInfo;
189
208
  }
190
209
  finally {
191
- if (await existsAsync(archivePath)) {
192
- await unlinkAsync(archivePath);
193
- }
210
+ await releaseLock();
194
211
  }
195
- writeVersionFile(chromeMode, expectedVersion);
196
- const revisionInfo = (0, exports.getRevisionInfo)(chromeMode);
197
- (0, make_file_executable_1.makeFileExecutableIfItIsNot)(revisionInfo.executablePath);
198
- return revisionInfo;
199
212
  };
200
213
  exports.downloadBrowser = downloadBrowser;
201
214
  const getFolderPath = (downloadsFolder, platform) => {
@@ -0,0 +1 @@
1
+ export declare const acquireBrowserDownloadLock: (lockPath: string) => Promise<() => Promise<void>>;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.acquireBrowserDownloadLock = void 0;
40
+ const node_crypto_1 = require("node:crypto");
41
+ const fs = __importStar(require("node:fs"));
42
+ const node_os_1 = __importDefault(require("node:os"));
43
+ const RETRY_INTERVAL_IN_MS = 100;
44
+ const STALE_LOCK_IN_MS = 30000;
45
+ const HEARTBEAT_INTERVAL_IN_MS = 5000;
46
+ const wait = (timeoutInMilliseconds) => {
47
+ return new Promise((resolve) => {
48
+ setTimeout(resolve, timeoutInMilliseconds);
49
+ });
50
+ };
51
+ const isErrorWithCode = (error, code) => {
52
+ return (error instanceof Error &&
53
+ 'code' in error &&
54
+ error.code === code);
55
+ };
56
+ const readLock = async (lockPath) => {
57
+ try {
58
+ const parsed = JSON.parse(await fs.promises.readFile(lockPath, 'utf8'));
59
+ if (typeof parsed.hostname !== 'string' ||
60
+ typeof parsed.pid !== 'number' ||
61
+ typeof parsed.token !== 'string') {
62
+ return null;
63
+ }
64
+ return parsed;
65
+ }
66
+ catch (_a) {
67
+ return null;
68
+ }
69
+ };
70
+ const isProcessRunning = (pid) => {
71
+ try {
72
+ process.kill(pid, 0);
73
+ return true;
74
+ }
75
+ catch (error) {
76
+ return !isErrorWithCode(error, 'ESRCH');
77
+ }
78
+ };
79
+ const removeStaleLock = async (lockPath) => {
80
+ let stats;
81
+ try {
82
+ stats = await fs.promises.stat(lockPath);
83
+ }
84
+ catch (error) {
85
+ if (isErrorWithCode(error, 'ENOENT')) {
86
+ return;
87
+ }
88
+ throw error;
89
+ }
90
+ const lock = await readLock(lockPath);
91
+ if ((lock === null || lock === void 0 ? void 0 : lock.hostname) === node_os_1.default.hostname()) {
92
+ if (isProcessRunning(lock.pid)) {
93
+ return;
94
+ }
95
+ }
96
+ else if (Date.now() - stats.mtimeMs < STALE_LOCK_IN_MS) {
97
+ return;
98
+ }
99
+ try {
100
+ await fs.promises.unlink(lockPath);
101
+ }
102
+ catch (error) {
103
+ if (!isErrorWithCode(error, 'ENOENT')) {
104
+ throw error;
105
+ }
106
+ }
107
+ };
108
+ const acquireBrowserDownloadLock = async (lockPath) => {
109
+ // Browser archives and extraction directories are shared by every Remotion
110
+ // process in a project, so the whole installation must be cross-process safe.
111
+ for (;;) {
112
+ const token = (0, node_crypto_1.randomUUID)();
113
+ let lockFile;
114
+ try {
115
+ lockFile = await fs.promises.open(lockPath, 'wx');
116
+ }
117
+ catch (error) {
118
+ if (!isErrorWithCode(error, 'EEXIST')) {
119
+ throw error;
120
+ }
121
+ await removeStaleLock(lockPath);
122
+ await wait(RETRY_INTERVAL_IN_MS);
123
+ continue;
124
+ }
125
+ try {
126
+ await lockFile.writeFile(JSON.stringify({
127
+ hostname: node_os_1.default.hostname(),
128
+ pid: process.pid,
129
+ token,
130
+ }));
131
+ }
132
+ catch (error) {
133
+ await lockFile.close();
134
+ await fs.promises.rm(lockPath, { force: true });
135
+ throw error;
136
+ }
137
+ const heartbeat = setInterval(async () => {
138
+ const currentLock = await readLock(lockPath);
139
+ if ((currentLock === null || currentLock === void 0 ? void 0 : currentLock.token) !== token) {
140
+ return;
141
+ }
142
+ const now = new Date();
143
+ try {
144
+ await fs.promises.utimes(lockPath, now, now);
145
+ }
146
+ catch (_a) {
147
+ // The lock may have been cleaned up while the heartbeat was pending.
148
+ }
149
+ }, HEARTBEAT_INTERVAL_IN_MS);
150
+ heartbeat.unref();
151
+ return async () => {
152
+ clearInterval(heartbeat);
153
+ const currentLock = await readLock(lockPath);
154
+ await lockFile.close();
155
+ if ((currentLock === null || currentLock === void 0 ? void 0 : currentLock.token) === token) {
156
+ await fs.promises.rm(lockPath, { force: true });
157
+ }
158
+ };
159
+ }
160
+ };
161
+ exports.acquireBrowserDownloadLock = acquireBrowserDownloadLock;
package/dist/client.d.ts CHANGED
@@ -1364,14 +1364,14 @@ export declare const BrowserSafeApis: {
1364
1364
  description: () => import("react/jsx-runtime").JSX.Element;
1365
1365
  ssrName: "frameRange";
1366
1366
  docLink: string;
1367
- type: import("./frame-range").FrameRange | null;
1367
+ type: import("./frame-range").FrameSelection;
1368
1368
  getValue: ({ commandLine }: {
1369
1369
  commandLine: Record<string, unknown>;
1370
1370
  }) => {
1371
1371
  source: string;
1372
- value: import("./frame-range").FrameRange | null;
1372
+ value: import("./frame-range").FrameSelection;
1373
1373
  };
1374
- setConfig: (value: import("./frame-range").FrameRange | null) => void;
1374
+ setConfig: (value: import("./frame-range").FrameSelection) => void;
1375
1375
  id: "frames";
1376
1376
  };
1377
1377
  forceNewStudioOption: {
@@ -1565,7 +1565,7 @@ export declare const BrowserSafeApis: {
1565
1565
  };
1566
1566
  rspackOption: {
1567
1567
  name: string;
1568
- cliFlag: "experimental-rspack";
1568
+ cliFlag: "rspack";
1569
1569
  description: () => import("react/jsx-runtime").JSX.Element;
1570
1570
  ssrName: null;
1571
1571
  docLink: null;
@@ -1577,7 +1577,7 @@ export declare const BrowserSafeApis: {
1577
1577
  source: string;
1578
1578
  };
1579
1579
  setConfig(value: boolean): void;
1580
- id: "experimental-rspack";
1580
+ id: "rspack";
1581
1581
  };
1582
1582
  outDirOption: {
1583
1583
  name: string;
@@ -1,5 +1,5 @@
1
1
  import type { Codec } from './codec';
2
- import type { FrameRange } from './frame-range';
2
+ import type { SingleFrameRange } from './frame-range';
3
3
  import type { LogLevel } from './log-level';
4
4
  import type { CancelSignal } from './make-cancel-signal';
5
5
  import type { AudioCodec } from './options/audio-codec';
@@ -27,7 +27,7 @@ type OptionalCombineChunksOptions = {
27
27
  audioCodec: AudioCodec | null;
28
28
  cancelSignal: CancelSignal | undefined;
29
29
  metadata: Record<string, string> | null;
30
- frameRange: FrameRange | null;
30
+ frameRange: SingleFrameRange | null;
31
31
  everyNthFrame: number;
32
32
  sampleRate: number;
33
33
  };
@@ -45,8 +45,8 @@ const internalCombineChunks = async ({ outputLocation: output, onProgress, codec
45
45
  });
46
46
  const shouldCreateAudio = resolvedAudioCodec !== null && audioFiles.length > 0;
47
47
  const seamlessVideo = (0, can_concat_seamlessly_1.canConcatVideoSeamlessly)(codec);
48
- const seamlessAudio = (0, can_concat_seamlessly_1.canConcatAudioSeamlessly)(resolvedAudioCodec, framesPerChunk);
49
48
  const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(compositionDurationInFrames, frameRange);
49
+ const seamlessAudio = (0, can_concat_seamlessly_1.canConcatAudioSeamlessly)(resolvedAudioCodec, framesPerChunk);
50
50
  const numberOfFrames = (0, get_duration_from_frame_range_1.getFramesToRender)(realFrameRange, everyNthFrame).length;
51
51
  const videoOutput = shouldCreateVideo
52
52
  ? (0, node_path_1.join)(filelistDir, `video.${(0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, resolvedAudioCodec)}`)
@@ -25,7 +25,7 @@ const createAudio = async ({ assets, onDownload, fps, logLevel, onProgress, down
25
25
  binariesDirectory,
26
26
  });
27
27
  (0, download_and_map_assets_to_file_1.markAllAssetsAsDownloaded)(downloadMap);
28
- const assetPositions = (0, calculate_asset_positions_1.calculateAssetPositions)(fileUrlAssets);
28
+ const assetPositions = (0, calculate_asset_positions_1.calculateAssetPositions)(fileUrlAssets, assets.map((asset) => asset.frame));
29
29
  logger_1.Log.verbose({ indent, logLevel, tag: 'audio' }, 'asset positions', JSON.stringify(assetPositions));
30
30
  const preprocessProgress = new Array(assetPositions.length).fill(0);
31
31
  let mergeProgress = 0;