@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.
@@ -43,6 +43,7 @@ const generateFfmpegArgs = ({ hasPreencoded, proResProfileName, pixelFormat, x26
43
43
  hardwareAcceleration,
44
44
  indent,
45
45
  logLevel,
46
+ onLog: null,
46
47
  });
47
48
  if (encoderSettings === null) {
48
49
  throw new TypeError(`encoderSettings is null: ${JSON.stringify(codec)} (hwaccel = ${hardwareAcceleration})`);
@@ -0,0 +1,12 @@
1
+ import type { FastStartMuxer } from './get-fast-start-muxer';
2
+ import type { CancelSignal } from './make-cancel-signal';
3
+ export declare const finalizeFastStart: ({ input, output, muxer, force, indent, logLevel, binariesDirectory, cancelSignal, }: {
4
+ input: string;
5
+ output: string;
6
+ muxer: FastStartMuxer;
7
+ force: boolean;
8
+ indent: boolean;
9
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
10
+ binariesDirectory: string | null;
11
+ cancelSignal: CancelSignal | null;
12
+ }) => Promise<void>;
@@ -0,0 +1,70 @@
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.finalizeFastStart = void 0;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const call_ffmpeg_1 = require("./call-ffmpeg");
11
+ const logger_1 = require("./logger");
12
+ const finalizeFastStart = async ({ input, output, muxer, force, indent, logLevel, binariesDirectory, cancelSignal, }) => {
13
+ var _a;
14
+ let fastStartFile = null;
15
+ for (let attempt = 0; attempt < 3; attempt++) {
16
+ const candidate = node_path_1.default.join(node_path_1.default.dirname(output), `${node_path_1.default.basename(output)}.${(0, node_crypto_1.randomUUID)()}.remotion-in-progress`);
17
+ const task = (0, call_ffmpeg_1.callFf)({
18
+ bin: 'ffmpeg',
19
+ args: [
20
+ '-hide_banner',
21
+ '-i',
22
+ input,
23
+ '-c',
24
+ 'copy',
25
+ '-movflags',
26
+ 'faststart',
27
+ '-y',
28
+ '-f',
29
+ muxer,
30
+ candidate,
31
+ ],
32
+ indent,
33
+ logLevel,
34
+ binariesDirectory,
35
+ cancelSignal: cancelSignal !== null && cancelSignal !== void 0 ? cancelSignal : undefined,
36
+ });
37
+ let stderr = '';
38
+ (_a = task.stderr) === null || _a === void 0 ? void 0 : _a.on('data', (data) => {
39
+ stderr += data.toString();
40
+ });
41
+ try {
42
+ await task;
43
+ fastStartFile = candidate;
44
+ break;
45
+ }
46
+ catch (error) {
47
+ await node_fs_1.promises.rm(candidate, { force: true }).catch(() => undefined);
48
+ const isReopenFailure = stderr.includes('Unable to re-open') &&
49
+ stderr.includes('output file for shifting data');
50
+ if (!isReopenFailure || attempt === 2) {
51
+ throw error;
52
+ }
53
+ logger_1.Log.verbose({ indent, logLevel, tag: 'stitchFramesToVideo()' }, `Retrying Fast Start finalization (attempt ${attempt + 2} of 3)`);
54
+ }
55
+ }
56
+ if (fastStartFile === null) {
57
+ throw new Error('Fast Start finalization did not produce an output file');
58
+ }
59
+ if (force) {
60
+ await node_fs_1.promises.rename(fastStartFile, output);
61
+ return;
62
+ }
63
+ try {
64
+ await node_fs_1.promises.link(fastStartFile, output);
65
+ }
66
+ finally {
67
+ await node_fs_1.promises.rm(fastStartFile, { force: true });
68
+ }
69
+ };
70
+ exports.finalizeFastStart = finalizeFastStart;
@@ -1,3 +1,4 @@
1
+ import type { OnLog } from './browser/BrowserPage';
1
2
  export type CodecSettings = {
2
3
  encoderName: string;
3
4
  hardwareAccelerated: boolean;
@@ -7,7 +8,7 @@ export declare const hasSpecifiedUnsupportedHardwareQualifySettings: ({ encoding
7
8
  encodingBufferSize: string | null;
8
9
  crf: unknown;
9
10
  }) => "crf" | "encodingBufferSize" | "encodingMaxRate" | null;
10
- export declare const getCodecName: ({ codec, encodingMaxRate, encodingBufferSize, crf, hardwareAcceleration, logLevel, indent, }: {
11
+ export declare const getCodecName: ({ codec, encodingMaxRate, encodingBufferSize, crf, hardwareAcceleration, logLevel, indent, onLog, }: {
11
12
  codec: "aac" | "av1" | "gif" | "h264" | "h264-mkv" | "h264-ts" | "h265" | "mp3" | "prores" | "vp8" | "vp9" | "wav";
12
13
  hardwareAcceleration: "disable" | "if-possible" | "required";
13
14
  encodingMaxRate: string | null;
@@ -15,4 +16,5 @@ export declare const getCodecName: ({ codec, encodingMaxRate, encodingBufferSize
15
16
  crf: unknown;
16
17
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
17
18
  indent: boolean;
19
+ onLog: OnLog | null;
18
20
  }) => CodecSettings | null;
@@ -15,7 +15,7 @@ const hasSpecifiedUnsupportedHardwareQualifySettings = ({ encodingMaxRate, encod
15
15
  return null;
16
16
  };
17
17
  exports.hasSpecifiedUnsupportedHardwareQualifySettings = hasSpecifiedUnsupportedHardwareQualifySettings;
18
- const getCodecName = ({ codec, encodingMaxRate, encodingBufferSize, crf, hardwareAcceleration, logLevel, indent, }) => {
18
+ const getCodecName = ({ codec, encodingMaxRate, encodingBufferSize, crf, hardwareAcceleration, logLevel, indent, onLog, }) => {
19
19
  const preferredHwAcceleration = hardwareAcceleration === 'required' ||
20
20
  hardwareAcceleration === 'if-possible';
21
21
  const unsupportedQualityOption = (0, exports.hasSpecifiedUnsupportedHardwareQualifySettings)({
@@ -28,7 +28,16 @@ const getCodecName = ({ codec, encodingMaxRate, encodingBufferSize, crf, hardwar
28
28
  }
29
29
  const warnAboutDisabledHardwareAcceleration = () => {
30
30
  if (hardwareAcceleration === 'if-possible' && unsupportedQualityOption) {
31
- logger_1.Log.warn({ indent, logLevel }, `${indent ? '' : '\n'}Hardware accelerated encoding disabled - "${unsupportedQualityOption}" option is not supported with hardware acceleration`);
31
+ const message = `Hardware accelerated encoding disabled - "${unsupportedQualityOption}" option is not supported with hardware acceleration`;
32
+ if (onLog !== null) {
33
+ onLog({
34
+ logLevel: 'warn',
35
+ previewString: message,
36
+ tag: '',
37
+ });
38
+ return;
39
+ }
40
+ logger_1.Log.warn({ indent, logLevel }, `${indent ? '' : '\n'}${message}`);
32
41
  }
33
42
  };
34
43
  if (codec === 'prores') {
@@ -0,0 +1,2 @@
1
+ export type FastStartMuxer = 'mov' | 'mp4';
2
+ export declare const getFastStartMuxer: (outputExtension: string) => FastStartMuxer | null;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFastStartMuxer = void 0;
4
+ const getFastStartMuxer = (outputExtension) => {
5
+ const normalizedExtension = outputExtension.toLowerCase();
6
+ if (normalizedExtension === 'mp4' || normalizedExtension === 'mov') {
7
+ return normalizedExtension;
8
+ }
9
+ return null;
10
+ };
11
+ exports.getFastStartMuxer = getFastStartMuxer;
@@ -0,0 +1,21 @@
1
+ import type { DownloadMap } from './assets/download-map';
2
+ import type { InlineAudioTrack } from './assets/inline-audio-mixing';
3
+ import type { CancelSignal } from './make-cancel-signal';
4
+ import type { PreprocessedAudioTrack } from './preprocess-audio-track';
5
+ export declare const inlineAudioTrackToPreprocessedAudioTrack: ({ track, relativeToInSamples, padToDurationInSamples, }: {
6
+ track: InlineAudioTrack;
7
+ relativeToInSamples: number;
8
+ padToDurationInSamples: number | null;
9
+ }) => PreprocessedAudioTrack;
10
+ export declare const mergeInlineAudioTracks: ({ tracks, downloadMap, remotionRoot, indent, logLevel, binariesDirectory, cancelSignal, fps, chunkLengthInSeconds, sampleRate, }: {
11
+ tracks: InlineAudioTrack[];
12
+ downloadMap: DownloadMap;
13
+ remotionRoot: string;
14
+ indent: boolean;
15
+ logLevel: "error" | "info" | "trace" | "verbose" | "warn";
16
+ binariesDirectory: string | null;
17
+ cancelSignal: CancelSignal | undefined;
18
+ fps: number;
19
+ chunkLengthInSeconds: number;
20
+ sampleRate: number;
21
+ }) => Promise<InlineAudioTrack | null>;
@@ -0,0 +1,74 @@
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.mergeInlineAudioTracks = exports.inlineAudioTrackToPreprocessedAudioTrack = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const chunk_1 = require("./chunk");
9
+ const delete_directory_1 = require("./delete-directory");
10
+ const merge_audio_track_1 = require("./merge-audio-track");
11
+ const MAX_INLINE_AUDIO_INPUTS = 10;
12
+ const inlineAudioTrackToPreprocessedAudioTrack = ({ track, relativeToInSamples, padToDurationInSamples, }) => {
13
+ const delayInSamples = Math.max(0, track.startInSamples - relativeToInSamples);
14
+ const padAtEndInSamples = padToDurationInSamples === null
15
+ ? 0
16
+ : Math.max(0, padToDurationInSamples - delayInSamples - track.durationInSamples);
17
+ return {
18
+ outName: track.outName,
19
+ filter: {
20
+ pad_start: delayInSamples === 0
21
+ ? null
22
+ : `adelay=${new Array(3).fill(`${delayInSamples}S`).join('|')}`,
23
+ pad_end: padAtEndInSamples === 0 ? null : `apad=pad_len=${padAtEndInSamples}`,
24
+ },
25
+ };
26
+ };
27
+ exports.inlineAudioTrackToPreprocessedAudioTrack = inlineAudioTrackToPreprocessedAudioTrack;
28
+ const mergeInlineAudioTracks = async ({ tracks, downloadMap, remotionRoot, indent, logLevel, binariesDirectory, cancelSignal, fps, chunkLengthInSeconds, sampleRate, }) => {
29
+ var _a;
30
+ let currentTracks = tracks.sort((a, b) => a.startInSamples - b.startInSamples);
31
+ let level = 0;
32
+ while (currentTracks.length > 1) {
33
+ const groups = (0, chunk_1.chunk)(currentTracks, MAX_INLINE_AUDIO_INPUTS);
34
+ currentTracks = await Promise.all(groups.map(async (group, index) => {
35
+ if (group.length === 1) {
36
+ return group[0];
37
+ }
38
+ const [{ startInSamples }] = group;
39
+ const durationInSamples = Math.max(...group.map((track) => {
40
+ return (track.startInSamples - startInSamples + track.durationInSamples);
41
+ }));
42
+ const outName = node_path_1.default.join(downloadMap.audioMixing, `inline-${level}-${index}.wav`);
43
+ await (0, merge_audio_track_1.mergeAudioTrack)({
44
+ files: group.map((track) => (0, exports.inlineAudioTrackToPreprocessedAudioTrack)({
45
+ track,
46
+ relativeToInSamples: startInSamples,
47
+ padToDurationInSamples: null,
48
+ })),
49
+ outName,
50
+ downloadMap,
51
+ remotionRoot,
52
+ indent,
53
+ logLevel,
54
+ binariesDirectory,
55
+ cancelSignal,
56
+ onProgress: () => undefined,
57
+ fps,
58
+ chunkLengthInSeconds,
59
+ sampleRate,
60
+ });
61
+ for (const track of group) {
62
+ (0, delete_directory_1.deleteDirectory)(track.outName);
63
+ }
64
+ return {
65
+ outName,
66
+ startInSamples,
67
+ durationInSamples,
68
+ };
69
+ }));
70
+ level++;
71
+ }
72
+ return (_a = currentTracks[0]) !== null && _a !== void 0 ? _a : null;
73
+ };
74
+ exports.mergeInlineAudioTracks = mergeInlineAudioTracks;
@@ -1,5 +1,5 @@
1
1
  import type { CancelSignal } from './make-cancel-signal';
2
- export declare const muxVideoAndAudio: ({ videoOutput, audioOutput, output, indent, logLevel, onProgress, binariesDirectory, fps, cancelSignal, addFaststart, metadata, numberOfGifLoops, }: {
2
+ export declare const muxVideoAndAudio: ({ videoOutput, audioOutput, output, indent, logLevel, onProgress, binariesDirectory, fps, cancelSignal, metadata, numberOfGifLoops, }: {
3
3
  videoOutput: string | null;
4
4
  audioOutput: string | null;
5
5
  output: string;
@@ -9,7 +9,6 @@ export declare const muxVideoAndAudio: ({ videoOutput, audioOutput, output, inde
9
9
  fps: number;
10
10
  onProgress: (p: number) => void;
11
11
  cancelSignal: CancelSignal | undefined;
12
- addFaststart: boolean;
13
12
  metadata?: Record<string, string> | null | undefined;
14
13
  numberOfGifLoops: number | null;
15
14
  }) => Promise<void>;
@@ -3,14 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.muxVideoAndAudio = void 0;
4
4
  const call_ffmpeg_1 = require("./call-ffmpeg");
5
5
  const convert_number_of_gif_loops_to_ffmpeg_1 = require("./convert-number-of-gif-loops-to-ffmpeg");
6
+ const get_extension_of_filename_1 = require("./get-extension-of-filename");
7
+ const get_fast_start_muxer_1 = require("./get-fast-start-muxer");
6
8
  const logger_1 = require("./logger");
7
9
  const make_metadata_args_1 = require("./make-metadata-args");
8
10
  const parse_ffmpeg_progress_1 = require("./parse-ffmpeg-progress");
9
11
  const truthy_1 = require("./truthy");
10
- const muxVideoAndAudio = async ({ videoOutput, audioOutput, output, indent, logLevel, onProgress, binariesDirectory, fps, cancelSignal, addFaststart, metadata, numberOfGifLoops, }) => {
12
+ const muxVideoAndAudio = async ({ videoOutput, audioOutput, output, indent, logLevel, onProgress, binariesDirectory, fps, cancelSignal, metadata, numberOfGifLoops, }) => {
11
13
  var _a;
12
14
  const startTime = Date.now();
13
15
  logger_1.Log.verbose({ indent, logLevel }, 'Muxing video and audio together');
16
+ const outputExtension = (0, get_extension_of_filename_1.getExtensionOfFilename)(output);
17
+ const fastStartMuxer = outputExtension
18
+ ? (0, get_fast_start_muxer_1.getFastStartMuxer)(outputExtension)
19
+ : null;
14
20
  const command = [
15
21
  '-hide_banner',
16
22
  videoOutput ? '-i' : null,
@@ -30,8 +36,8 @@ const muxVideoAndAudio = async ({ videoOutput, audioOutput, output, indent, logL
30
36
  numberOfGifLoops === null
31
37
  ? null
32
38
  : (0, convert_number_of_gif_loops_to_ffmpeg_1.convertNumberOfGifLoopsToFfmpegSyntax)(numberOfGifLoops),
33
- addFaststart ? '-movflags' : null,
34
- addFaststart ? 'faststart' : null,
39
+ fastStartMuxer ? '-movflags' : null,
40
+ fastStartMuxer ? 'faststart' : null,
35
41
  ...(0, make_metadata_args_1.makeMetadataArgs)(metadata !== null && metadata !== void 0 ? metadata : {}),
36
42
  '-y',
37
43
  output,
@@ -1,4 +1,5 @@
1
1
  import type { _InternalTypes } from 'remotion';
2
+ import type { OnLog } from './browser/BrowserPage';
2
3
  import type { HardwareAccelerationOption } from './client';
3
4
  import type { Codec } from './codec';
4
5
  import type { FfmpegOverrideFn } from './ffmpeg-override';
@@ -42,6 +43,7 @@ type PreStitcherOptions = {
42
43
  colorSpace: ColorSpace | null;
43
44
  binariesDirectory: string | null;
44
45
  hardwareAcceleration: HardwareAccelerationOption;
46
+ onLog: OnLog;
45
47
  };
46
48
  export declare const prespawnFfmpeg: (options: PreStitcherOptions) => {
47
49
  task: import("execa").ExecaChildProcess<string>;
@@ -39,6 +39,7 @@ const prespawnFfmpeg = (options) => {
39
39
  crf: options.crf,
40
40
  encodingMaxRate: options.encodingMaxRate,
41
41
  encodingBufferSize: options.encodingBufferSize,
42
+ onLog: options.onLog,
42
43
  });
43
44
  const ffmpegArgs = [
44
45
  ['-r', options.fps],
@@ -1,3 +1,4 @@
1
+ import type { OnLog } from './browser/BrowserPage';
1
2
  /**
2
3
  * Probes FFmpeg to check if a specific encoder is available.
3
4
  * Returns true if the encoder is supported, false otherwise.
@@ -14,7 +15,7 @@ export declare const probeEncoderAvailability: ({ encoderName, binariesDirectory
14
15
  * - `if-possible` mode: falls back to software encoding
15
16
  * - `required` mode: throws a clear error
16
17
  */
17
- export declare const resolveHardwareAcceleration: ({ codec, hardwareAcceleration, binariesDirectory, indent, logLevel, crf, encodingMaxRate, encodingBufferSize, }: {
18
+ export declare const resolveHardwareAcceleration: ({ codec, hardwareAcceleration, binariesDirectory, indent, logLevel, crf, encodingMaxRate, encodingBufferSize, onLog, }: {
18
19
  codec: "aac" | "av1" | "gif" | "h264" | "h264-mkv" | "h264-ts" | "h265" | "mp3" | "prores" | "vp8" | "vp9" | "wav";
19
20
  hardwareAcceleration: "disable" | "if-possible" | "required";
20
21
  binariesDirectory: string | null;
@@ -23,4 +24,5 @@ export declare const resolveHardwareAcceleration: ({ codec, hardwareAcceleration
23
24
  crf: unknown;
24
25
  encodingMaxRate: string | null;
25
26
  encodingBufferSize: string | null;
27
+ onLog: OnLog | null;
26
28
  }) => "disable" | "if-possible" | "required";
@@ -53,7 +53,7 @@ exports.probeEncoderAvailability = probeEncoderAvailability;
53
53
  * - `if-possible` mode: falls back to software encoding
54
54
  * - `required` mode: throws a clear error
55
55
  */
56
- const resolveHardwareAcceleration = ({ codec, hardwareAcceleration, binariesDirectory, indent, logLevel, crf, encodingMaxRate, encodingBufferSize, }) => {
56
+ const resolveHardwareAcceleration = ({ codec, hardwareAcceleration, binariesDirectory, indent, logLevel, crf, encodingMaxRate, encodingBufferSize, onLog, }) => {
57
57
  if (hardwareAcceleration === 'disable') {
58
58
  return 'disable';
59
59
  }
@@ -65,6 +65,7 @@ const resolveHardwareAcceleration = ({ codec, hardwareAcceleration, binariesDire
65
65
  encodingBufferSize,
66
66
  logLevel,
67
67
  indent,
68
+ onLog,
68
69
  });
69
70
  // Audio codecs return null, no probing needed
70
71
  if (preferred === null) {
@@ -72,6 +73,14 @@ const resolveHardwareAcceleration = ({ codec, hardwareAcceleration, binariesDire
72
73
  }
73
74
  // Only probe if getCodecName selected a hardware-accelerated encoder
74
75
  if (!preferred.hardwareAccelerated) {
76
+ if (hardwareAcceleration === 'if-possible' &&
77
+ (0, get_codec_name_1.hasSpecifiedUnsupportedHardwareQualifySettings)({
78
+ crf,
79
+ encodingMaxRate,
80
+ encodingBufferSize,
81
+ })) {
82
+ return 'disable';
83
+ }
75
84
  return hardwareAcceleration;
76
85
  }
77
86
  const encoderAvailable = (0, exports.probeEncoderAvailability)({
@@ -255,6 +255,7 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
255
255
  colorSpace,
256
256
  binariesDirectory,
257
257
  hardwareAcceleration,
258
+ onLog,
258
259
  });
259
260
  stitcherFfmpeg = preStitcher.task;
260
261
  }
@@ -478,6 +479,7 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
478
479
  separateAudioTo,
479
480
  metadata,
480
481
  hardwareAcceleration,
482
+ onLog,
481
483
  sampleRate,
482
484
  });
483
485
  })
@@ -1,6 +1,7 @@
1
1
  import type { _InternalTypes } from 'remotion';
2
2
  import type { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
3
  import type { RenderAssetInfo } from './assets/download-map';
4
+ import type { OnLog } from './browser/BrowserPage';
4
5
  import type { Codec } from './codec';
5
6
  import type { FfmpegOverrideFn } from './ffmpeg-override';
6
7
  import type { LogLevel } from './log-level';
@@ -44,6 +45,7 @@ type InternalStitchFramesToVideoOptions = {
44
45
  binariesDirectory: string | null;
45
46
  metadata: Record<string, string> | null;
46
47
  sampleRate: number;
48
+ onLog: OnLog;
47
49
  } & ToOptions<typeof optionsMap.stitchFramesToVideo>;
48
50
  export type StitchFramesToVideoOptions = {
49
51
  fps: number;
@@ -13,10 +13,14 @@ const codec_supports_media_1 = require("./codec-supports-media");
13
13
  const convert_number_of_gif_loops_to_ffmpeg_1 = require("./convert-number-of-gif-loops-to-ffmpeg");
14
14
  const create_audio_1 = require("./create-audio");
15
15
  const crf_1 = require("./crf");
16
+ const default_on_log_1 = require("./default-on-log");
16
17
  const delete_directory_1 = require("./delete-directory");
17
18
  const ffmpeg_args_1 = require("./ffmpeg-args");
19
+ const finalize_fast_start_1 = require("./finalize-fast-start");
18
20
  const find_closest_package_json_1 = require("./find-closest-package-json");
19
21
  const get_extension_from_codec_1 = require("./get-extension-from-codec");
22
+ const get_extension_of_filename_1 = require("./get-extension-of-filename");
23
+ const get_fast_start_muxer_1 = require("./get-fast-start-muxer");
20
24
  const get_prores_profile_name_1 = require("./get-prores-profile-name");
21
25
  const logger_1 = require("./logger");
22
26
  const make_cancel_signal_1 = require("./make-cancel-signal");
@@ -32,8 +36,9 @@ const render_has_audio_1 = require("./render-has-audio");
32
36
  const validate_1 = require("./validate");
33
37
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
34
38
  const validate_videobitrate_1 = require("./validate-videobitrate");
35
- const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec: audioCodecSetting, cancelSignal, codec, crf, gopSize, enforceAudioTrack, ffmpegOverride, force, fps, height, indent, muted, onDownload, outputLocation, pixelFormat, preEncodedFileLocation, preferLossless, proResProfile, logLevel, videoBitrate, maxRate, bufferSize, width, numberOfGifLoops, onProgress, x264Preset, colorSpace, binariesDirectory, separateAudioTo, metadata, hardwareAcceleration, sampleRate, }, remotionRoot) => {
39
+ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec: audioCodecSetting, cancelSignal, codec, crf, gopSize, enforceAudioTrack, ffmpegOverride, force, fps, height, indent, muted, onDownload, outputLocation, pixelFormat, preEncodedFileLocation, preferLossless, proResProfile, logLevel, videoBitrate, maxRate, bufferSize, width, numberOfGifLoops, onProgress, x264Preset, colorSpace, binariesDirectory, separateAudioTo, metadata, hardwareAcceleration, sampleRate, onLog, }, remotionRoot) => {
36
40
  var _a;
41
+ var _b, _c;
37
42
  (0, validate_1.validateDimension)(height, 'height', 'passed to `stitchFramesToVideo()`');
38
43
  (0, validate_1.validateDimension)(width, 'width', 'passed to `stitchFramesToVideo()`');
39
44
  (0, validate_even_dimensions_with_codec_1.validateEvenDimensionsWithCodec)({
@@ -82,6 +87,13 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
82
87
  const tempFile = outputLocation
83
88
  ? null
84
89
  : node_path_1.default.join(assetsInfo.downloadMap.stitchFrames, `out.${(0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, resolvedAudioCodec)}`);
90
+ const outputExtension = ((_b = (0, get_extension_of_filename_1.getExtensionOfFilename)(outputLocation)) !== null && _b !== void 0 ? _b : (0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, resolvedAudioCodec)).toLowerCase();
91
+ const fastStartMuxer = (0, get_fast_start_muxer_1.getFastStartMuxer)(outputExtension);
92
+ // Fast Start reopens its output for an in-place second pass. Keep that work
93
+ // away from the public output path so Windows file observers cannot lock it.
94
+ const fastStartIntermediate = fastStartMuxer === null
95
+ ? null
96
+ : node_path_1.default.join(assetsInfo.downloadMap.stitchFrames, 'fast-start-intermediate.remotion-in-progress');
85
97
  logger_1.Log.verbose({
86
98
  indent,
87
99
  logLevel,
@@ -178,16 +190,20 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
178
190
  assetsInfo.downloadMap.allowCleanup();
179
191
  return Promise.resolve(file);
180
192
  }
181
- const resolvedHardwareAcceleration = (0, probe_encoder_1.resolveHardwareAcceleration)({
182
- codec,
183
- hardwareAcceleration,
184
- binariesDirectory,
185
- indent: indent !== null && indent !== void 0 ? indent : false,
186
- logLevel,
187
- crf,
188
- encodingMaxRate: maxRate,
189
- encodingBufferSize: bufferSize,
190
- });
193
+ // Parallel encoding already resolved the encoder in the pre-stitcher.
194
+ const resolvedHardwareAcceleration = preEncodedFileLocation
195
+ ? 'disable'
196
+ : (0, probe_encoder_1.resolveHardwareAcceleration)({
197
+ codec,
198
+ hardwareAcceleration,
199
+ binariesDirectory,
200
+ indent: indent !== null && indent !== void 0 ? indent : false,
201
+ logLevel,
202
+ crf,
203
+ encodingMaxRate: maxRate,
204
+ encodingBufferSize: bufferSize,
205
+ onLog,
206
+ });
191
207
  const ffmpegArgs = [
192
208
  ...(preEncodedFileLocation
193
209
  ? [['-i', preEncodedFileLocation]]
@@ -221,12 +237,12 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
221
237
  indent,
222
238
  logLevel,
223
239
  }),
224
- codec === 'h264' ? ['-movflags', 'faststart'] : null,
225
240
  // Ignore metadata that may come from remote media
226
241
  ['-map_metadata', '-1'],
227
242
  ...(0, make_metadata_args_1.makeMetadataArgs)(metadata !== null && metadata !== void 0 ? metadata : {}),
228
- force ? '-y' : null,
229
- outputLocation !== null && outputLocation !== void 0 ? outputLocation : tempFile,
243
+ force || fastStartIntermediate ? '-y' : null,
244
+ fastStartIntermediate ? ['-f', fastStartMuxer] : null,
245
+ (_c = fastStartIntermediate !== null && fastStartIntermediate !== void 0 ? fastStartIntermediate : outputLocation) !== null && _c !== void 0 ? _c : tempFile,
230
246
  ];
231
247
  const ffmpegString = ffmpegArgs.flat(2).filter(Boolean);
232
248
  const finalFfmpegString = ffmpegOverride
@@ -282,30 +298,34 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
282
298
  (0, node_fs_1.cpSync)(audio, finalDestination);
283
299
  (0, node_fs_1.rmSync)(audio);
284
300
  }
285
- const result = await new Promise((resolve, reject) => {
301
+ await new Promise((resolve, reject) => {
286
302
  task.once('close', (code, signal) => {
287
303
  if (code === 0) {
288
- if (tempFile === null) {
289
- (0, download_map_1.cleanDownloadMap)(assetsInfo.downloadMap);
290
- return resolve(null);
291
- }
292
- node_fs_1.promises
293
- .readFile(tempFile)
294
- .then((f) => {
295
- resolve(f);
296
- })
297
- .catch((e) => {
298
- reject(e);
299
- })
300
- .finally(() => {
301
- (0, download_map_1.cleanDownloadMap)(assetsInfo.downloadMap);
302
- });
304
+ resolve();
303
305
  }
304
306
  else {
305
307
  reject(new Error(`FFmpeg quit with code ${code} ${signal ? `(${signal})` : ''} The FFmpeg output was ${ffmpegStderr}`));
306
308
  }
307
309
  });
308
310
  });
311
+ if (fastStartIntermediate && fastStartMuxer) {
312
+ const destination = outputLocation !== null && outputLocation !== void 0 ? outputLocation : tempFile;
313
+ if (destination === null) {
314
+ throw new Error('Expected a Fast Start output destination');
315
+ }
316
+ await (0, finalize_fast_start_1.finalizeFastStart)({
317
+ input: fastStartIntermediate,
318
+ output: node_path_1.default.resolve(remotionRoot, destination),
319
+ muxer: fastStartMuxer,
320
+ force,
321
+ indent,
322
+ logLevel,
323
+ binariesDirectory,
324
+ cancelSignal,
325
+ });
326
+ }
327
+ const result = tempFile === null ? null : await node_fs_1.promises.readFile(tempFile);
328
+ (0, download_map_1.cleanDownloadMap)(assetsInfo.downloadMap);
309
329
  assetsInfo.downloadMap.allowCleanup();
310
330
  return result;
311
331
  };
@@ -363,6 +383,7 @@ const stitchFramesToVideo = ({ assetsInfo, force, fps, height, width, audioBitra
363
383
  separateAudioTo: separateAudioTo !== null && separateAudioTo !== void 0 ? separateAudioTo : null,
364
384
  hardwareAcceleration: hardwareAcceleration !== null && hardwareAcceleration !== void 0 ? hardwareAcceleration : 'disable',
365
385
  sampleRate: sampleRate !== null && sampleRate !== void 0 ? sampleRate : 48000,
386
+ onLog: default_on_log_1.defaultOnLog,
366
387
  });
367
388
  };
368
389
  exports.stitchFramesToVideo = stitchFramesToVideo;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/renderer"
4
4
  },
5
5
  "name": "@remotion/renderer",
6
- "version": "4.0.516",
6
+ "version": "4.0.518",
7
7
  "description": "Render Remotion videos using Node.js or Bun",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "execa": "5.1.1",
25
- "remotion": "4.0.516",
26
- "@remotion/streaming": "4.0.516",
25
+ "remotion": "4.0.518",
26
+ "@remotion/streaming": "4.0.518",
27
27
  "source-map": "0.8.0",
28
28
  "ws": "8.21.0",
29
- "@remotion/licensing": "4.0.516"
29
+ "@remotion/licensing": "4.0.518"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": ">=16.8.0",
@@ -40,19 +40,19 @@
40
40
  "react-dom": "19.2.3",
41
41
  "@typescript/native-preview": "7.0.0-dev.20260217.1",
42
42
  "@types/ws": "8.5.10",
43
- "@remotion/example-videos": "4.0.516",
44
- "@remotion/eslint-config-internal": "4.0.516",
43
+ "@remotion/example-videos": "4.0.518",
44
+ "@remotion/eslint-config-internal": "4.0.518",
45
45
  "eslint": "9.19.0",
46
46
  "@types/node": "20.12.14"
47
47
  },
48
48
  "optionalDependencies": {
49
- "@remotion/compositor-darwin-arm64": "4.0.516",
50
- "@remotion/compositor-darwin-x64": "4.0.516",
51
- "@remotion/compositor-linux-arm64-gnu": "4.0.516",
52
- "@remotion/compositor-linux-arm64-musl": "4.0.516",
53
- "@remotion/compositor-linux-x64-gnu": "4.0.516",
54
- "@remotion/compositor-linux-x64-musl": "4.0.516",
55
- "@remotion/compositor-win32-x64-msvc": "4.0.516"
49
+ "@remotion/compositor-darwin-arm64": "4.0.518",
50
+ "@remotion/compositor-darwin-x64": "4.0.518",
51
+ "@remotion/compositor-linux-arm64-gnu": "4.0.518",
52
+ "@remotion/compositor-linux-arm64-musl": "4.0.518",
53
+ "@remotion/compositor-linux-x64-gnu": "4.0.518",
54
+ "@remotion/compositor-linux-x64-musl": "4.0.518",
55
+ "@remotion/compositor-win32-x64-msvc": "4.0.518"
56
56
  },
57
57
  "keywords": [
58
58
  "remotion",