@remotion/renderer 3.0.8 → 3.0.11

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.
Files changed (195) hide show
  1. package/dist/assets/download-and-map-assets-to-file.d.ts +6 -0
  2. package/dist/assets/download-and-map-assets-to-file.js +22 -12
  3. package/dist/assets/ffmpeg-volume-expression.d.ts +2 -1
  4. package/dist/assets/ffmpeg-volume-expression.js +15 -12
  5. package/dist/combine-videos.js +2 -0
  6. package/dist/extract-frame-from-video.d.ts +17 -0
  7. package/dist/extract-frame-from-video.js +132 -0
  8. package/dist/frame-to-ffmpeg-timestamp.d.ts +1 -0
  9. package/dist/frame-to-ffmpeg-timestamp.js +8 -0
  10. package/dist/get-compositions.d.ts +3 -1
  11. package/dist/get-compositions.js +21 -5
  12. package/dist/index.d.ts +7 -7
  13. package/dist/index.js +0 -2
  14. package/dist/offthread/index.d.ts +0 -0
  15. package/dist/offthread/index.js +1 -0
  16. package/dist/offthread-video-server.d.ts +13 -0
  17. package/dist/offthread-video-server.js +64 -0
  18. package/dist/prepare-server.d.ts +12 -2
  19. package/dist/prepare-server.js +22 -5
  20. package/dist/prespawn-ffmpeg.js +7 -9
  21. package/dist/render-frames.d.ts +3 -1
  22. package/dist/render-frames.js +33 -15
  23. package/dist/render-media.d.ts +4 -2
  24. package/dist/render-media.js +4 -1
  25. package/dist/render-still.d.ts +7 -2
  26. package/dist/render-still.js +27 -8
  27. package/dist/serve-static.d.ts +9 -3
  28. package/dist/serve-static.js +16 -0
  29. package/dist/set-props-and-env.d.ts +2 -1
  30. package/dist/set-props-and-env.js +6 -3
  31. package/dist/stitch-frames-to-video.js +1 -0
  32. package/dist/stringify-ffmpeg-filter.js +3 -0
  33. package/package.json +3 -4
  34. package/dist/assets/calculate-asset-positions.d.ts.map +0 -1
  35. package/dist/assets/calculate-asset-positions.js.map +0 -1
  36. package/dist/assets/calculate-atempo.d.ts.map +0 -1
  37. package/dist/assets/calculate-atempo.js.map +0 -1
  38. package/dist/assets/convert-assets-to-file-urls.d.ts.map +0 -1
  39. package/dist/assets/convert-assets-to-file-urls.js.map +0 -1
  40. package/dist/assets/download-and-map-assets-to-file.d.ts.map +0 -1
  41. package/dist/assets/download-and-map-assets-to-file.js.map +0 -1
  42. package/dist/assets/download-file.d.ts.map +0 -1
  43. package/dist/assets/download-file.js.map +0 -1
  44. package/dist/assets/ffmpeg-volume-expression.d.ts.map +0 -1
  45. package/dist/assets/ffmpeg-volume-expression.js.map +0 -1
  46. package/dist/assets/flatten-volume-array.d.ts.map +0 -1
  47. package/dist/assets/flatten-volume-array.js.map +0 -1
  48. package/dist/assets/get-audio-channels.d.ts.map +0 -1
  49. package/dist/assets/get-audio-channels.js.map +0 -1
  50. package/dist/assets/read-file.d.ts.map +0 -1
  51. package/dist/assets/read-file.js.map +0 -1
  52. package/dist/assets/round-volume-to-avoid-stack-overflow.d.ts.map +0 -1
  53. package/dist/assets/round-volume-to-avoid-stack-overflow.js.map +0 -1
  54. package/dist/assets/sanitize-filename.d.ts.map +0 -1
  55. package/dist/assets/sanitize-filename.js.map +0 -1
  56. package/dist/assets/sanitize-filepath.d.ts.map +0 -1
  57. package/dist/assets/sanitize-filepath.js.map +0 -1
  58. package/dist/assets/truncate-utf8-bytes.d.ts.map +0 -1
  59. package/dist/assets/truncate-utf8-bytes.js.map +0 -1
  60. package/dist/assets/types.d.ts.map +0 -1
  61. package/dist/assets/types.js.map +0 -1
  62. package/dist/browser-log.d.ts.map +0 -1
  63. package/dist/browser-log.js.map +0 -1
  64. package/dist/calculate-ffmpeg-filters.d.ts.map +0 -1
  65. package/dist/calculate-ffmpeg-filters.js.map +0 -1
  66. package/dist/can-use-parallel-encoding.d.ts.map +0 -1
  67. package/dist/can-use-parallel-encoding.js.map +0 -1
  68. package/dist/chunk.d.ts.map +0 -1
  69. package/dist/chunk.js.map +0 -1
  70. package/dist/combine-videos.d.ts.map +0 -1
  71. package/dist/combine-videos.js.map +0 -1
  72. package/dist/convert-to-pcm.d.ts.map +0 -1
  73. package/dist/convert-to-pcm.js.map +0 -1
  74. package/dist/create-ffmpeg-complex-filter.d.ts.map +0 -1
  75. package/dist/create-ffmpeg-complex-filter.js.map +0 -1
  76. package/dist/create-ffmpeg-merge-filter.d.ts.map +0 -1
  77. package/dist/create-ffmpeg-merge-filter.js.map +0 -1
  78. package/dist/create-silent-audio.d.ts.map +0 -1
  79. package/dist/create-silent-audio.js.map +0 -1
  80. package/dist/cycle-browser-tabs.d.ts.map +0 -1
  81. package/dist/cycle-browser-tabs.js.map +0 -1
  82. package/dist/delay-render-embedded-stack.d.ts.map +0 -1
  83. package/dist/delay-render-embedded-stack.js.map +0 -1
  84. package/dist/delete-directory.d.ts.map +0 -1
  85. package/dist/delete-directory.js.map +0 -1
  86. package/dist/ensure-frames-in-order.d.ts.map +0 -1
  87. package/dist/ensure-frames-in-order.js.map +0 -1
  88. package/dist/ensure-output-directory.d.ts.map +0 -1
  89. package/dist/ensure-output-directory.js.map +0 -1
  90. package/dist/error-handling/handle-javascript-exception.d.ts.map +0 -1
  91. package/dist/error-handling/handle-javascript-exception.js.map +0 -1
  92. package/dist/error-handling/symbolicate-error.d.ts.map +0 -1
  93. package/dist/error-handling/symbolicate-error.js.map +0 -1
  94. package/dist/error-handling/symbolicateable-error.d.ts.map +0 -1
  95. package/dist/error-handling/symbolicateable-error.js.map +0 -1
  96. package/dist/ffmpeg-filter-file.d.ts.map +0 -1
  97. package/dist/ffmpeg-filter-file.js.map +0 -1
  98. package/dist/ffmpeg-flags.d.ts.map +0 -1
  99. package/dist/ffmpeg-flags.js.map +0 -1
  100. package/dist/get-audio-codec-name.d.ts.map +0 -1
  101. package/dist/get-audio-codec-name.js.map +0 -1
  102. package/dist/get-browser-instance.d.ts.map +0 -1
  103. package/dist/get-browser-instance.js.map +0 -1
  104. package/dist/get-codec-name.d.ts.map +0 -1
  105. package/dist/get-codec-name.js.map +0 -1
  106. package/dist/get-compositions.d.ts.map +0 -1
  107. package/dist/get-compositions.js.map +0 -1
  108. package/dist/get-concurrency.d.ts.map +0 -1
  109. package/dist/get-concurrency.js.map +0 -1
  110. package/dist/get-duration-from-frame-range.d.ts.map +0 -1
  111. package/dist/get-duration-from-frame-range.js.map +0 -1
  112. package/dist/get-extension-from-codec.d.ts.map +0 -1
  113. package/dist/get-extension-from-codec.js.map +0 -1
  114. package/dist/get-frame-to-render.d.ts.map +0 -1
  115. package/dist/get-frame-to-render.js.map +0 -1
  116. package/dist/get-local-browser-executable.d.ts.map +0 -1
  117. package/dist/get-local-browser-executable.js.map +0 -1
  118. package/dist/get-port.d.ts.map +0 -1
  119. package/dist/get-port.js.map +0 -1
  120. package/dist/get-prores-profile-name.d.ts.map +0 -1
  121. package/dist/get-prores-profile-name.js.map +0 -1
  122. package/dist/image-format.d.ts.map +0 -1
  123. package/dist/image-format.js.map +0 -1
  124. package/dist/index.d.ts.map +0 -1
  125. package/dist/index.js.map +0 -1
  126. package/dist/is-serve-url.d.ts.map +0 -1
  127. package/dist/is-serve-url.js.map +0 -1
  128. package/dist/legacy-webpack-config.d.ts.map +0 -1
  129. package/dist/legacy-webpack-config.js.map +0 -1
  130. package/dist/make-assets-download-dir.d.ts.map +0 -1
  131. package/dist/make-assets-download-dir.js.map +0 -1
  132. package/dist/merge-audio-track.d.ts.map +0 -1
  133. package/dist/merge-audio-track.js.map +0 -1
  134. package/dist/normalize-serve-url.d.ts.map +0 -1
  135. package/dist/normalize-serve-url.js.map +0 -1
  136. package/dist/open-browser.d.ts.map +0 -1
  137. package/dist/open-browser.js.map +0 -1
  138. package/dist/p-limit.d.ts.map +0 -1
  139. package/dist/p-limit.js.map +0 -1
  140. package/dist/parse-browser-error-stack.d.ts.map +0 -1
  141. package/dist/parse-browser-error-stack.js.map +0 -1
  142. package/dist/parse-ffmpeg-progress.d.ts.map +0 -1
  143. package/dist/parse-ffmpeg-progress.js.map +0 -1
  144. package/dist/pool.d.ts.map +0 -1
  145. package/dist/pool.js.map +0 -1
  146. package/dist/prepare-server.d.ts.map +0 -1
  147. package/dist/prepare-server.js.map +0 -1
  148. package/dist/preprocess-audio-track.d.ts.map +0 -1
  149. package/dist/preprocess-audio-track.js.map +0 -1
  150. package/dist/prespawn-ffmpeg.d.ts.map +0 -1
  151. package/dist/prespawn-ffmpeg.js.map +0 -1
  152. package/dist/provide-screenshot.d.ts.map +0 -1
  153. package/dist/provide-screenshot.js.map +0 -1
  154. package/dist/puppeteer-evaluate.d.ts.map +0 -1
  155. package/dist/puppeteer-evaluate.js.map +0 -1
  156. package/dist/puppeteer-screenshot.d.ts.map +0 -1
  157. package/dist/puppeteer-screenshot.js.map +0 -1
  158. package/dist/render-frames.d.ts.map +0 -1
  159. package/dist/render-frames.js.map +0 -1
  160. package/dist/render-media.d.ts.map +0 -1
  161. package/dist/render-media.js.map +0 -1
  162. package/dist/render-still.d.ts.map +0 -1
  163. package/dist/render-still.js.map +0 -1
  164. package/dist/resolve-asset-src.d.ts.map +0 -1
  165. package/dist/resolve-asset-src.js.map +0 -1
  166. package/dist/sample-rate.d.ts.map +0 -1
  167. package/dist/sample-rate.js.map +0 -1
  168. package/dist/screenshot-dom-element.d.ts.map +0 -1
  169. package/dist/screenshot-dom-element.js.map +0 -1
  170. package/dist/screenshot-task.d.ts.map +0 -1
  171. package/dist/screenshot-task.js.map +0 -1
  172. package/dist/seek-to-frame.d.ts.map +0 -1
  173. package/dist/seek-to-frame.js.map +0 -1
  174. package/dist/serve-static.d.ts.map +0 -1
  175. package/dist/serve-static.js.map +0 -1
  176. package/dist/set-props-and-env.d.ts.map +0 -1
  177. package/dist/set-props-and-env.js.map +0 -1
  178. package/dist/stitch-frames-to-video.d.ts.map +0 -1
  179. package/dist/stitch-frames-to-video.js.map +0 -1
  180. package/dist/stringify-ffmpeg-filter.d.ts.map +0 -1
  181. package/dist/stringify-ffmpeg-filter.js.map +0 -1
  182. package/dist/symbolicate-stacktrace.d.ts.map +0 -1
  183. package/dist/symbolicate-stacktrace.js.map +0 -1
  184. package/dist/tmp-dir.d.ts.map +0 -1
  185. package/dist/tmp-dir.js.map +0 -1
  186. package/dist/types.d.ts.map +0 -1
  187. package/dist/types.js.map +0 -1
  188. package/dist/validate-even-dimensions-with-codec.d.ts.map +0 -1
  189. package/dist/validate-even-dimensions-with-codec.js.map +0 -1
  190. package/dist/validate-ffmpeg.d.ts.map +0 -1
  191. package/dist/validate-ffmpeg.js.map +0 -1
  192. package/dist/validate-puppeteer-timeout.d.ts.map +0 -1
  193. package/dist/validate-puppeteer-timeout.js.map +0 -1
  194. package/dist/validate-scale.d.ts.map +0 -1
  195. package/dist/validate-scale.js.map +0 -1
