@remotion/renderer 3.2.11 → 3.2.12-crf.5

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.
@@ -4,4 +4,5 @@ declare type MediaSupport = {
4
4
  audio: boolean;
5
5
  };
6
6
  export declare const codecSupportsMedia: (codec: Codec) => MediaSupport;
7
+ export declare const codecSupportsCrf: (codec: Codec) => boolean | "" | null;
7
8
  export {};
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.codecSupportsMedia = void 0;
3
+ exports.codecSupportsCrf = exports.codecSupportsMedia = void 0;
4
+ const get_codec_name_1 = require("./get-codec-name");
4
5
  const support = {
5
6
  'h264-mkv': {
6
7
  audio: true,
@@ -47,3 +48,9 @@ const codecSupportsMedia = (codec) => {
47
48
  return support[codec];
48
49
  };
49
50
  exports.codecSupportsMedia = codecSupportsMedia;
51
+ const codecSupportsCrf = (codec) => {
52
+ const encoderName = (0, get_codec_name_1.getCodecName)(codec);
53
+ const supportsCrf = encoderName && codec !== 'prores';
54
+ return supportsCrf;
55
+ };
56
+ exports.codecSupportsCrf = codecSupportsCrf;
@@ -1,5 +1,5 @@
1
1
  import type { Codec } from './codec';
2
- export declare const combineVideos: ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, numberOfGifLoops, }: {
2
+ export declare const combineVideos: ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, numberOfGifLoops, crf, }: {
3
3
  files: string[];
4
4
  filelistDir: string;
5
5
  output: string;
@@ -8,4 +8,5 @@ export declare const combineVideos: ({ files, filelistDir, output, onProgress, n
8
8
  codec: Codec;
9
9
  fps: number;
10
10
  numberOfGifLoops: number | null;
11
+ crf: number | null;
11
12
  }) => Promise<void>;
@@ -8,15 +8,17 @@ exports.combineVideos = void 0;
8
8
  const execa_1 = __importDefault(require("execa"));
9
9
  const fs_1 = require("fs");
10
10
  const path_1 = require("path");
11
+ const codec_supports_media_1 = require("./codec-supports-media");
11
12
  const get_audio_codec_name_1 = require("./get-audio-codec-name");
12
13
  const is_audio_codec_1 = require("./is-audio-codec");
13
14
  const parse_ffmpeg_progress_1 = require("./parse-ffmpeg-progress");
14
15
  const truthy_1 = require("./truthy");
15
- const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, numberOfGifLoops, }) => {
16
+ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfFrames, codec, fps, numberOfGifLoops, crf, }) => {
16
17
  var _a;
17
18
  const fileList = files.map((p) => `file '${p}'`).join('\n');
18
19
  const fileListTxt = (0, path_1.join)(filelistDir, 'files.txt');
19
20
  (0, fs_1.writeFileSync)(fileListTxt, fileList);
21
+ const supportsCrf = (0, codec_supports_media_1.codecSupportsCrf)(codec);
20
22
  try {
21
23
  const task = (0, execa_1.default)('ffmpeg', [
22
24
  (0, is_audio_codec_1.isAudioCodec)(codec) ? null : '-r',
@@ -40,6 +42,8 @@ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfF
40
42
  // Set max bitrate up to 1024kbps, will choose lower if that's too much
41
43
  '-b:a',
42
44
  '512K',
45
+ supportsCrf ? '-crf' : null,
46
+ supportsCrf ? String(crf) : null,
43
47
  codec === 'h264' ? '-movflags' : null,
44
48
  codec === 'h264' ? 'faststart' : null,
45
49
  '-shortest',
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { OffthreadVideoImageFormat } from 'remotion';
3
2
  import type { DownloadMap } from './assets/download-map';
4
3
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -32,9 +32,9 @@ const determineResizeParams = (needsResize) => {
32
32
  };
33
33
  // Uses no seeking, therefore the whole video has to be decoded. This is a last resort and should only happen
34
34
  // if the video is corrupted
35
- const getFrameOfVideoSlow = async ({ src, timestamp, ffmpegExecutable, imageFormat, specialVCodecForTransparency, needsResize, }) => {
36
- console.warn(`\nUsing a slow method to extract the frame at ${timestamp}ms of ${src}. See https://remotion.dev/docs/slow-method-to-extract-frame for advice`);
37
- const actualOffset = `-${timestamp * 1000}ms`;
35
+ const getFrameOfVideoSlow = async ({ src, duration, ffmpegExecutable, imageFormat, specialVCodecForTransparency, needsResize, offset, fps, }) => {
36
+ console.warn(`\nUsing a slow method to extract the frame at ${duration}ms of ${src}. See https://remotion.dev/docs/slow-method-to-extract-frame for advice`);
37
+ const actualOffset = `-${duration * 1000 - offset}ms`;
38
38
  const command = [
39
39
  '-itsoffset',
40
40
  actualOffset,
@@ -72,7 +72,20 @@ const getFrameOfVideoSlow = async ({ src, timestamp, ffmpegExecutable, imageForm
72
72
  const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
73
73
  const isEmpty = stdErr.includes('Output file is empty');
74
74
  if (isEmpty) {
75
- throw new Error(`Could not get last frame of ${src}. Tried to seek to the end using the command "ffmpeg ${command.join(' ')}" but got no frame. Most likely this video is corrupted.`);
75
+ if (offset > 70) {
76
+ throw new Error(`Could not get last frame of ${src}. Tried to seek to the end using the command "ffmpeg ${command.join(' ')}" but got no frame. Most likely this video is corrupted.`);
77
+ }
78
+ return getFrameOfVideoSlow({
79
+ ffmpegExecutable,
80
+ duration,
81
+ // Decrement in 10ms increments, or 1 frame (e.g. fps = 25 --> 40ms)
82
+ offset: offset + (fps === null ? 10 : 1000 / fps),
83
+ src,
84
+ imageFormat,
85
+ specialVCodecForTransparency,
86
+ needsResize,
87
+ fps,
88
+ });
76
89
  }
77
90
  return stdoutBuffer;
78
91
  };
@@ -88,12 +101,14 @@ const getLastFrameOfVideoFastUnlimited = async (options) => {
88
101
  }
89
102
  if (options.specialVCodecForTransparency === 'vp8' || offset > 40) {
90
103
  const last = await getFrameOfVideoSlow({
91
- timestamp: duration,
104
+ duration,
92
105
  ffmpegExecutable,
93
106
  src,
94
107
  imageFormat: options.imageFormat,
95
108
  specialVCodecForTransparency: options.specialVCodecForTransparency,
96
109
  needsResize: options.needsResize,
110
+ offset: offset - 1000 / (fps === null ? 10 : fps),
111
+ fps,
97
112
  });
98
113
  return last;
99
114
  }
@@ -143,7 +158,7 @@ const getLastFrameOfVideoFastUnlimited = async (options) => {
143
158
  const unlimited = await getLastFrameOfVideoFastUnlimited({
144
159
  ffmpegExecutable,
145
160
  // Decrement in 10ms increments, or 1 frame (e.g. fps = 25 --> 40ms)
146
- offset: offset + 1000 / (fps === null ? 10 : fps),
161
+ offset: offset + (fps === null ? 10 : 1000 / fps),
147
162
  src,
148
163
  ffprobeExecutable,
149
164
  imageFormat: options.imageFormat,
@@ -167,13 +182,16 @@ const extractFrameFromVideoFn = async ({ time, ffmpegExecutable, ffprobeExecutab
167
182
  const src = await (0, ensure_presentation_timestamp_1.ensurePresentationTimestamps)(downloadMap, options.src);
168
183
  const { specialVcodec, needsResize } = await (0, get_video_info_1.getVideoInfo)(downloadMap, src, ffprobeExecutable);
169
184
  if (specialVcodec === 'vp8') {
185
+ const { fps } = await (0, get_video_stream_duration_1.getVideoStreamDuration)(downloadMap, src, ffprobeExecutable);
170
186
  return getFrameOfVideoSlow({
171
187
  ffmpegExecutable,
172
188
  imageFormat,
173
189
  specialVCodecForTransparency: specialVcodec,
174
190
  src,
175
- timestamp: time,
191
+ duration: time,
176
192
  needsResize,
193
+ offset: 0,
194
+ fps,
177
195
  });
178
196
  }
179
197
  if ((0, is_beyond_last_frame_1.isBeyondLastFrame)(downloadMap, src, time)) {
@@ -243,6 +261,11 @@ const extractFrameFromVideoFn = async ({ time, ffmpegExecutable, ffprobeExecutab
243
261
  });
244
262
  return last;
245
263
  }
264
+ if (stdOut.length === 0) {
265
+ console.log('FFMPEG Logs:');
266
+ console.log(stderrStr);
267
+ throw new Error("Couldn't extract frame from video - FFMPEG did not return any data. Check logs to see more information");
268
+ }
246
269
  return stdOut;
247
270
  };
248
271
  const extractFrameFromVideo = async (options) => {
@@ -1 +1 @@
1
- export declare const guessExtensionForVideo: (src: string) => Promise<"mp3" | "webm" | "wav" | "mp4">;
1
+ export declare const guessExtensionForVideo: (src: string) => Promise<"mp3" | "wav" | "webm" | "mp4">;
package/dist/index.d.ts CHANGED
@@ -60,14 +60,14 @@ export declare const RenderInternals: {
60
60
  width: number;
61
61
  height: number;
62
62
  scale: number;
63
- codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif";
63
+ codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
64
64
  }) => void;
65
65
  normalizeServeUrl: (unnormalized: string) => string;
66
66
  spawnFfmpeg: (options: import("./stitch-frames-to-video").StitcherOptions) => Promise<{
67
67
  task: Promise<Buffer | null>;
68
68
  getLogs: () => string;
69
69
  }>;
70
- getFileExtensionFromCodec: (codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "webm" | "wav" | "mp4" | "aac" | "gif" | "mkv" | "mov";
70
+ getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
71
71
  tmpDir: (str: string) => string;
72
72
  deleteDirectory: (directory: string) => Promise<void>;
73
73
  isServeUrl: (potentialUrl: string) => boolean;
@@ -115,7 +115,7 @@ export declare const RenderInternals: {
115
115
  };
116
116
  registerErrorSymbolicationLock: () => number;
117
117
  unlockErrorSymbolicationLock: (id: number) => void;
118
- canUseParallelEncoding: (codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif") => boolean;
118
+ canUseParallelEncoding: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => boolean;
119
119
  mimeContentType: typeof mimeContentType;
120
120
  mimeLookup: typeof mimeLookup;
121
121
  validateConcurrency: (value: unknown, setting: string) => void;
@@ -124,8 +124,8 @@ export declare const RenderInternals: {
124
124
  validateFrameRange: (frameRange: import("./frame-range").FrameRange | null) => void;
125
125
  DEFAULT_OPENGL_RENDERER: "swangle" | "angle" | "egl" | "swiftshader" | null;
126
126
  validateOpenGlRenderer: (option: "swangle" | "angle" | "egl" | "swiftshader" | null) => "swangle" | "angle" | "egl" | "swiftshader" | null;
127
- getDefaultCrfForCodec: (codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif") => number;
128
- validateSelectedCrfAndCodecCombination: (crf: unknown, codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif") => void;
127
+ getDefaultCrfForCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => number;
128
+ validateSelectedCrfAndCodecCombination: (crf: unknown, codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
129
129
  validImageFormats: readonly ["png", "jpeg", "none"];
130
130
  validCodecs: readonly ["h264", "h265", "vp8", "vp9", "mp3", "aac", "wav", "prores", "h264-mkv", "gif"];
131
131
  DEFAULT_OVERWRITE: boolean;
@@ -133,14 +133,14 @@ export declare const RenderInternals: {
133
133
  validateQuality: (q: number | undefined) => void;
134
134
  validateFrame: (frame: number, durationInFrames: number) => void;
135
135
  DEFAULT_TIMEOUT: number;
136
- getValidCrfRanges: (codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif") => [number, number];
137
- validateSelectedPixelFormatAndCodecCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif") => void;
138
- validateSelectedCodecAndProResCombination: (actualCodec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif", actualProResProfile: "4444-xq" | "4444" | "hq" | "standard" | "light" | "proxy" | undefined) => void;
136
+ getValidCrfRanges: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => [number, number];
137
+ validateSelectedPixelFormatAndCodecCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
138
+ validateSelectedCodecAndProResCombination: (actualCodec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", actualProResProfile: "4444-xq" | "4444" | "hq" | "standard" | "light" | "proxy" | undefined) => void;
139
139
  validateSelectedPixelFormatAndImageFormatCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", imageFormat: "png" | "jpeg" | "none") => "none" | "valid";
140
- DEFAULT_CODEC: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif";
141
- isAudioCodec: (codec: "mp3" | "wav" | "h264" | "h265" | "vp8" | "vp9" | "aac" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
140
+ DEFAULT_CODEC: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
141
+ isAudioCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
142
142
  logLevels: readonly ["verbose", "info", "warn", "error"];
143
- isEqualOrBelowLogLevel: (currentLevel: "verbose" | "info" | "warn" | "error", level: "verbose" | "info" | "warn" | "error") => boolean;
143
+ isEqualOrBelowLogLevel: (currentLevel: "verbose" | "error" | "info" | "warn", level: "verbose" | "error" | "info" | "warn") => boolean;
144
144
  isValidLogLevel: (level: string) => boolean;
145
145
  validateEveryNthFrame: (everyNthFrame: unknown) => void;
146
146
  perf: typeof perf;
@@ -46,6 +46,9 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, ffmpegExecu
46
46
  if (outputLocation) {
47
47
  (0, validate_output_filename_1.validateOutputFilename)(codec, (0, get_extension_of_filename_1.getExtensionOfFilename)(outputLocation));
48
48
  }
49
+ const absoluteOutputLocation = outputLocation
50
+ ? path_1.default.resolve(process.cwd(), outputLocation)
51
+ : null;
49
52
  (0, validate_scale_1.validateScale)(scale);
50
53
  const everyNthFrame = (_a = options.everyNthFrame) !== null && _a !== void 0 ? _a : 1;
51
54
  const numberOfGifLoops = (_b = options.numberOfGifLoops) !== null && _b !== void 0 ? _b : null;
@@ -207,8 +210,8 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, ffmpegExecu
207
210
  .then(([{ assetsInfo }]) => {
208
211
  renderedDoneIn = Date.now() - renderStart;
209
212
  callUpdate();
210
- if (outputLocation) {
211
- (0, ensure_output_directory_1.ensureOutputDirectory)(outputLocation);
213
+ if (absoluteOutputLocation) {
214
+ (0, ensure_output_directory_1.ensureOutputDirectory)(absoluteOutputLocation);
212
215
  }
213
216
  const stitchStart = Date.now();
214
217
  return Promise.all([
@@ -216,7 +219,7 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, ffmpegExecu
216
219
  width: composition.width * (scale !== null && scale !== void 0 ? scale : 1),
217
220
  height: composition.height * (scale !== null && scale !== void 0 ? scale : 1),
218
221
  fps,
219
- outputLocation,
222
+ outputLocation: absoluteOutputLocation,
220
223
  internalOptions: {
221
224
  preEncodedFileLocation,
222
225
  imageFormat,
@@ -96,7 +96,7 @@ const spawnFfmpeg = async (options) => {
96
96
  const audioCodecName = (0, get_audio_codec_name_1.getAudioCodecName)(codec);
97
97
  const proResProfileName = (0, get_prores_profile_name_1.getProResProfileName)(codec, options.proResProfile);
98
98
  const mediaSupport = (0, codec_supports_media_1.codecSupportsMedia)(codec);
99
- const supportsCrf = encoderName && codec !== 'prores';
99
+ const supportsCrf = (0, codec_supports_media_1.codecSupportsCrf)(codec);
100
100
  const tempFile = options.outputLocation
101
101
  ? null
102
102
  : path_1.default.join(options.assetsInfo.downloadMap.stitchFrames, `out.${(0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, 'final')}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.2.11",
3
+ "version": "3.2.12-crf.5+14dc380ad",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "execa": "5.1.1",
24
24
  "extract-zip": "2.0.1",
25
- "remotion": "3.2.11",
25
+ "remotion": "3.2.12-crf.5+14dc380ad",
26
26
  "source-map": "^0.8.0-beta.0",
27
27
  "ws": "8.7.0"
28
28
  },
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "43d44e718e41200aea544719e9963482f9a65120"
60
+ "gitHead": "14dc380ad148868c35c5612f48eaf8e78ab42d9c"
61
61
  }