@remotion/renderer 4.0.516 → 4.0.518

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.
@@ -17206,11 +17206,14 @@ var correctFloatingPointError = (value) => {
17206
17206
  var BIT_DEPTH = 16;
17207
17207
  var BYTES_PER_SAMPLE = BIT_DEPTH / 8;
17208
17208
  var NUMBER_OF_CHANNELS = 2;
17209
+ var WAV_HEADER_SIZE = 44;
17209
17210
  var makeInlineAudioMixing = (dir, sampleRate) => {
17210
17211
  const folderToAdd = makeAndReturn(dir, "remotion-inline-audio-mixing");
17211
17212
  const openFiles = {};
17212
17213
  const writtenHeaders = {};
17213
17214
  const toneFrequencies = {};
17215
+ const startTimesInSamples = {};
17216
+ const writtenDataSizes = {};
17214
17217
  const cleanup = () => {
17215
17218
  for (const fileName of Object.keys(openFiles)) {
17216
17219
  try {
@@ -17221,42 +17224,39 @@ var makeInlineAudioMixing = (dir, sampleRate) => {
17221
17224
  deleteDirectory(folderToAdd);
17222
17225
  };
17223
17226
  const getListOfAssets = () => {
17224
- return Object.keys(openFiles);
17227
+ return Object.keys(writtenHeaders).map((outName) => ({
17228
+ outName,
17229
+ startInSamples: startTimesInSamples[outName],
17230
+ durationInSamples: writtenDataSizes[outName] / (NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)
17231
+ })).sort((a, b) => a.startInSamples - b.startInSamples);
17225
17232
  };
17226
17233
  const getFilePath = (asset) => {
17227
17234
  return path15.join(folderToAdd, `${asset.id}.wav`);
17228
17235
  };
17229
- const ensureAsset = ({
17230
- asset,
17231
- fps,
17232
- totalNumberOfFrames,
17233
- trimLeftOffset,
17234
- trimRightOffset
17235
- }) => {
17236
+ const ensureAsset = (asset) => {
17236
17237
  const filePath = getFilePath(asset);
17237
- if (!openFiles[filePath]) {
17238
+ if (openFiles[filePath] === undefined) {
17238
17239
  openFiles[filePath] = fs15.openSync(filePath, "w");
17239
17240
  }
17240
17241
  if (writtenHeaders[filePath]) {
17241
17242
  return;
17242
17243
  }
17243
17244
  writtenHeaders[filePath] = true;
17244
- const expectedDataSize = Math.round((totalNumberOfFrames / fps - trimLeftOffset + trimRightOffset) * NUMBER_OF_CHANNELS * sampleRate * BYTES_PER_SAMPLE);
17245
- const expectedSize = 40 + expectedDataSize;
17245
+ writtenDataSizes[filePath] = 0;
17246
17246
  const fd = openFiles[filePath];
17247
17247
  writeSync(fd, new Uint8Array([82, 73, 70, 70]), 0, 4, 0);
17248
- writeSync(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedSize)), 0, 4, 4);
17248
+ writeSync(fd, numberTo32BiIntLittleEndian(36), 0, 4, 4);
17249
17249
  writeSync(fd, new Uint8Array([87, 65, 86, 69]), 0, 4, 8);
17250
17250
  writeSync(fd, new Uint8Array([102, 109, 116, 32]), 0, 4, 12);
17251
17251
  writeSync(fd, new Uint8Array([BIT_DEPTH, 0, 0, 0]), 0, 4, 16);
17252
17252
  writeSync(fd, new Uint8Array([1, 0]), 0, 2, 20);
17253
17253
  writeSync(fd, new Uint8Array([NUMBER_OF_CHANNELS, 0]), 0, 2, 22);
17254
- writeSync(fd, new Uint8Array(numberTo32BiIntLittleEndian(sampleRate)), 0, 4, 24);
17255
- writeSync(fd, new Uint8Array(numberTo32BiIntLittleEndian(sampleRate * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 4, 28);
17256
- writeSync(fd, new Uint8Array(numberTo16BitLittleEndian(NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 2, 32);
17254
+ writeSync(fd, numberTo32BiIntLittleEndian(sampleRate), 0, 4, 24);
17255
+ writeSync(fd, numberTo32BiIntLittleEndian(sampleRate * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE), 0, 4, 28);
17256
+ writeSync(fd, numberTo16BitLittleEndian(NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE), 0, 2, 32);
17257
17257
  writeSync(fd, numberTo16BitLittleEndian(BIT_DEPTH), 0, 2, 34);
17258
17258
  writeSync(fd, new Uint8Array([100, 97, 116, 97]), 0, 4, 36);
17259
- writeSync(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedDataSize)), 0, 4, 40);
17259
+ writeSync(fd, numberTo32BiIntLittleEndian(0), 0, 4, 40);
17260
17260
  };
17261
17261
  const finish = async ({
17262
17262
  binariesDirectory,
@@ -17265,7 +17265,14 @@ var makeInlineAudioMixing = (dir, sampleRate) => {
17265
17265
  cancelSignal,
17266
17266
  sampleRate: finishSampleRate
17267
17267
  }) => {
17268
- for (const fileName of Object.keys(openFiles)) {
17268
+ for (const fileName of Object.keys(writtenHeaders)) {
17269
+ const fd = openFiles[fileName];
17270
+ const dataSize = Math.max(0, fs15.fstatSync(fd).size - WAV_HEADER_SIZE);
17271
+ writtenDataSizes[fileName] = dataSize;
17272
+ writeSync(fd, numberTo32BiIntLittleEndian(36 + dataSize), 0, 4, 4);
17273
+ writeSync(fd, numberTo32BiIntLittleEndian(dataSize), 0, 4, 40);
17274
+ fs15.closeSync(fd);
17275
+ delete openFiles[fileName];
17269
17276
  const frequency = toneFrequencies[fileName];
17270
17277
  if (frequency === 1) {
17271
17278
  continue;
@@ -17281,9 +17288,6 @@ var makeInlineAudioMixing = (dir, sampleRate) => {
17281
17288
  cancelSignal,
17282
17289
  sampleRate: finishSampleRate
17283
17290
  });
17284
- try {
17285
- fs15.closeSync(openFiles[fileName]);
17286
- } catch {}
17287
17291
  fs15.renameSync(tmpFile, fileName);
17288
17292
  }
17289
17293
  };
@@ -17295,19 +17299,20 @@ var makeInlineAudioMixing = (dir, sampleRate) => {
17295
17299
  trimLeftOffset,
17296
17300
  trimRightOffset
17297
17301
  }) => {
17298
- ensureAsset({
17299
- asset,
17300
- fps,
17301
- totalNumberOfFrames,
17302
- trimLeftOffset,
17303
- trimRightOffset
17304
- });
17302
+ ensureAsset(asset);
17305
17303
  const filePath = getFilePath(asset);
17306
17304
  if (toneFrequencies[filePath] !== undefined && toneFrequencies[filePath] !== asset.toneFrequency) {
17307
17305
  throw new Error(`toneFrequency must be the same across the entire audio, got ${asset.toneFrequency}, but before it was ${toneFrequencies[filePath]}`);
17308
17306
  }
17309
17307
  const fileDescriptor = openFiles[filePath];
17310
17308
  toneFrequencies[filePath] = asset.toneFrequency;
17309
+ const assetStartInVideo = asset.startInVideo ?? firstFrame;
17310
+ const firstFrameForAsset = Math.max(assetStartInVideo, firstFrame);
17311
+ const startInSamples = Math.max(0, Math.floor(correctFloatingPointError(((firstFrameForAsset - firstFrame) / fps - trimLeftOffset) * sampleRate)));
17312
+ if (startTimesInSamples[filePath] !== undefined && startTimesInSamples[filePath] !== startInSamples) {
17313
+ throw new Error(`The start time for inline audio asset ${asset.id} changed from ${startTimesInSamples[filePath]} to ${startInSamples} samples`);
17314
+ }
17315
+ startTimesInSamples[filePath] = startInSamples;
17311
17316
  let arr = new Int16Array(asset.audio);
17312
17317
  const isFirst = asset.frame === firstFrame;
17313
17318
  const isLast = asset.frame === totalNumberOfFrames + firstFrame - 1;
@@ -17319,9 +17324,10 @@ var makeInlineAudioMixing = (dir, sampleRate) => {
17319
17324
  if (isLast) {
17320
17325
  arr = arr.slice(0, arr.length + Math.ceil(correctFloatingPointError(samplesToShaveFromEnd)) * NUMBER_OF_CHANNELS);
17321
17326
  }
17322
- const positionInSeconds = (asset.frame - firstFrame) / fps - (isFirst ? 0 : trimLeftOffset);
17323
- const position = Math.floor(correctFloatingPointError(positionInSeconds * sampleRate)) * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE;
17324
- writeSync(fileDescriptor, arr, 0, arr.byteLength, 44 + position);
17327
+ const positionInRenderInSeconds = (asset.frame - firstFrame) / fps - (isFirst ? 0 : trimLeftOffset);
17328
+ const position = (Math.floor(correctFloatingPointError(positionInRenderInSeconds * sampleRate)) - startInSamples) * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE;
17329
+ writeSync(fileDescriptor, arr, 0, arr.byteLength, WAV_HEADER_SIZE + position);
17330
+ writtenDataSizes[filePath] = Math.max(writtenDataSizes[filePath], position + arr.byteLength);
17325
17331
  };
17326
17332
  return {
17327
17333
  cleanup,
@@ -21564,7 +21570,7 @@ var renderFrames = (options2) => {
21564
21570
  // src/render-media.ts
21565
21571
  import fs19 from "node:fs";
21566
21572
  import os10 from "node:os";
21567
- import path28 from "node:path";
21573
+ import path30 from "node:path";
21568
21574
  import { LicensingInternals } from "@remotion/licensing";
21569
21575
  import { NoReactInternals as NoReactInternals17 } from "remotion/no-react";
21570
21576
 
@@ -21844,7 +21850,8 @@ var getCodecName = ({
21844
21850
  crf,
21845
21851
  hardwareAcceleration,
21846
21852
  logLevel,
21847
- indent
21853
+ indent,
21854
+ onLog
21848
21855
  }) => {
21849
21856
  const preferredHwAcceleration = hardwareAcceleration === "required" || hardwareAcceleration === "if-possible";
21850
21857
  const unsupportedQualityOption = hasSpecifiedUnsupportedHardwareQualifySettings({
@@ -21857,8 +21864,17 @@ var getCodecName = ({
21857
21864
  }
21858
21865
  const warnAboutDisabledHardwareAcceleration = () => {
21859
21866
  if (hardwareAcceleration === "if-possible" && unsupportedQualityOption) {
21867
+ const message = `Hardware accelerated encoding disabled - "${unsupportedQualityOption}" option is not supported with hardware acceleration`;
21868
+ if (onLog !== null) {
21869
+ onLog({
21870
+ logLevel: "warn",
21871
+ previewString: message,
21872
+ tag: ""
21873
+ });
21874
+ return;
21875
+ }
21860
21876
  Log.warn({ indent, logLevel }, `${indent ? "" : `
21861
- `}Hardware accelerated encoding disabled - "${unsupportedQualityOption}" option is not supported with hardware acceleration`);
21877
+ `}${message}`);
21862
21878
  }
21863
21879
  };
21864
21880
  if (codec === "prores") {
@@ -21990,7 +22006,8 @@ var generateFfmpegArgs = ({
21990
22006
  crf,
21991
22007
  hardwareAcceleration,
21992
22008
  indent,
21993
- logLevel
22009
+ logLevel,
22010
+ onLog: null
21994
22011
  });
21995
22012
  if (encoderSettings === null) {
21996
22013
  throw new TypeError(`encoderSettings is null: ${JSON.stringify(codec)} (hwaccel = ${hardwareAcceleration})`);
@@ -22105,7 +22122,8 @@ var resolveHardwareAcceleration = ({
22105
22122
  logLevel,
22106
22123
  crf,
22107
22124
  encodingMaxRate,
22108
- encodingBufferSize
22125
+ encodingBufferSize,
22126
+ onLog
22109
22127
  }) => {
22110
22128
  if (hardwareAcceleration === "disable") {
22111
22129
  return "disable";
@@ -22117,12 +22135,20 @@ var resolveHardwareAcceleration = ({
22117
22135
  encodingMaxRate,
22118
22136
  encodingBufferSize,
22119
22137
  logLevel,
22120
- indent
22138
+ indent,
22139
+ onLog
22121
22140
  });
22122
22141
  if (preferred === null) {
22123
22142
  return hardwareAcceleration;
22124
22143
  }
22125
22144
  if (!preferred.hardwareAccelerated) {
22145
+ if (hardwareAcceleration === "if-possible" && hasSpecifiedUnsupportedHardwareQualifySettings({
22146
+ crf,
22147
+ encodingMaxRate,
22148
+ encodingBufferSize
22149
+ })) {
22150
+ return "disable";
22151
+ }
22126
22152
  return hardwareAcceleration;
22127
22153
  }
22128
22154
  const encoderAvailable = probeEncoderAvailability({
@@ -22209,7 +22235,8 @@ var prespawnFfmpeg = (options2) => {
22209
22235
  logLevel: options2.logLevel,
22210
22236
  crf: options2.crf,
22211
22237
  encodingMaxRate: options2.encodingMaxRate,
22212
- encodingBufferSize: options2.encodingBufferSize
22238
+ encodingBufferSize: options2.encodingBufferSize,
22239
+ onLog: options2.onLog
22213
22240
  });
22214
22241
  const ffmpegArgs = [
22215
22242
  ["-r", options2.fps],
@@ -22342,8 +22369,8 @@ var validateSelectedCodecAndProResCombination = ({
22342
22369
  };
22343
22370
 
22344
22371
  // src/stitch-frames-to-video.ts
22345
- import { cpSync as cpSync2, promises as promises4, rmSync as rmSync4 } from "node:fs";
22346
- import path27 from "node:path";
22372
+ import { cpSync as cpSync2, promises as promises5, rmSync as rmSync4 } from "node:fs";
22373
+ import path29 from "node:path";
22347
22374
 
22348
22375
  // src/convert-number-of-gif-loops-to-ffmpeg.ts
22349
22376
  var convertNumberOfGifLoopsToFfmpegSyntax = (loops) => {
@@ -22357,7 +22384,7 @@ var convertNumberOfGifLoopsToFfmpegSyntax = (loops) => {
22357
22384
  };
22358
22385
 
22359
22386
  // src/create-audio.ts
22360
- import path26 from "path";
22387
+ import path27 from "path";
22361
22388
 
22362
22389
  // src/resolve-asset-src.ts
22363
22390
  import url from "node:url";
@@ -22831,6 +22858,83 @@ var mergeAudioTrack = (options2) => {
22831
22858
  return limit2(mergeAudioTrackUnlimited, options2);
22832
22859
  };
22833
22860
 
22861
+ // src/merge-inline-audio-tracks.ts
22862
+ import path26 from "node:path";
22863
+ var MAX_INLINE_AUDIO_INPUTS = 10;
22864
+ var inlineAudioTrackToPreprocessedAudioTrack = ({
22865
+ track,
22866
+ relativeToInSamples,
22867
+ padToDurationInSamples
22868
+ }) => {
22869
+ const delayInSamples = Math.max(0, track.startInSamples - relativeToInSamples);
22870
+ const padAtEndInSamples = padToDurationInSamples === null ? 0 : Math.max(0, padToDurationInSamples - delayInSamples - track.durationInSamples);
22871
+ return {
22872
+ outName: track.outName,
22873
+ filter: {
22874
+ pad_start: delayInSamples === 0 ? null : `adelay=${new Array(3).fill(`${delayInSamples}S`).join("|")}`,
22875
+ pad_end: padAtEndInSamples === 0 ? null : `apad=pad_len=${padAtEndInSamples}`
22876
+ }
22877
+ };
22878
+ };
22879
+ var mergeInlineAudioTracks = async ({
22880
+ tracks,
22881
+ downloadMap,
22882
+ remotionRoot,
22883
+ indent,
22884
+ logLevel,
22885
+ binariesDirectory,
22886
+ cancelSignal,
22887
+ fps,
22888
+ chunkLengthInSeconds,
22889
+ sampleRate
22890
+ }) => {
22891
+ let currentTracks = tracks.sort((a, b) => a.startInSamples - b.startInSamples);
22892
+ let level = 0;
22893
+ while (currentTracks.length > 1) {
22894
+ const groups = chunk2(currentTracks, MAX_INLINE_AUDIO_INPUTS);
22895
+ currentTracks = await Promise.all(groups.map(async (group, index) => {
22896
+ if (group.length === 1) {
22897
+ return group[0];
22898
+ }
22899
+ const [{ startInSamples }] = group;
22900
+ const durationInSamples = Math.max(...group.map((track) => {
22901
+ return track.startInSamples - startInSamples + track.durationInSamples;
22902
+ }));
22903
+ const outName = path26.join(downloadMap.audioMixing, `inline-${level}-${index}.wav`);
22904
+ await mergeAudioTrack({
22905
+ files: group.map((track) => inlineAudioTrackToPreprocessedAudioTrack({
22906
+ track,
22907
+ relativeToInSamples: startInSamples,
22908
+ padToDurationInSamples: null
22909
+ })),
22910
+ outName,
22911
+ downloadMap,
22912
+ remotionRoot,
22913
+ indent,
22914
+ logLevel,
22915
+ binariesDirectory,
22916
+ cancelSignal,
22917
+ onProgress: () => {
22918
+ return;
22919
+ },
22920
+ fps,
22921
+ chunkLengthInSeconds,
22922
+ sampleRate
22923
+ });
22924
+ for (const track of group) {
22925
+ deleteDirectory(track.outName);
22926
+ }
22927
+ return {
22928
+ outName,
22929
+ startInSamples,
22930
+ durationInSamples
22931
+ };
22932
+ }));
22933
+ level++;
22934
+ }
22935
+ return currentTracks[0] ?? null;
22936
+ };
22937
+
22834
22938
  // src/assets/calculate-atempo.ts
22835
22939
  var calculateATempo = (playbackRate) => {
22836
22940
  if (playbackRate === 1) {
@@ -23204,7 +23308,7 @@ var createAudio = async ({
23204
23308
  onProgress(totalProgress);
23205
23309
  };
23206
23310
  const audioTracks = await Promise.all(assetPositions.map(async (asset, index) => {
23207
- const filterFile = path26.join(downloadMap.audioMixing, `${index}.wav`);
23311
+ const filterFile = path27.join(downloadMap.audioMixing, `${index}.wav`);
23208
23312
  const result = await preprocessAudioTrack({
23209
23313
  outName: filterFile,
23210
23314
  asset,
@@ -23237,20 +23341,31 @@ var createAudio = async ({
23237
23341
  sampleRate
23238
23342
  });
23239
23343
  const inlinedAudio = downloadMap.inlineAudioMixing.getListOfAssets();
23344
+ const mergedInlineAudio = await mergeInlineAudioTracks({
23345
+ tracks: inlinedAudio,
23346
+ downloadMap,
23347
+ remotionRoot,
23348
+ indent,
23349
+ logLevel,
23350
+ binariesDirectory,
23351
+ cancelSignal,
23352
+ fps,
23353
+ chunkLengthInSeconds,
23354
+ sampleRate
23355
+ });
23240
23356
  const preprocessed = [
23241
23357
  ...audioTracks.filter(truthy),
23242
- ...inlinedAudio.map((asset) => ({
23243
- outName: asset,
23244
- filter: {
23245
- filter: null,
23246
- pad_start: null,
23247
- pad_end: null
23248
- }
23249
- }))
23358
+ ...mergedInlineAudio ? [
23359
+ inlineAudioTrackToPreprocessedAudioTrack({
23360
+ track: mergedInlineAudio,
23361
+ relativeToInSamples: 0,
23362
+ padToDurationInSamples: Math.round(chunkLengthInSeconds * sampleRate)
23363
+ })
23364
+ ] : []
23250
23365
  ];
23251
- const merged = path26.join(downloadMap.audioPreprocessing, "merged.wav");
23366
+ const merged = path27.join(downloadMap.audioPreprocessing, "merged.wav");
23252
23367
  const extension = getExtensionFromAudioCodec(audioCodec);
23253
- const outName = path26.join(downloadMap.audioPreprocessing, `audio.${extension}`);
23368
+ const outName = path27.join(downloadMap.audioPreprocessing, `audio.${extension}`);
23254
23369
  await mergeAudioTrack({
23255
23370
  files: preprocessed,
23256
23371
  outName: merged,
@@ -23292,6 +23407,85 @@ var createAudio = async ({
23292
23407
  return outName;
23293
23408
  };
23294
23409
 
23410
+ // src/finalize-fast-start.ts
23411
+ import { randomUUID as randomUUID2 } from "node:crypto";
23412
+ import { promises as promises4 } from "node:fs";
23413
+ import path28 from "node:path";
23414
+ var finalizeFastStart = async ({
23415
+ input,
23416
+ output,
23417
+ muxer,
23418
+ force,
23419
+ indent,
23420
+ logLevel,
23421
+ binariesDirectory,
23422
+ cancelSignal
23423
+ }) => {
23424
+ let fastStartFile = null;
23425
+ for (let attempt = 0;attempt < 3; attempt++) {
23426
+ const candidate = path28.join(path28.dirname(output), `${path28.basename(output)}.${randomUUID2()}.remotion-in-progress`);
23427
+ const task = callFf({
23428
+ bin: "ffmpeg",
23429
+ args: [
23430
+ "-hide_banner",
23431
+ "-i",
23432
+ input,
23433
+ "-c",
23434
+ "copy",
23435
+ "-movflags",
23436
+ "faststart",
23437
+ "-y",
23438
+ "-f",
23439
+ muxer,
23440
+ candidate
23441
+ ],
23442
+ indent,
23443
+ logLevel,
23444
+ binariesDirectory,
23445
+ cancelSignal: cancelSignal ?? undefined
23446
+ });
23447
+ let stderr = "";
23448
+ task.stderr?.on("data", (data) => {
23449
+ stderr += data.toString();
23450
+ });
23451
+ try {
23452
+ await task;
23453
+ fastStartFile = candidate;
23454
+ break;
23455
+ } catch (error) {
23456
+ await promises4.rm(candidate, { force: true }).catch(() => {
23457
+ return;
23458
+ });
23459
+ const isReopenFailure = stderr.includes("Unable to re-open") && stderr.includes("output file for shifting data");
23460
+ if (!isReopenFailure || attempt === 2) {
23461
+ throw error;
23462
+ }
23463
+ Log.verbose({ indent, logLevel, tag: "stitchFramesToVideo()" }, `Retrying Fast Start finalization (attempt ${attempt + 2} of 3)`);
23464
+ }
23465
+ }
23466
+ if (fastStartFile === null) {
23467
+ throw new Error("Fast Start finalization did not produce an output file");
23468
+ }
23469
+ if (force) {
23470
+ await promises4.rename(fastStartFile, output);
23471
+ return;
23472
+ }
23473
+ try {
23474
+ await promises4.link(fastStartFile, output);
23475
+ } finally {
23476
+ await promises4.rm(fastStartFile, { force: true });
23477
+ }
23478
+ };
23479
+
23480
+ // src/get-fast-start-muxer.ts
23481
+ var getFastStartMuxer = (outputExtension) => {
23482
+ const normalizedExtension = outputExtension.toLowerCase();
23483
+ if (normalizedExtension === "mp4" || normalizedExtension === "mov") {
23484
+ return normalizedExtension;
23485
+ }
23486
+ return null;
23487
+ };
23488
+
23295
23489
  // src/make-metadata-args.ts
23296
23490
  import { VERSION as VERSION5 } from "remotion/version";
23297
23491
  var makeMetadataArgs = (metadata) => {
@@ -23384,7 +23578,8 @@ var innerStitchFramesToVideo = async ({
23384
23578
  separateAudioTo,
23385
23579
  metadata,
23386
23580
  hardwareAcceleration,
23387
- sampleRate
23581
+ sampleRate,
23582
+ onLog
23388
23583
  }, remotionRoot) => {
23389
23584
  validateDimension(height, "height", "passed to `stitchFramesToVideo()`");
23390
23585
  validateDimension(width, "width", "passed to `stitchFramesToVideo()`");
@@ -23430,7 +23625,10 @@ var innerStitchFramesToVideo = async ({
23430
23625
  setting: audioCodecSetting,
23431
23626
  separateAudioTo
23432
23627
  });
23433
- const tempFile = outputLocation ? null : path27.join(assetsInfo.downloadMap.stitchFrames, `out.${getFileExtensionFromCodec(codec, resolvedAudioCodec)}`);
23628
+ const tempFile = outputLocation ? null : path29.join(assetsInfo.downloadMap.stitchFrames, `out.${getFileExtensionFromCodec(codec, resolvedAudioCodec)}`);
23629
+ const outputExtension = (getExtensionOfFilename(outputLocation) ?? getFileExtensionFromCodec(codec, resolvedAudioCodec)).toLowerCase();
23630
+ const fastStartMuxer = getFastStartMuxer(outputExtension);
23631
+ const fastStartIntermediate = fastStartMuxer === null ? null : path29.join(assetsInfo.downloadMap.stitchFrames, "fast-start-intermediate.remotion-in-progress");
23434
23632
  Log.verbose({
23435
23633
  indent,
23436
23634
  logLevel,
@@ -23506,10 +23704,10 @@ var innerStitchFramesToVideo = async ({
23506
23704
  }
23507
23705
  cpSync2(audio, outputLocation ?? tempFile);
23508
23706
  onProgress?.(Math.round(assetsInfo.chunkLengthInSeconds * fps));
23509
- deleteDirectory(path27.dirname(audio));
23707
+ deleteDirectory(path29.dirname(audio));
23510
23708
  const file = await new Promise((resolve2, reject) => {
23511
23709
  if (tempFile) {
23512
- promises4.readFile(tempFile).then((f) => {
23710
+ promises5.readFile(tempFile).then((f) => {
23513
23711
  return resolve2(f);
23514
23712
  }).catch((e) => reject(e));
23515
23713
  } else {
@@ -23520,7 +23718,7 @@ var innerStitchFramesToVideo = async ({
23520
23718
  assetsInfo.downloadMap.allowCleanup();
23521
23719
  return Promise.resolve(file);
23522
23720
  }
23523
- const resolvedHardwareAcceleration = resolveHardwareAcceleration({
23721
+ const resolvedHardwareAcceleration = preEncodedFileLocation ? "disable" : resolveHardwareAcceleration({
23524
23722
  codec,
23525
23723
  hardwareAcceleration,
23526
23724
  binariesDirectory,
@@ -23528,7 +23726,8 @@ var innerStitchFramesToVideo = async ({
23528
23726
  logLevel,
23529
23727
  crf,
23530
23728
  encodingMaxRate: maxRate,
23531
- encodingBufferSize: bufferSize
23729
+ encodingBufferSize: bufferSize,
23730
+ onLog
23532
23731
  });
23533
23732
  const ffmpegArgs = [
23534
23733
  ...preEncodedFileLocation ? [["-i", preEncodedFileLocation]] : [
@@ -23557,11 +23756,11 @@ var innerStitchFramesToVideo = async ({
23557
23756
  indent,
23558
23757
  logLevel
23559
23758
  }),
23560
- codec === "h264" ? ["-movflags", "faststart"] : null,
23561
23759
  ["-map_metadata", "-1"],
23562
23760
  ...makeMetadataArgs(metadata ?? {}),
23563
- force ? "-y" : null,
23564
- outputLocation ?? tempFile
23761
+ force || fastStartIntermediate ? "-y" : null,
23762
+ fastStartIntermediate ? ["-f", fastStartMuxer] : null,
23763
+ fastStartIntermediate ?? outputLocation ?? tempFile
23565
23764
  ];
23566
23765
  const ffmpegString = ffmpegArgs.flat(2).filter(Boolean);
23567
23766
  const finalFfmpegString = ffmpegOverride ? ffmpegOverride({ type: "stitcher", args: ffmpegString }) : ffmpegString;
@@ -23606,29 +23805,37 @@ var innerStitchFramesToVideo = async ({
23606
23805
  if (!audio) {
23607
23806
  throw new Error(`\`separateAudioTo\` was set to ${JSON.stringify(separateAudioTo)}, but this render included no audio`);
23608
23807
  }
23609
- const finalDestination = path27.resolve(remotionRoot, separateAudioTo);
23808
+ const finalDestination = path29.resolve(remotionRoot, separateAudioTo);
23610
23809
  cpSync2(audio, finalDestination);
23611
23810
  rmSync4(audio);
23612
23811
  }
23613
- const result = await new Promise((resolve2, reject) => {
23812
+ await new Promise((resolve2, reject) => {
23614
23813
  task.once("close", (code, signal) => {
23615
23814
  if (code === 0) {
23616
- if (tempFile === null) {
23617
- cleanDownloadMap(assetsInfo.downloadMap);
23618
- return resolve2(null);
23619
- }
23620
- promises4.readFile(tempFile).then((f) => {
23621
- resolve2(f);
23622
- }).catch((e) => {
23623
- reject(e);
23624
- }).finally(() => {
23625
- cleanDownloadMap(assetsInfo.downloadMap);
23626
- });
23815
+ resolve2();
23627
23816
  } else {
23628
23817
  reject(new Error(`FFmpeg quit with code ${code} ${signal ? `(${signal})` : ""} The FFmpeg output was ${ffmpegStderr}`));
23629
23818
  }
23630
23819
  });
23631
23820
  });
23821
+ if (fastStartIntermediate && fastStartMuxer) {
23822
+ const destination = outputLocation ?? tempFile;
23823
+ if (destination === null) {
23824
+ throw new Error("Expected a Fast Start output destination");
23825
+ }
23826
+ await finalizeFastStart({
23827
+ input: fastStartIntermediate,
23828
+ output: path29.resolve(remotionRoot, destination),
23829
+ muxer: fastStartMuxer,
23830
+ force,
23831
+ indent,
23832
+ logLevel,
23833
+ binariesDirectory,
23834
+ cancelSignal
23835
+ });
23836
+ }
23837
+ const result = tempFile === null ? null : await promises5.readFile(tempFile);
23838
+ cleanDownloadMap(assetsInfo.downloadMap);
23632
23839
  assetsInfo.downloadMap.allowCleanup();
23633
23840
  return result;
23634
23841
  };
@@ -23711,7 +23918,8 @@ var stitchFramesToVideo = ({
23711
23918
  metadata: metadata ?? null,
23712
23919
  separateAudioTo: separateAudioTo ?? null,
23713
23920
  hardwareAcceleration: hardwareAcceleration ?? "disable",
23714
- sampleRate: sampleRate ?? 48000
23921
+ sampleRate: sampleRate ?? 48000,
23922
+ onLog: defaultOnLog
23715
23923
  });
23716
23924
  };
23717
23925
 
@@ -23975,7 +24183,7 @@ var internalRenderMediaRaw = ({
23975
24183
  separateAudioTo
23976
24184
  });
23977
24185
  }
23978
- const absoluteOutputLocation = outputLocation ? path28.resolve(process.cwd(), outputLocation) : null;
24186
+ const absoluteOutputLocation = outputLocation ? path30.resolve(process.cwd(), outputLocation) : null;
23979
24187
  validateScale(scale);
23980
24188
  validateFfmpegOverride(ffmpegOverride);
23981
24189
  validateEveryNthFrame(everyNthFrame);
@@ -24041,8 +24249,8 @@ var internalRenderMediaRaw = ({
24041
24249
  }
24042
24250
  const imageFormat = isAudioCodec(codec) ? "none" : provisionalImageFormat ?? compositionWithPossibleUnevenDimensions.defaultVideoImageFormat ?? DEFAULT_VIDEO_IMAGE_FORMAT;
24043
24251
  validateSelectedPixelFormatAndImageFormatCombination(pixelFormat, imageFormat);
24044
- const workingDir = fs19.mkdtempSync(path28.join(os10.tmpdir(), "react-motion-render"));
24045
- const preEncodedFileLocation = parallelEncoding ? path28.join(workingDir, "pre-encode." + getFileExtensionFromCodec(codec, audioCodec)) : null;
24252
+ const workingDir = fs19.mkdtempSync(path30.join(os10.tmpdir(), "react-motion-render"));
24253
+ const preEncodedFileLocation = parallelEncoding ? path30.join(workingDir, "pre-encode." + getFileExtensionFromCodec(codec, audioCodec)) : null;
24046
24254
  if (onCtrlCExit && workingDir) {
24047
24255
  onCtrlCExit(`Delete ${workingDir}`, () => deleteDirectory(workingDir));
24048
24256
  }
@@ -24115,7 +24323,8 @@ var internalRenderMediaRaw = ({
24115
24323
  gopSize,
24116
24324
  colorSpace,
24117
24325
  binariesDirectory,
24118
- hardwareAcceleration
24326
+ hardwareAcceleration,
24327
+ onLog
24119
24328
  });
24120
24329
  stitcherFfmpeg = preStitcher.task;
24121
24330
  }
@@ -24319,6 +24528,7 @@ var internalRenderMediaRaw = ({
24319
24528
  separateAudioTo,
24320
24529
  metadata,
24321
24530
  hardwareAcceleration,
24531
+ onLog,
24322
24532
  sampleRate
24323
24533
  });
24324
24534
  }).then((buffer2) => {
@@ -24382,7 +24592,7 @@ var internalRenderMediaRaw = ({
24382
24592
  reject(err);
24383
24593
  }).finally(() => {
24384
24594
  if (preEncodedFileLocation !== null && fs19.existsSync(preEncodedFileLocation)) {
24385
- deleteDirectory(path28.dirname(preEncodedFileLocation));
24595
+ deleteDirectory(path30.dirname(preEncodedFileLocation));
24386
24596
  }
24387
24597
  if (workingDir && fs19.existsSync(workingDir)) {
24388
24598
  deleteDirectory(workingDir);
@@ -24560,7 +24770,7 @@ var renderMedia = ({
24560
24770
 
24561
24771
  // src/render-still.ts
24562
24772
  import fs20, { statSync as statSync2 } from "node:fs";
24563
- import path29 from "node:path";
24773
+ import path31 from "node:path";
24564
24774
  import { LicensingInternals as LicensingInternals2 } from "@remotion/licensing";
24565
24775
  import { NoReactInternals as NoReactInternals18 } from "remotion/no-react";
24566
24776
  var innerRenderStill = async ({
@@ -24611,7 +24821,7 @@ var innerRenderStill = async ({
24611
24821
  });
24612
24822
  validatePuppeteerTimeout(timeoutInMilliseconds);
24613
24823
  validateScale(scale);
24614
- output = typeof output === "string" ? path29.resolve(process.cwd(), output) : null;
24824
+ output = typeof output === "string" ? path31.resolve(process.cwd(), output) : null;
24615
24825
  validateJpegQuality(jpegQuality);
24616
24826
  if (output) {
24617
24827
  if (fs20.existsSync(output)) {
@@ -25393,12 +25603,13 @@ var muxVideoAndAudio = async ({
25393
25603
  binariesDirectory,
25394
25604
  fps,
25395
25605
  cancelSignal,
25396
- addFaststart,
25397
25606
  metadata,
25398
25607
  numberOfGifLoops
25399
25608
  }) => {
25400
25609
  const startTime = Date.now();
25401
25610
  Log.verbose({ indent, logLevel }, "Muxing video and audio together");
25611
+ const outputExtension = getExtensionOfFilename(output);
25612
+ const fastStartMuxer = outputExtension ? getFastStartMuxer(outputExtension) : null;
25402
25613
  const command = [
25403
25614
  "-hide_banner",
25404
25615
  videoOutput ? "-i" : null,
@@ -25413,8 +25624,8 @@ var muxVideoAndAudio = async ({
25413
25624
  videoOutput ? String(fps) : null,
25414
25625
  numberOfGifLoops === null ? null : "-loop",
25415
25626
  numberOfGifLoops === null ? null : convertNumberOfGifLoopsToFfmpegSyntax(numberOfGifLoops),
25416
- addFaststart ? "-movflags" : null,
25417
- addFaststart ? "faststart" : null,
25627
+ fastStartMuxer ? "-movflags" : null,
25628
+ fastStartMuxer ? "faststart" : null,
25418
25629
  ...makeMetadataArgs(metadata ?? {}),
25419
25630
  "-y",
25420
25631
  output
@@ -25445,20 +25656,6 @@ var muxVideoAndAudio = async ({
25445
25656
  };
25446
25657
 
25447
25658
  // src/combine-chunks.ts
25448
- var codecSupportsFastStart = {
25449
- "h264-mkv": false,
25450
- "h264-ts": false,
25451
- h264: true,
25452
- h265: true,
25453
- av1: true,
25454
- aac: false,
25455
- gif: false,
25456
- mp3: false,
25457
- prores: false,
25458
- vp8: false,
25459
- vp9: false,
25460
- wav: false
25461
- };
25462
25659
  var REMOTION_FILELIST_TOKEN = "remotion-filelist";
25463
25660
  var internalCombineChunks = async ({
25464
25661
  outputLocation: output,
@@ -25564,7 +25761,6 @@ var internalCombineChunks = async ({
25564
25761
  binariesDirectory,
25565
25762
  fps,
25566
25763
  cancelSignal,
25567
- addFaststart: codecSupportsFastStart[codec],
25568
25764
  metadata,
25569
25765
  numberOfGifLoops
25570
25766
  });