@@ -2,6 +2,7 @@ import { TAsset } from 'remotion';
2
2
  export declare type RenderMediaOnDownload = (src: string) => ((progress: {
3
3
  percent: number;
4
4
  }) => void) | undefined | void;
5
+ export declare const waitForAssetToBeDownloaded: (src: string) => Promise<string>;
5
6
  export declare const markAllAssetsAsDownloaded: () => void;
6
7
  export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, }: {
7
8
  src: string;
@@ -12,3 +13,8 @@ export declare const downloadAndMapAssetsToFileUrl: ({ asset, downloadDir, onDow
12
13
  downloadDir: string;
13
14
  onDownload: RenderMediaOnDownload;
14
15
  }) => Promise<TAsset>;
16
+ export declare const startDownloadForSrc: ({ src, downloadDir, onDownload, }: {
17
+ src: string;
18
+ downloadDir: string;
19
+ onDownload: RenderMediaOnDownload;
20
+ }) => Promise<string>;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = void 0;
6
+ exports.startDownloadForSrc = exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = exports.waitForAssetToBeDownloaded = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const remotion_1 = require("remotion");
@@ -14,20 +14,24 @@ const isDownloadingMap = {};
14
14
  const hasBeenDownloadedMap = {};
15
15
  const listeners = {};
16
16
  const waitForAssetToBeDownloaded = (src) => {
17
+ if (hasBeenDownloadedMap[src]) {
18
+ return Promise.resolve(hasBeenDownloadedMap[src]);
19
+ }
17
20
  if (!listeners[src]) {
18
21
  listeners[src] = [];
19
22
  }
20
23
  return new Promise((resolve) => {
21
- listeners[src].push(() => resolve());
24
+ listeners[src].push((to) => resolve(to));
22
25
  });
23
26
  };
24
- const notifyAssetIsDownloaded = (src) => {
27
+ exports.waitForAssetToBeDownloaded = waitForAssetToBeDownloaded;
28
+ const notifyAssetIsDownloaded = (src, to) => {
25
29
  if (!listeners[src]) {
26
30
  listeners[src] = [];
27
31
  }
28
- listeners[src].forEach((fn) => fn());
32
+ listeners[src].forEach((fn) => fn(to));
29
33
  isDownloadingMap[src] = false;
30
- hasBeenDownloadedMap[src] = true;
34
+ hasBeenDownloadedMap[src] = to;
31
35
  };
32
36
  const validateMimeType = (mimeType, src) => {
33
37
  if (!mimeType.includes('/')) {
@@ -73,7 +77,7 @@ const downloadAsset = async (src, to, onDownload) => {
73
77
  return;
74
78
  }
75
79
  if (isDownloadingMap[src]) {
76
- return waitForAssetToBeDownloaded(src);
80
+ return (0, exports.waitForAssetToBeDownloaded)(src);
77
81
  }
78
82
  isDownloadingMap[src] = true;
79
83
  const onProgress = onDownload(src);
@@ -94,7 +98,7 @@ const downloadAsset = async (src, to, onDownload) => {
94
98
  validateBufferEncoding(encoding, src);
95
99
  const buff = Buffer.from(assetData, encoding);
96
100
  await fs_1.default.promises.writeFile(to, buff);
97
- notifyAssetIsDownloaded(src);
101
+ notifyAssetIsDownloaded(src, to);
98
102
  return;
99
103
  }
100
104
  await (0, download_file_1.downloadFile)(src, to, ({ progress }) => {
@@ -102,7 +106,7 @@ const downloadAsset = async (src, to, onDownload) => {
102
106
  percent: progress,
103
107
  });
104
108
  });
105
- notifyAssetIsDownloaded(src);
109
+ notifyAssetIsDownloaded(src, to);
106
110
  };
107
111
  const markAllAssetsAsDownloaded = () => {
108
112
  Object.keys(hasBeenDownloadedMap).forEach((key) => {
@@ -127,16 +131,22 @@ const getSanitizedFilenameForAssetUrl = ({ src, downloadDir, }) => {
127
131
  };
128
132
  exports.getSanitizedFilenameForAssetUrl = getSanitizedFilenameForAssetUrl;
129
133
  const downloadAndMapAssetsToFileUrl = async ({ asset, downloadDir, onDownload, }) => {
130
- const newSrc = (0, exports.getSanitizedFilenameForAssetUrl)({
134
+ const newSrc = await (0, exports.startDownloadForSrc)({
131
135
  src: asset.src,
132
136
  downloadDir,
137
+ onDownload,
133
138
  });
134
- if (!remotion_1.Internals.AssetCompression.isAssetCompressed(newSrc)) {
135
- await downloadAsset(asset.src, newSrc, onDownload);
136
- }
137
139
  return {
138
140
  ...asset,
139
141
  src: newSrc,
140
142
  };
141
143
  };
142
144
  exports.downloadAndMapAssetsToFileUrl = downloadAndMapAssetsToFileUrl;
145
+ const startDownloadForSrc = async ({ src, downloadDir, onDownload, }) => {
146
+ const newSrc = (0, exports.getSanitizedFilenameForAssetUrl)({ downloadDir, src });
147
+ if (!remotion_1.Internals.AssetCompression.isAssetCompressed(newSrc)) {
148
+ await downloadAsset(src, newSrc, onDownload);
149
+ }
150
+ return newSrc;
151
+ };
152
+ exports.startDownloadForSrc = startDownloadForSrc;
@@ -4,9 +4,10 @@ declare type FfmpegVolumeExpression = {
4
4
  eval: FfmpegEval;
5
5
  value: string;
6
6
  };
7
- export declare const ffmpegVolumeExpression: ({ volume, startInVideo, fps, }: {
7
+ export declare const ffmpegVolumeExpression: ({ volume, startInVideo, fps, trimLeft, }: {
8
8
  volume: AssetVolume;
9
9
  startInVideo: number;
10
+ trimLeft: number;
10
11
  fps: number;
11
12
  }) => FfmpegVolumeExpression;
12
13
  export {};
@@ -12,12 +12,12 @@ const FFMPEG_TIME_VARIABLE = 't';
12
12
  const ffmpegIfOrElse = (condition, then, elseDo) => {
13
13
  return `if(${condition},${then},${elseDo})`;
14
14
  };
15
- const ffmpegIsOneOfFrames = (frames, fps) => {
15
+ const ffmpegIsOneOfFrames = ({ frames, trimLeft, fps, }) => {
16
16
  const consecutiveArrays = [];
17
17
  for (let i = 0; i < frames.length; i++) {
18
18
  const previousFrame = frames[i - 1];
19
19
  const frame = frames[i];
20
- if (!previousFrame || frame !== previousFrame + 1) {
20
+ if (previousFrame === undefined || frame !== previousFrame + 1) {
21
21
  consecutiveArrays.push([]);
22
22
  }
23
23
  consecutiveArrays[consecutiveArrays.length - 1].push(frame);
@@ -28,25 +28,22 @@ const ffmpegIsOneOfFrames = (frames, fps) => {
28
28
  const lastFrame = f[f.length - 1];
29
29
  const before = (firstFrame - 0.5) / fps;
30
30
  const after = (lastFrame + 0.5) / fps;
31
- return `between(${FFMPEG_TIME_VARIABLE},${before.toFixed(4)},${after.toFixed(4)})`;
31
+ return `between(${FFMPEG_TIME_VARIABLE},${(before + trimLeft).toFixed(4)},${(after + trimLeft).toFixed(4)})`;
32
32
  })
33
33
  .join('+');
34
34
  };
35
- const ffmpegBuildVolumeExpression = (arr, fps) => {
35
+ const ffmpegBuildVolumeExpression = (arr, delay, fps) => {
36
36
  if (arr.length === 0) {
37
37
  throw new Error('Volume array expression should never have length 0');
38
38
  }
39
39
  if (arr.length === 1) {
40
- // FFMpeg tends to request volume for frames outside the range
41
- // where the audio actually plays.
42
- // If this is the case, we just return volume 0 to clip it.
43
- return ffmpegIfOrElse(ffmpegIsOneOfFrames(arr[0][1], fps), String(arr[0][0]), String(0));
40
+ return String(arr[0][0]);
44
41
  }
45
42
  const [first, ...rest] = arr;
46
43
  const [volume, frames] = first;
47
- return ffmpegIfOrElse(ffmpegIsOneOfFrames(frames, fps), String(volume), ffmpegBuildVolumeExpression(rest, fps));
44
+ return ffmpegIfOrElse(ffmpegIsOneOfFrames({ frames, trimLeft: delay, fps }), String(volume), ffmpegBuildVolumeExpression(rest, delay, fps));
48
45
  };
49
- const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
46
+ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, trimLeft, }) => {
50
47
  // If it's a static volume, we return it and tell
51
48
  // FFMPEG it only has to evaluate it once
52
49
  if (typeof volume === 'number') {
@@ -60,14 +57,20 @@ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
60
57
  volume: volume[0],
61
58
  startInVideo,
62
59
  fps,
60
+ trimLeft,
63
61
  });
64
62
  }
63
+ // A 1 sec video with frames 0-29 would mean that
64
+ // frame 29 corresponds to timestamp 0.966666...
65
+ // but the audio is actually 1 sec long. For that reason we pad the last
66
+ // timestamp.
67
+ const paddedVolume = [...volume, volume[volume.length - 1]];
65
68
  // Otherwise, we construct an FFMPEG expression. First step:
66
69
  // Make a map of all possible volumes
67
70
  // {possibleVolume1} => [frame1, frame2]
68
71
  // {possibleVolume2} => [frame3, frame4]
69
72
  const volumeMap = {};
70
- volume.forEach((baseVolume, frame) => {
73
+ paddedVolume.forEach((baseVolume, frame) => {
71
74
  // Adjust volume based on how many other tracks have not yet finished
72
75
  const actualVolume = (0, round_volume_to_avoid_stack_overflow_1.roundVolumeToAvoidStackOverflow)(Math.min(1, baseVolume));
73
76
  if (!volumeMap[actualVolume]) {
@@ -81,7 +84,7 @@ const ffmpegVolumeExpression = ({ volume, startInVideo, fps, }) => {
81
84
  .map((key) => [Number(key), volumeMap[key]])
82
85
  .sort((a, b) => a[1].length - b[1].length);
83
86
  // Construct and tell FFMPEG it has to evaluate expression on each frame
84
- const expression = ffmpegBuildVolumeExpression(volumeArray, fps);
87
+ const expression = ffmpegBuildVolumeExpression(volumeArray, trimLeft, fps);
85
88
  return {
86
89
  eval: 'frame',
87
90
  value: `'${expression}'`,
@@ -28,6 +28,8 @@ const combineVideos = async ({ files, filelistDir, output, onProgress, numberOfF
28
28
  remotion_1.Internals.isAudioCodec(codec) ? null : 'copy',
29
29
  '-c:a',
30
30
  (0, get_audio_codec_name_1.getAudioCodecName)(codec),
31
+ codec === 'h264' ? '-movflags' : null,
32
+ codec === 'h264' ? 'faststart' : null,
31
33
  '-shortest',
32
34
  '-y',
33
35
  output,
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ import { FfmpegExecutable } from 'remotion';
3
+ import { Readable } from 'stream';
4
+ export declare function streamToString(stream: Readable): Promise<string>;
5
+ export declare const getLastFrameOfVideo: ({ ffmpegExecutable, offset, src, }: {
6
+ ffmpegExecutable: FfmpegExecutable;
7
+ offset: number;
8
+ src: string;
9
+ }) => Promise<Buffer>;
10
+ declare type Options = {
11
+ time: number;
12
+ src: string;
13
+ ffmpegExecutable: FfmpegExecutable;
14
+ };
15
+ export declare const extractFrameFromVideoFn: ({ time, src, ffmpegExecutable, }: Options) => Promise<Buffer>;
16
+ export declare const extractFrameFromVideo: (options: Options) => Promise<Buffer>;
17
+ export {};
@@ -0,0 +1,132 @@
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.extractFrameFromVideo = exports.extractFrameFromVideoFn = exports.getLastFrameOfVideo = exports.streamToString = void 0;
7
+ const execa_1 = __importDefault(require("execa"));
8
+ const frame_to_ffmpeg_timestamp_1 = require("./frame-to-ffmpeg-timestamp");
9
+ const p_limit_1 = require("./p-limit");
10
+ function streamToString(stream) {
11
+ const chunks = [];
12
+ return new Promise((resolve, reject) => {
13
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
14
+ stream.on('error', (err) => reject(err));
15
+ stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
16
+ });
17
+ }
18
+ exports.streamToString = streamToString;
19
+ const getLastFrameOfVideo = async ({ ffmpegExecutable, offset, src, }) => {
20
+ if (offset > 100) {
21
+ throw new Error('could not get last frame of ' +
22
+ src +
23
+ '. Tried to seek 100ms before the end of the video and no frame was found. The video container has a duration that is longer than it contains video.');
24
+ }
25
+ const actualOffset = `-${offset + 10}ms`;
26
+ const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
27
+ '-sseof',
28
+ actualOffset,
29
+ '-i',
30
+ src,
31
+ '-frames:v',
32
+ '1',
33
+ '-f',
34
+ 'image2pipe',
35
+ '-',
36
+ ]);
37
+ if (!stderr) {
38
+ throw new Error('unexpectedly did not get stderr');
39
+ }
40
+ if (!stdout) {
41
+ throw new Error('unexpectedly did not get stdout');
42
+ }
43
+ const stderrChunks = [];
44
+ const stdoutChunks = [];
45
+ const stdErrString = new Promise((resolve, reject) => {
46
+ stderr.on('data', (d) => stderrChunks.push(d));
47
+ stderr.on('error', (err) => reject(err));
48
+ stderr.on('end', () => resolve(Buffer.concat(stderrChunks).toString('utf-8')));
49
+ });
50
+ const stdoutChunk = new Promise((resolve, reject) => {
51
+ stdout.on('data', (d) => {
52
+ stdoutChunks.push(d);
53
+ });
54
+ stdout.on('error', (err) => {
55
+ reject(err);
56
+ });
57
+ stdout.on('end', () => {
58
+ resolve(Buffer.concat(stdoutChunks));
59
+ });
60
+ });
61
+ const [stdErr, stdoutBuffer] = await Promise.all([stdErrString, stdoutChunk]);
62
+ const isEmpty = stdErr.includes('Output file is empty');
63
+ if (isEmpty) {
64
+ return (0, exports.getLastFrameOfVideo)({ ffmpegExecutable, offset: offset + 10, src });
65
+ }
66
+ return stdoutBuffer;
67
+ };
68
+ exports.getLastFrameOfVideo = getLastFrameOfVideo;
69
+ const limit = (0, p_limit_1.pLimit)(5);
70
+ const extractFrameFromVideoFn = async ({ time, src, ffmpegExecutable, }) => {
71
+ const ffmpegTimestamp = (0, frame_to_ffmpeg_timestamp_1.frameToFfmpegTimestamp)(time);
72
+ const { stdout, stderr } = (0, execa_1.default)(ffmpegExecutable !== null && ffmpegExecutable !== void 0 ? ffmpegExecutable : 'ffmpeg', [
73
+ '-ss',
74
+ ffmpegTimestamp,
75
+ '-i',
76
+ src,
77
+ '-frames:v',
78
+ '1',
79
+ '-f',
80
+ 'image2pipe',
81
+ '-',
82
+ ], {
83
+ buffer: false,
84
+ });
85
+ if (!stderr) {
86
+ throw new Error('unexpectedly did not get stderr');
87
+ }
88
+ if (!stdout) {
89
+ throw new Error('unexpectedly did not get stdout');
90
+ }
91
+ const stdoutChunks = [];
92
+ const stderrChunks = [];
93
+ const stderrStringProm = new Promise((resolve, reject) => {
94
+ stderr.on('data', (d) => {
95
+ stderrChunks.push(d);
96
+ });
97
+ stderr.on('error', (err) => {
98
+ reject(err);
99
+ });
100
+ stderr.on('end', () => {
101
+ resolve(Buffer.concat(stderrChunks).toString('utf8'));
102
+ });
103
+ });
104
+ const stdoutBuffer = new Promise((resolve, reject) => {
105
+ stdout.on('data', (d) => {
106
+ stdoutChunks.push(d);
107
+ });
108
+ stdout.on('error', (err) => {
109
+ reject(err);
110
+ });
111
+ stdout.on('end', () => {
112
+ resolve(Buffer.concat(stdoutChunks));
113
+ });
114
+ });
115
+ const [stderrStr, stdOut] = await Promise.all([
116
+ stderrStringProm,
117
+ stdoutBuffer,
118
+ ]);
119
+ if (stderrStr.includes('Output file is empty')) {
120
+ return (0, exports.getLastFrameOfVideo)({
121
+ ffmpegExecutable,
122
+ offset: 0,
123
+ src,
124
+ });
125
+ }
126
+ return stdOut;
127
+ };
128
+ exports.extractFrameFromVideoFn = extractFrameFromVideoFn;
129
+ const extractFrameFromVideo = (options) => {
130
+ return limit(exports.extractFrameFromVideoFn, options);
131
+ };
132
+ exports.extractFrameFromVideo = extractFrameFromVideo;
@@ -0,0 +1 @@
1
+ export declare const frameToFfmpegTimestamp: (time: number) => string;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.frameToFfmpegTimestamp = void 0;
4
+ const frameToFfmpegTimestamp = (time) => {
5
+ // We ceil because FFMPEG seeks to the closest frame BEFORE the position
6
+ return (Math.ceil(time * 1000000) / 1000).toFixed(3) + 'ms';
7
+ };
8
+ exports.frameToFfmpegTimestamp = frameToFfmpegTimestamp;
@@ -1,5 +1,5 @@
1
1
  import { Browser } from 'puppeteer-core';
2
- import { BrowserExecutable, TCompMetadata } from 'remotion';
2
+ import { BrowserExecutable, FfmpegExecutable, TCompMetadata } from 'remotion';
3
3
  import { BrowserLog } from './browser-log';
4
4
  import { ChromiumOptions } from './open-browser';
5
5
  declare type GetCompositionsConfig = {
@@ -10,6 +10,8 @@ declare type GetCompositionsConfig = {
10
10
  browserExecutable?: BrowserExecutable;
11
11
  timeoutInMilliseconds?: number;
12
12
  chromiumOptions?: ChromiumOptions;
13
+ ffmpegExecutable?: FfmpegExecutable;
14
+ port?: number | null;
13
15
  };
14
16
  export declare const getCompositions: (serveUrlOrWebpackUrl: string, config?: GetCompositionsConfig | undefined) => Promise<TCompMetadata[]>;
15
17
  export {};
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getCompositions = void 0;
4
4
  const handle_javascript_exception_1 = require("./error-handling/handle-javascript-exception");
5
5
  const get_browser_instance_1 = require("./get-browser-instance");
6
+ const make_assets_download_dir_1 = require("./make-assets-download-dir");
6
7
  const prepare_server_1 = require("./prepare-server");
7
8
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
8
9
  const set_props_and_env_1 = require("./set-props-and-env");
9
10
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
10
- const innerGetCompositions = async (serveUrl, page, config) => {
11
+ const innerGetCompositions = async (serveUrl, page, config, proxyPort) => {
11
12
  if (config === null || config === void 0 ? void 0 : config.onBrowserLog) {
12
13
  page.on('console', (log) => {
13
14
  var _a;
@@ -26,6 +27,7 @@ const innerGetCompositions = async (serveUrl, page, config) => {
26
27
  serveUrl,
27
28
  initialFrame: 0,
28
29
  timeoutInMilliseconds: config === null || config === void 0 ? void 0 : config.timeoutInMilliseconds,
30
+ proxyPort,
29
31
  });
30
32
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
31
33
  page,
@@ -50,26 +52,40 @@ const innerGetCompositions = async (serveUrl, page, config) => {
50
52
  };
51
53
  const getCompositions = async (serveUrlOrWebpackUrl, config) => {
52
54
  var _a, _b;
53
- const { serveUrl, closeServer } = await (0, prepare_server_1.prepareServer)(serveUrlOrWebpackUrl);
55
+ const downloadDir = (0, make_assets_download_dir_1.makeAssetsDownloadTmpDir)();
54
56
  const { page, cleanup } = await (0, get_browser_instance_1.getPageAndCleanupFn)({
55
57
  passedInInstance: config === null || config === void 0 ? void 0 : config.puppeteerInstance,
56
58
  browserExecutable: (_a = config === null || config === void 0 ? void 0 : config.browserExecutable) !== null && _a !== void 0 ? _a : null,
57
59
  chromiumOptions: (_b = config === null || config === void 0 ? void 0 : config.chromiumOptions) !== null && _b !== void 0 ? _b : {},
58
60
  });
59
61
  return new Promise((resolve, reject) => {
62
+ var _a, _b;
63
+ const onError = (err) => reject(err);
60
64
  const cleanupPageError = (0, handle_javascript_exception_1.handleJavascriptException)({
61
65
  page,
62
66
  frame: null,
63
- onError: (err) => reject(err),
67
+ onError,
64
68
  });
65
- innerGetCompositions(serveUrl, page, config !== null && config !== void 0 ? config : {})
69
+ let close = null;
70
+ (0, prepare_server_1.prepareServer)({
71
+ webpackConfigOrServeUrl: serveUrlOrWebpackUrl,
72
+ downloadDir,
73
+ onDownload: () => undefined,
74
+ onError,
75
+ ffmpegExecutable: (_a = config === null || config === void 0 ? void 0 : config.ffmpegExecutable) !== null && _a !== void 0 ? _a : null,
76
+ port: (_b = config === null || config === void 0 ? void 0 : config.port) !== null && _b !== void 0 ? _b : null,
77
+ })
78
+ .then(({ serveUrl, closeServer, offthreadPort }) => {
79
+ close = closeServer;
80
+ return innerGetCompositions(serveUrl, page, config !== null && config !== void 0 ? config : {}, offthreadPort);
81
+ })
66
82
  .then((comp) => resolve(comp))
67
83
  .catch((err) => {
68
84
  reject(err);
69
85
  })
70
86
  .finally(() => {
71
87
  cleanup();
72
- closeServer();
88
+ close === null || close === void 0 ? void 0 : close();
73
89
  cleanupPageError();
74
90
  });
75
91
  });
package/dist/index.d.ts CHANGED
@@ -29,9 +29,13 @@ export declare const RenderInternals: {
29
29
  getFfmpegBuildInfo: (options: {
30
30
  ffmpegExecutable: string | null;
31
31
  }) => Promise<string>;
32
- serveStatic: (path: string, options?: {
33
- port?: number | undefined;
34
- } | undefined) => Promise<{
32
+ serveStatic: (path: string | null, options: {
33
+ port: number | null;
34
+ ffmpegExecutable: import("remotion").FfmpegExecutable;
35
+ downloadDir: string;
36
+ onDownload: import("./assets/download-and-map-assets-to-file").RenderMediaOnDownload;
37
+ onError: (err: Error) => void;
38
+ }) => Promise<{
35
39
  port: number;
36
40
  close: () => Promise<void>;
37
41
  }>;
@@ -50,10 +54,6 @@ export declare const RenderInternals: {
50
54
  makeAssetsDownloadTmpDir: () => string;
51
55
  tmpDir: (str: string) => string;
52
56
  deleteDirectory: (directory: string) => Promise<void>;
53
- prepareServer: (webpackConfigOrServeUrl: string) => Promise<{
54
- serveUrl: string;
55
- closeServer: () => Promise<void>;
56
- }>;
57
57
  isServeUrl: (potentialUrl: string) => boolean;
58
58
  ensureOutputDirectory: (outputLocation: string) => void;
59
59
  getRealFrameRange: (durationInFrames: number, frameRange: import("remotion").FrameRange | null) => [number, number];
package/dist/index.js CHANGED
@@ -18,7 +18,6 @@ const make_assets_download_dir_1 = require("./make-assets-download-dir");
18
18
  const normalize_serve_url_1 = require("./normalize-serve-url");
19
19
  const open_browser_1 = require("./open-browser");
20
20
  const parse_browser_error_stack_1 = require("./parse-browser-error-stack");
21
- const prepare_server_1 = require("./prepare-server");
22
21
  const serve_static_1 = require("./serve-static");
23
22
  const stitch_frames_to_video_1 = require("./stitch-frames-to-video");
24
23
  const tmp_dir_1 = require("./tmp-dir");
@@ -58,7 +57,6 @@ exports.RenderInternals = {
58
57
  makeAssetsDownloadTmpDir: make_assets_download_dir_1.makeAssetsDownloadTmpDir,
59
58
  tmpDir: tmp_dir_1.tmpDir,
60
59
  deleteDirectory: delete_directory_1.deleteDirectory,
61
- prepareServer: prepare_server_1.prepareServer,
62
60
  isServeUrl: is_serve_url_1.isServeUrl,
63
61
  ensureOutputDirectory: ensure_output_directory_1.ensureOutputDirectory,
64
62
  getRealFrameRange: get_frame_to_render_1.getRealFrameRange,
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,13 @@
1
+ import { RequestListener } from 'http';
2
+ import { FfmpegExecutable } from 'remotion';
3
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
+ export declare const extractUrlAndSourceFromUrl: (url: string) => {
5
+ src: string;
6
+ time: number;
7
+ };
8
+ export declare const startOffthreadVideoServer: ({ ffmpegExecutable, downloadDir, onDownload, onError, }: {
9
+ ffmpegExecutable: FfmpegExecutable;
10
+ downloadDir: string;
11
+ onDownload: RenderMediaOnDownload;
12
+ onError: (err: Error) => void;
13
+ }) => RequestListener;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startOffthreadVideoServer = exports.extractUrlAndSourceFromUrl = void 0;
4
+ const url_1 = require("url");
5
+ const download_and_map_assets_to_file_1 = require("./assets/download-and-map-assets-to-file");
6
+ const extract_frame_from_video_1 = require("./extract-frame-from-video");
7
+ const extractUrlAndSourceFromUrl = (url) => {
8
+ const parsed = new URL(url, 'http://localhost');
9
+ const query = parsed.search;
10
+ if (!query.trim()) {
11
+ throw new Error('Expected query from ' + url);
12
+ }
13
+ const params = new url_1.URLSearchParams(query);
14
+ const src = params.get('src');
15
+ if (!src) {
16
+ throw new Error('Did not pass `src` parameter');
17
+ }
18
+ const time = params.get('time');
19
+ if (!time) {
20
+ throw new Error('Did not get `time` parameter');
21
+ }
22
+ return { src, time: parseFloat(time) };
23
+ };
24
+ exports.extractUrlAndSourceFromUrl = extractUrlAndSourceFromUrl;
25
+ const startOffthreadVideoServer = ({ ffmpegExecutable, downloadDir, onDownload, onError, }) => {
26
+ return (req, res) => {
27
+ if (!req.url) {
28
+ throw new Error('Request came in without URL');
29
+ }
30
+ if (!req.url.startsWith('/proxy')) {
31
+ res.writeHead(404);
32
+ res.end();
33
+ return;
34
+ }
35
+ res.setHeader('access-control-allow-origin', '*');
36
+ res.setHeader('content-type', 'image/jpg');
37
+ const { src, time } = (0, exports.extractUrlAndSourceFromUrl)(req.url);
38
+ (0, download_and_map_assets_to_file_1.startDownloadForSrc)({ src, downloadDir, onDownload }).catch((err) => {
39
+ onError(new Error(`Error while downloading asset: ${err.stack}`));
40
+ });
41
+ (0, download_and_map_assets_to_file_1.waitForAssetToBeDownloaded)(src)
42
+ .then((newSrc) => {
43
+ return (0, extract_frame_from_video_1.extractFrameFromVideo)({
44
+ time,
45
+ src: newSrc,
46
+ ffmpegExecutable,
47
+ });
48
+ })
49
+ .then((readable) => {
50
+ if (!readable) {
51
+ throw new Error('no readable from ffmpeg');
52
+ }
53
+ res.writeHead(200);
54
+ res.write(readable);
55
+ res.end();
56
+ })
57
+ .catch((err) => {
58
+ res.writeHead(500);
59
+ res.end();
60
+ console.log('Error occurred', err);
61
+ });
62
+ };
63
+ };
64
+ exports.startOffthreadVideoServer = startOffthreadVideoServer;
@@ -1,4 +1,14 @@
1
- export declare const prepareServer: (webpackConfigOrServeUrl: string) => Promise<{
1
+ import { FfmpegExecutable } from 'remotion';
2
+ import { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
+ export declare const prepareServer: ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }: {
4
+ webpackConfigOrServeUrl: string;
5
+ downloadDir: string;
6
+ onDownload: RenderMediaOnDownload;
7
+ onError: (err: Error) => void;
8
+ ffmpegExecutable: FfmpegExecutable;
9
+ port: number | null;
10
+ }) => Promise<{
2
11
  serveUrl: string;
3
- closeServer: () => Promise<void>;
12
+ closeServer: () => Promise<unknown>;
13
+ offthreadPort: number;
4
14
  }>;
@@ -3,17 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareServer = void 0;
4
4
  const is_serve_url_1 = require("./is-serve-url");
5
5
  const serve_static_1 = require("./serve-static");
6
- const prepareServer = async (webpackConfigOrServeUrl) => {
6
+ const prepareServer = async ({ downloadDir, ffmpegExecutable, onDownload, onError, webpackConfigOrServeUrl, port, }) => {
7
7
  if ((0, is_serve_url_1.isServeUrl)(webpackConfigOrServeUrl)) {
8
+ const { port: offthreadPort, close: closeProxy } = await (0, serve_static_1.serveStatic)(null, {
9
+ downloadDir,
10
+ onDownload,
11
+ onError,
12
+ ffmpegExecutable,
13
+ port,
14
+ });
8
15
  return Promise.resolve({
9
16
  serveUrl: webpackConfigOrServeUrl,
10
- closeServer: () => Promise.resolve(undefined),
17
+ closeServer: () => closeProxy(),
18
+ offthreadPort,
11
19
  });
12
20
  }
13
- const { port, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl);
21
+ const { port: serverPort, close } = await (0, serve_static_1.serveStatic)(webpackConfigOrServeUrl, {
22
+ downloadDir,
23
+ onDownload,
24
+ onError,
25
+ ffmpegExecutable,
26
+ port,
27
+ });
14
28
  return Promise.resolve({
15
- closeServer: () => close(),
16
- serveUrl: `http://localhost:${port}`,
29
+ closeServer: () => {
30
+ return close();
31
+ },
32
+ serveUrl: `http://localhost:${serverPort}`,
33
+ offthreadPort: serverPort,
17
34
  });
18
35
  };
19
36
  exports.prepareServer = prepareServer;