@remotion/media 4.0.356 → 4.0.358
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.
- package/dist/audio/audio-for-preview.d.ts +30 -0
- package/dist/audio/audio-for-preview.js +229 -0
- package/dist/audio/audio-for-rendering.js +35 -19
- package/dist/audio/audio.js +7 -49
- package/dist/audio/props.d.ts +8 -14
- package/dist/audio-extraction/audio-cache.d.ts +1 -1
- package/dist/audio-extraction/audio-cache.js +5 -1
- package/dist/audio-extraction/audio-iterator.d.ts +4 -1
- package/dist/audio-extraction/audio-iterator.js +22 -10
- package/dist/audio-extraction/audio-manager.d.ts +8 -37
- package/dist/audio-extraction/audio-manager.js +35 -8
- package/dist/audio-extraction/extract-audio.d.ts +9 -2
- package/dist/audio-extraction/extract-audio.js +29 -15
- package/dist/caches.d.ts +9 -44
- package/dist/convert-audiodata/combine-audiodata.js +2 -23
- package/dist/convert-audiodata/convert-audiodata.d.ts +1 -5
- package/dist/convert-audiodata/convert-audiodata.js +16 -24
- package/dist/esm/index.mjs +2864 -2173
- package/dist/extract-frame-and-audio.d.ts +6 -7
- package/dist/extract-frame-and-audio.js +28 -19
- package/dist/{get-sink-weak.d.ts → get-sink.d.ts} +1 -1
- package/dist/get-sink.js +15 -0
- package/dist/get-time-in-seconds.d.ts +11 -0
- package/dist/get-time-in-seconds.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/is-network-error.d.ts +6 -0
- package/dist/is-network-error.js +17 -0
- package/dist/render-timestamp-range.d.ts +1 -0
- package/dist/render-timestamp-range.js +9 -0
- package/dist/show-in-timeline.d.ts +8 -0
- package/dist/show-in-timeline.js +31 -0
- package/dist/use-media-in-timeline.d.ts +19 -0
- package/dist/use-media-in-timeline.js +103 -0
- package/dist/video/media-player.d.ts +34 -7
- package/dist/video/media-player.js +164 -63
- package/dist/video/props.d.ts +1 -0
- package/dist/video/video-for-preview.d.ts +17 -9
- package/dist/video/video-for-preview.js +138 -92
- package/dist/video/video-for-rendering.d.ts +3 -0
- package/dist/video/video-for-rendering.js +58 -25
- package/dist/video/video.js +6 -10
- package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +18 -6
- package/dist/video-extraction/extract-frame-via-broadcast-channel.js +21 -7
- package/dist/video-extraction/extract-frame.d.ts +20 -2
- package/dist/video-extraction/extract-frame.js +41 -9
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +5 -3
- package/dist/video-extraction/get-frames-since-keyframe.js +7 -4
- package/dist/video-extraction/keyframe-bank.d.ts +3 -2
- package/dist/video-extraction/keyframe-bank.js +32 -12
- package/dist/video-extraction/keyframe-manager.d.ts +3 -8
- package/dist/video-extraction/keyframe-manager.js +25 -10
- package/package.json +54 -54
- package/LICENSE.md +0 -49
- package/dist/convert-audiodata/apply-tonefrequency.d.ts +0 -2
- package/dist/convert-audiodata/apply-tonefrequency.js +0 -44
- package/dist/convert-audiodata/wsola.d.ts +0 -13
- package/dist/convert-audiodata/wsola.js +0 -197
- package/dist/get-sink-weak.js +0 -23
- package/dist/log.d.ts +0 -10
- package/dist/log.js +0 -33
|
@@ -15,16 +15,20 @@ if (window.remotion_broadcastChannel && window.remotion_isMainTab) {
|
|
|
15
15
|
includeVideo: data.includeVideo,
|
|
16
16
|
loop: data.loop,
|
|
17
17
|
audioStreamIndex: data.audioStreamIndex,
|
|
18
|
+
trimAfter: data.trimAfter,
|
|
19
|
+
trimBefore: data.trimBefore,
|
|
20
|
+
fps: data.fps,
|
|
18
21
|
});
|
|
19
|
-
if (result === 'cannot-decode') {
|
|
22
|
+
if (result.type === 'cannot-decode') {
|
|
20
23
|
const cannotDecodeResponse = {
|
|
21
24
|
type: 'response-cannot-decode',
|
|
22
25
|
id: data.id,
|
|
26
|
+
durationInSeconds: result.durationInSeconds,
|
|
23
27
|
};
|
|
24
28
|
window.remotion_broadcastChannel.postMessage(cannotDecodeResponse);
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
|
-
if (result === 'network-error') {
|
|
31
|
+
if (result.type === 'network-error') {
|
|
28
32
|
const networkErrorResponse = {
|
|
29
33
|
type: 'response-network-error',
|
|
30
34
|
id: data.id,
|
|
@@ -32,7 +36,7 @@ if (window.remotion_broadcastChannel && window.remotion_isMainTab) {
|
|
|
32
36
|
window.remotion_broadcastChannel.postMessage(networkErrorResponse);
|
|
33
37
|
return;
|
|
34
38
|
}
|
|
35
|
-
if (result === 'unknown-container-format') {
|
|
39
|
+
if (result.type === 'unknown-container-format') {
|
|
36
40
|
const unknownContainerFormatResponse = {
|
|
37
41
|
type: 'response-unknown-container-format',
|
|
38
42
|
id: data.id,
|
|
@@ -72,7 +76,7 @@ if (window.remotion_broadcastChannel && window.remotion_isMainTab) {
|
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, isClientSideRendering, loop, audioStreamIndex, }) => {
|
|
79
|
+
export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, isClientSideRendering, loop, audioStreamIndex, trimAfter, trimBefore, fps, }) => {
|
|
76
80
|
if (isClientSideRendering || window.remotion_isMainTab) {
|
|
77
81
|
return extractFrameAndAudio({
|
|
78
82
|
logLevel,
|
|
@@ -84,6 +88,9 @@ export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel,
|
|
|
84
88
|
includeVideo,
|
|
85
89
|
loop,
|
|
86
90
|
audioStreamIndex,
|
|
91
|
+
trimAfter,
|
|
92
|
+
trimBefore,
|
|
93
|
+
fps,
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
const requestId = crypto.randomUUID();
|
|
@@ -98,6 +105,7 @@ export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel,
|
|
|
98
105
|
}
|
|
99
106
|
if (data.type === 'response-success') {
|
|
100
107
|
resolve({
|
|
108
|
+
type: 'success',
|
|
101
109
|
frame: data.frame ? data.frame : null,
|
|
102
110
|
audio: data.audio ? data.audio : null,
|
|
103
111
|
durationInSeconds: data.durationInSeconds
|
|
@@ -113,17 +121,20 @@ export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel,
|
|
|
113
121
|
return;
|
|
114
122
|
}
|
|
115
123
|
if (data.type === 'response-cannot-decode') {
|
|
116
|
-
resolve(
|
|
124
|
+
resolve({
|
|
125
|
+
type: 'cannot-decode',
|
|
126
|
+
durationInSeconds: data.durationInSeconds,
|
|
127
|
+
});
|
|
117
128
|
window.remotion_broadcastChannel.removeEventListener('message', onMessage);
|
|
118
129
|
return;
|
|
119
130
|
}
|
|
120
131
|
if (data.type === 'response-network-error') {
|
|
121
|
-
resolve('network-error');
|
|
132
|
+
resolve({ type: 'network-error' });
|
|
122
133
|
window.remotion_broadcastChannel.removeEventListener('message', onMessage);
|
|
123
134
|
return;
|
|
124
135
|
}
|
|
125
136
|
if (data.type === 'response-unknown-container-format') {
|
|
126
|
-
resolve('unknown-container-format');
|
|
137
|
+
resolve({ type: 'unknown-container-format' });
|
|
127
138
|
window.remotion_broadcastChannel.removeEventListener('message', onMessage);
|
|
128
139
|
return;
|
|
129
140
|
}
|
|
@@ -143,6 +154,9 @@ export const extractFrameViaBroadcastChannel = ({ src, timeInSeconds, logLevel,
|
|
|
143
154
|
includeVideo,
|
|
144
155
|
loop,
|
|
145
156
|
audioStreamIndex,
|
|
157
|
+
trimAfter,
|
|
158
|
+
trimBefore,
|
|
159
|
+
fps,
|
|
146
160
|
};
|
|
147
161
|
window.remotion_broadcastChannel.postMessage(request);
|
|
148
162
|
let timeoutId;
|
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
import type { VideoSample } from 'mediabunny';
|
|
2
2
|
import { type LogLevel } from 'remotion';
|
|
3
|
-
|
|
3
|
+
type ExtractFrameResult = {
|
|
4
|
+
type: 'success';
|
|
5
|
+
frame: VideoSample | null;
|
|
6
|
+
durationInSeconds: number | null;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'cannot-decode';
|
|
9
|
+
durationInSeconds: number | null;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'unknown-container-format';
|
|
12
|
+
};
|
|
13
|
+
type ExtractFrameParams = {
|
|
4
14
|
src: string;
|
|
5
15
|
timeInSeconds: number;
|
|
6
16
|
logLevel: LogLevel;
|
|
7
17
|
loop: boolean;
|
|
8
|
-
|
|
18
|
+
trimAfter: number | undefined;
|
|
19
|
+
trimBefore: number | undefined;
|
|
20
|
+
playbackRate: number;
|
|
21
|
+
fps: number;
|
|
22
|
+
};
|
|
23
|
+
declare const extractFrameInternal: ({ src, timeInSeconds: unloopedTimeInSeconds, logLevel, loop, trimAfter, trimBefore, playbackRate, fps, }: ExtractFrameParams) => Promise<ExtractFrameResult>;
|
|
24
|
+
type ExtractFrameReturnType = Awaited<ReturnType<typeof extractFrameInternal>>;
|
|
25
|
+
export declare const extractFrame: (params: ExtractFrameParams) => Promise<ExtractFrameReturnType>;
|
|
26
|
+
export {};
|
|
@@ -1,20 +1,40 @@
|
|
|
1
1
|
import { keyframeManager } from '../caches';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getSink } from '../get-sink';
|
|
3
|
+
import { getTimeInSeconds } from '../get-time-in-seconds';
|
|
4
|
+
const extractFrameInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds, logLevel, loop, trimAfter, trimBefore, playbackRate, fps, }) => {
|
|
5
|
+
const sink = await getSink(src, logLevel);
|
|
5
6
|
const video = await sink.getVideo();
|
|
6
7
|
if (video === 'no-video-track') {
|
|
7
8
|
throw new Error(`No video track found for ${src}`);
|
|
8
9
|
}
|
|
9
10
|
if (video === 'cannot-decode') {
|
|
10
|
-
return 'cannot-decode';
|
|
11
|
+
return { type: 'cannot-decode', durationInSeconds: await sink.getDuration() };
|
|
11
12
|
}
|
|
12
13
|
if (video === 'unknown-container-format') {
|
|
13
|
-
return 'unknown-container-format';
|
|
14
|
+
return { type: 'unknown-container-format' };
|
|
15
|
+
}
|
|
16
|
+
let mediaDurationInSeconds = null;
|
|
17
|
+
if (loop) {
|
|
18
|
+
mediaDurationInSeconds = await sink.getDuration();
|
|
19
|
+
}
|
|
20
|
+
const timeInSeconds = getTimeInSeconds({
|
|
21
|
+
loop,
|
|
22
|
+
mediaDurationInSeconds,
|
|
23
|
+
unloopedTimeInSeconds,
|
|
24
|
+
src,
|
|
25
|
+
trimAfter,
|
|
26
|
+
playbackRate,
|
|
27
|
+
trimBefore,
|
|
28
|
+
fps,
|
|
29
|
+
ifNoMediaDuration: 'fail',
|
|
30
|
+
});
|
|
31
|
+
if (timeInSeconds === null) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'success',
|
|
34
|
+
frame: null,
|
|
35
|
+
durationInSeconds: await sink.getDuration(),
|
|
36
|
+
};
|
|
14
37
|
}
|
|
15
|
-
const timeInSeconds = loop
|
|
16
|
-
? unloopedTimeinSeconds % (await sink.getDuration())
|
|
17
|
-
: unloopedTimeinSeconds;
|
|
18
38
|
const keyframeBank = await keyframeManager.requestKeyframeBank({
|
|
19
39
|
packetSink: video.packetSink,
|
|
20
40
|
videoSampleSink: video.sampleSink,
|
|
@@ -22,6 +42,18 @@ export const extractFrame = async ({ src, timeInSeconds: unloopedTimeinSeconds,
|
|
|
22
42
|
src,
|
|
23
43
|
logLevel,
|
|
24
44
|
});
|
|
45
|
+
if (!keyframeBank) {
|
|
46
|
+
return {
|
|
47
|
+
type: 'success',
|
|
48
|
+
frame: null,
|
|
49
|
+
durationInSeconds: await sink.getDuration(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
25
52
|
const frame = await keyframeBank.getFrameFromTimestamp(timeInSeconds);
|
|
26
|
-
return frame;
|
|
53
|
+
return { type: 'success', frame, durationInSeconds: await sink.getDuration() };
|
|
54
|
+
};
|
|
55
|
+
let queue = Promise.resolve(undefined);
|
|
56
|
+
export const extractFrame = (params) => {
|
|
57
|
+
queue = queue.then(() => extractFrameInternal(params));
|
|
58
|
+
return queue;
|
|
27
59
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EncodedPacket } from 'mediabunny';
|
|
2
2
|
import { AudioSampleSink, EncodedPacketSink, VideoSampleSink } from 'mediabunny';
|
|
3
|
+
import type { LogLevel } from 'remotion';
|
|
3
4
|
type VideoSinks = {
|
|
4
5
|
sampleSink: VideoSampleSink;
|
|
5
6
|
packetSink: EncodedPacketSink;
|
|
@@ -9,7 +10,7 @@ type AudioSinks = {
|
|
|
9
10
|
};
|
|
10
11
|
export type AudioSinkResult = AudioSinks | 'no-audio-track' | 'cannot-decode-audio' | 'unknown-container-format';
|
|
11
12
|
export type VideoSinkResult = VideoSinks | 'no-video-track' | 'cannot-decode' | 'unknown-container-format';
|
|
12
|
-
export declare const getSinks: (src: string) => Promise<
|
|
13
|
+
export declare const getSinks: (src: string) => Promise<{
|
|
13
14
|
getVideo: () => Promise<VideoSinkResult>;
|
|
14
15
|
getAudio: (index: number) => Promise<AudioSinkResult>;
|
|
15
16
|
actualMatroskaTimestamps: {
|
|
@@ -18,11 +19,12 @@ export declare const getSinks: (src: string) => Promise<WeakRef<{
|
|
|
18
19
|
};
|
|
19
20
|
isMatroska: boolean;
|
|
20
21
|
getDuration: () => Promise<number>;
|
|
21
|
-
}
|
|
22
|
+
}>;
|
|
22
23
|
export type GetSink = Awaited<ReturnType<typeof getSinks>>;
|
|
23
|
-
export declare const getFramesSinceKeyframe: ({ packetSink, videoSampleSink, startPacket, }: {
|
|
24
|
+
export declare const getFramesSinceKeyframe: ({ packetSink, videoSampleSink, startPacket, logLevel, }: {
|
|
24
25
|
packetSink: EncodedPacketSink;
|
|
25
26
|
videoSampleSink: VideoSampleSink;
|
|
26
27
|
startPacket: EncodedPacket;
|
|
28
|
+
logLevel: LogLevel;
|
|
27
29
|
}) => Promise<import("./keyframe-bank").KeyframeBank>;
|
|
28
30
|
export {};
|
|
@@ -72,15 +72,17 @@ export const getSinks = async (src) => {
|
|
|
72
72
|
audioSinksPromise[index] = getAudioSinks(index);
|
|
73
73
|
return audioSinksPromise[index];
|
|
74
74
|
};
|
|
75
|
-
return
|
|
75
|
+
return {
|
|
76
76
|
getVideo: () => getVideoSinksPromise(),
|
|
77
77
|
getAudio: (index) => getAudioSinksPromise(index),
|
|
78
78
|
actualMatroskaTimestamps: rememberActualMatroskaTimestamps(isMatroska),
|
|
79
79
|
isMatroska,
|
|
80
|
-
getDuration: () =>
|
|
81
|
-
|
|
80
|
+
getDuration: () => {
|
|
81
|
+
return input.computeDuration();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
82
84
|
};
|
|
83
|
-
export const getFramesSinceKeyframe = async ({ packetSink, videoSampleSink, startPacket, }) => {
|
|
85
|
+
export const getFramesSinceKeyframe = async ({ packetSink, videoSampleSink, startPacket, logLevel, }) => {
|
|
84
86
|
const nextKeyPacket = await packetSink.getNextKeyPacket(startPacket, {
|
|
85
87
|
verifyKeyPackets: true,
|
|
86
88
|
});
|
|
@@ -89,6 +91,7 @@ export const getFramesSinceKeyframe = async ({ packetSink, videoSampleSink, star
|
|
|
89
91
|
startTimestampInSeconds: startPacket.timestamp,
|
|
90
92
|
endTimestampInSeconds: nextKeyPacket ? nextKeyPacket.timestamp : Infinity,
|
|
91
93
|
sampleIterator,
|
|
94
|
+
logLevel,
|
|
92
95
|
});
|
|
93
96
|
return keyframeBank;
|
|
94
97
|
};
|
|
@@ -4,7 +4,7 @@ export type KeyframeBank = {
|
|
|
4
4
|
startTimestampInSeconds: number;
|
|
5
5
|
endTimestampInSeconds: number;
|
|
6
6
|
getFrameFromTimestamp: (timestamp: number) => Promise<VideoSample | null>;
|
|
7
|
-
prepareForDeletion: () =>
|
|
7
|
+
prepareForDeletion: (logLevel: LogLevel) => void;
|
|
8
8
|
deleteFramesBeforeTimestamp: ({ logLevel, src, timestampInSeconds, }: {
|
|
9
9
|
timestampInSeconds: number;
|
|
10
10
|
logLevel: LogLevel;
|
|
@@ -18,8 +18,9 @@ export type KeyframeBank = {
|
|
|
18
18
|
};
|
|
19
19
|
getLastUsed: () => number;
|
|
20
20
|
};
|
|
21
|
-
export declare const makeKeyframeBank: ({ startTimestampInSeconds, endTimestampInSeconds, sampleIterator, }: {
|
|
21
|
+
export declare const makeKeyframeBank: ({ startTimestampInSeconds, endTimestampInSeconds, sampleIterator, logLevel: parentLogLevel, }: {
|
|
22
22
|
startTimestampInSeconds: number;
|
|
23
23
|
endTimestampInSeconds: number;
|
|
24
24
|
sampleIterator: AsyncGenerator<VideoSample, void, unknown>;
|
|
25
|
+
logLevel: LogLevel;
|
|
25
26
|
}) => KeyframeBank;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Internals } from 'remotion';
|
|
2
|
+
import { renderTimestampRange } from '../render-timestamp-range';
|
|
2
3
|
// Round to only 4 digits, because WebM has a timescale of 1_000, e.g. framer.webm
|
|
3
4
|
const roundTo4Digits = (timestamp) => {
|
|
4
5
|
return Math.round(timestamp * 1000) / 1000;
|
|
5
6
|
};
|
|
6
|
-
export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSeconds, sampleIterator, }) => {
|
|
7
|
+
export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSeconds, sampleIterator, logLevel: parentLogLevel, }) => {
|
|
8
|
+
Internals.Log.verbose({ logLevel: parentLogLevel, tag: '@remotion/media' }, `Creating keyframe bank from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
|
|
7
9
|
const frames = {};
|
|
8
10
|
const frameTimestamps = [];
|
|
9
11
|
let lastUsed = Date.now();
|
|
@@ -19,7 +21,7 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
19
21
|
return true;
|
|
20
22
|
}
|
|
21
23
|
return (roundTo4Digits(lastFrame.timestamp + lastFrame.duration) >
|
|
22
|
-
roundTo4Digits(timestamp));
|
|
24
|
+
roundTo4Digits(timestamp) + 0.001);
|
|
23
25
|
};
|
|
24
26
|
const addFrame = (frame) => {
|
|
25
27
|
frames[frame.timestamp] = frame;
|
|
@@ -53,7 +55,11 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
53
55
|
if (!sample) {
|
|
54
56
|
return null;
|
|
55
57
|
}
|
|
56
|
-
if (roundTo4Digits(sample.timestamp) <=
|
|
58
|
+
if (roundTo4Digits(sample.timestamp) <=
|
|
59
|
+
roundTo4Digits(timestampInSeconds) ||
|
|
60
|
+
// Match 0.3333333333 to 0.33355555
|
|
61
|
+
// this does not satisfy the previous condition, since one rounds up and one rounds down
|
|
62
|
+
Math.abs(sample.timestamp - timestampInSeconds) <= 0.001) {
|
|
57
63
|
return sample;
|
|
58
64
|
}
|
|
59
65
|
}
|
|
@@ -62,12 +68,15 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
62
68
|
const hasTimestampInSecond = async (timestamp) => {
|
|
63
69
|
return (await getFrameFromTimestamp(timestamp)) !== null;
|
|
64
70
|
};
|
|
65
|
-
const prepareForDeletion =
|
|
71
|
+
const prepareForDeletion = (logLevel) => {
|
|
72
|
+
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Preparing for deletion of keyframe bank from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
|
|
66
73
|
// Cleanup frames that have been extracted that might not have been retrieved yet
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
sampleIterator.return().then((result) => {
|
|
75
|
+
if (result.value) {
|
|
76
|
+
result.value.close();
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
});
|
|
71
80
|
for (const frameTimestamp of frameTimestamps) {
|
|
72
81
|
if (!frames[frameTimestamp]) {
|
|
73
82
|
continue;
|
|
@@ -79,7 +88,8 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
79
88
|
frameTimestamps.length = 0;
|
|
80
89
|
};
|
|
81
90
|
const deleteFramesBeforeTimestamp = ({ logLevel, src, timestampInSeconds, }) => {
|
|
82
|
-
|
|
91
|
+
const deletedTimestamps = [];
|
|
92
|
+
for (const frameTimestamp of frameTimestamps.slice()) {
|
|
83
93
|
const isLast = frameTimestamp === frameTimestamps[frameTimestamps.length - 1];
|
|
84
94
|
// Don't delete the last frame, since it may be the last one in the video!
|
|
85
95
|
if (isLast) {
|
|
@@ -93,9 +103,12 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
93
103
|
frameTimestamps.splice(frameTimestamps.indexOf(frameTimestamp), 1);
|
|
94
104
|
frames[frameTimestamp].close();
|
|
95
105
|
delete frames[frameTimestamp];
|
|
96
|
-
|
|
106
|
+
deletedTimestamps.push(frameTimestamp);
|
|
97
107
|
}
|
|
98
108
|
}
|
|
109
|
+
if (deletedTimestamps.length > 0) {
|
|
110
|
+
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Deleted ${deletedTimestamps.length} frame${deletedTimestamps.length === 1 ? '' : 's'} ${renderTimestampRange(deletedTimestamps)} for src ${src} because it is lower than ${timestampInSeconds}. Remaining: ${renderTimestampRange(frameTimestamps)}`);
|
|
111
|
+
}
|
|
99
112
|
};
|
|
100
113
|
const getOpenFrameCount = () => {
|
|
101
114
|
return {
|
|
@@ -106,11 +119,18 @@ export const makeKeyframeBank = ({ startTimestampInSeconds, endTimestampInSecond
|
|
|
106
119
|
const getLastUsed = () => {
|
|
107
120
|
return lastUsed;
|
|
108
121
|
};
|
|
122
|
+
let queue = Promise.resolve(undefined);
|
|
109
123
|
const keyframeBank = {
|
|
110
124
|
startTimestampInSeconds,
|
|
111
125
|
endTimestampInSeconds,
|
|
112
|
-
getFrameFromTimestamp
|
|
113
|
-
|
|
126
|
+
getFrameFromTimestamp: (timestamp) => {
|
|
127
|
+
queue = queue.then(() => getFrameFromTimestamp(timestamp));
|
|
128
|
+
return queue;
|
|
129
|
+
},
|
|
130
|
+
prepareForDeletion: (logLevel) => {
|
|
131
|
+
queue = queue.then(() => prepareForDeletion(logLevel));
|
|
132
|
+
return queue;
|
|
133
|
+
},
|
|
114
134
|
hasTimestampInSecond,
|
|
115
135
|
addFrame,
|
|
116
136
|
deleteFramesBeforeTimestamp,
|
|
@@ -3,21 +3,16 @@ import { type LogLevel } from 'remotion';
|
|
|
3
3
|
import { type KeyframeBank } from './keyframe-bank';
|
|
4
4
|
export declare const makeKeyframeManager: () => {
|
|
5
5
|
requestKeyframeBank: ({ packetSink, timestamp, videoSampleSink, src, logLevel, }: {
|
|
6
|
-
timestamp: number;
|
|
7
6
|
packetSink: EncodedPacketSink;
|
|
7
|
+
timestamp: number;
|
|
8
8
|
videoSampleSink: VideoSampleSink;
|
|
9
9
|
src: string;
|
|
10
10
|
logLevel: LogLevel;
|
|
11
|
-
}) => Promise<KeyframeBank>;
|
|
12
|
-
addKeyframeBank: ({ src, bank, startTimestampInSeconds, }: {
|
|
13
|
-
src: string;
|
|
14
|
-
bank: Promise<KeyframeBank>;
|
|
15
|
-
startTimestampInSeconds: number;
|
|
16
|
-
}) => void;
|
|
11
|
+
}) => Promise<KeyframeBank | null>;
|
|
17
12
|
getCacheStats: () => Promise<{
|
|
18
13
|
count: number;
|
|
19
14
|
totalSize: number;
|
|
20
15
|
}>;
|
|
21
|
-
clearAll: () => Promise<void>;
|
|
16
|
+
clearAll: (logLevel: LogLevel) => Promise<void>;
|
|
22
17
|
};
|
|
23
18
|
export type KeyframeManager = Awaited<ReturnType<typeof makeKeyframeManager>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Internals } from 'remotion';
|
|
2
2
|
import { getMaxVideoCacheSize, getTotalCacheStats, SAFE_BACK_WINDOW_IN_SECONDS, } from '../caches';
|
|
3
|
+
import { renderTimestampRange } from '../render-timestamp-range';
|
|
3
4
|
import { getFramesSinceKeyframe } from './get-frames-since-keyframe';
|
|
4
5
|
export const makeKeyframeManager = () => {
|
|
5
6
|
// src => {[startTimestampInSeconds]: KeyframeBank
|
|
@@ -20,7 +21,7 @@ export const makeKeyframeManager = () => {
|
|
|
20
21
|
if (size === 0) {
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
23
|
-
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Open frames for src ${src}: ${timestamps
|
|
24
|
+
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Open frames for src ${src}: ${renderTimestampRange(timestamps)}`);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Video cache stats: ${count} open frames, ${totalSize} bytes`);
|
|
@@ -62,7 +63,7 @@ export const makeKeyframeManager = () => {
|
|
|
62
63
|
const deleteOldestKeyframeBank = async (logLevel) => {
|
|
63
64
|
const { bank: mostInThePastBank, src: mostInThePastSrc } = await getTheKeyframeBankMostInThePast();
|
|
64
65
|
if (mostInThePastBank) {
|
|
65
|
-
await mostInThePastBank.prepareForDeletion();
|
|
66
|
+
await mostInThePastBank.prepareForDeletion(logLevel);
|
|
66
67
|
delete sources[mostInThePastSrc][mostInThePastBank.startTimestampInSeconds];
|
|
67
68
|
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Deleted frames for src ${mostInThePastSrc} from ${mostInThePastBank.startTimestampInSeconds}sec to ${mostInThePastBank.endTimestampInSeconds}sec to free up memory.`);
|
|
68
69
|
}
|
|
@@ -85,7 +86,7 @@ export const makeKeyframeManager = () => {
|
|
|
85
86
|
const bank = await sources[src][startTimeInSeconds];
|
|
86
87
|
const { endTimestampInSeconds, startTimestampInSeconds } = bank;
|
|
87
88
|
if (endTimestampInSeconds < threshold) {
|
|
88
|
-
await bank.prepareForDeletion();
|
|
89
|
+
await bank.prepareForDeletion(logLevel);
|
|
89
90
|
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `[Video] Cleared frames for src ${src} from ${startTimestampInSeconds}sec to ${endTimestampInSeconds}sec`);
|
|
90
91
|
delete sources[src][startTimeInSeconds];
|
|
91
92
|
}
|
|
@@ -104,7 +105,10 @@ export const makeKeyframeManager = () => {
|
|
|
104
105
|
verifyKeyPackets: true,
|
|
105
106
|
});
|
|
106
107
|
if (!startPacket) {
|
|
107
|
-
|
|
108
|
+
// e.g. https://discord.com/channels/809501355504959528/809501355504959531/1424400511070765086
|
|
109
|
+
// The video has an offset and the first frame is at time 0.033sec
|
|
110
|
+
// we shall not crash here but handle it gracefully
|
|
111
|
+
return null;
|
|
108
112
|
}
|
|
109
113
|
const startTimestampInSeconds = startPacket.timestamp;
|
|
110
114
|
const existingBank = sources[src]?.[startTimestampInSeconds];
|
|
@@ -114,6 +118,7 @@ export const makeKeyframeManager = () => {
|
|
|
114
118
|
packetSink,
|
|
115
119
|
videoSampleSink,
|
|
116
120
|
startPacket,
|
|
121
|
+
logLevel,
|
|
117
122
|
});
|
|
118
123
|
addKeyframeBank({ src, bank: newKeyframeBank, startTimestampInSeconds });
|
|
119
124
|
return newKeyframeBank;
|
|
@@ -122,16 +127,17 @@ export const makeKeyframeManager = () => {
|
|
|
122
127
|
if (await (await existingBank).hasTimestampInSecond(timestamp)) {
|
|
123
128
|
return existingBank;
|
|
124
129
|
}
|
|
125
|
-
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Keyframe bank exists but
|
|
130
|
+
Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Keyframe bank exists but frame at time ${timestamp} does not exist anymore.`);
|
|
126
131
|
// Bank exists but frames have already been evicted!
|
|
127
132
|
// First delete it entirely
|
|
128
|
-
await (await existingBank).prepareForDeletion();
|
|
133
|
+
await (await existingBank).prepareForDeletion(logLevel);
|
|
129
134
|
delete sources[src][startTimestampInSeconds];
|
|
130
135
|
// Then refetch
|
|
131
136
|
const replacementKeybank = getFramesSinceKeyframe({
|
|
132
137
|
packetSink,
|
|
133
138
|
videoSampleSink,
|
|
134
139
|
startPacket,
|
|
140
|
+
logLevel,
|
|
135
141
|
});
|
|
136
142
|
addKeyframeBank({ src, bank: replacementKeybank, startTimestampInSeconds });
|
|
137
143
|
return replacementKeybank;
|
|
@@ -152,20 +158,29 @@ export const makeKeyframeManager = () => {
|
|
|
152
158
|
});
|
|
153
159
|
return keyframeBank;
|
|
154
160
|
};
|
|
155
|
-
const clearAll = async () => {
|
|
161
|
+
const clearAll = async (logLevel) => {
|
|
156
162
|
const srcs = Object.keys(sources);
|
|
157
163
|
for (const src of srcs) {
|
|
158
164
|
const banks = Object.keys(sources[src]);
|
|
159
165
|
for (const startTimeInSeconds of banks) {
|
|
160
166
|
const bank = await sources[src][startTimeInSeconds];
|
|
161
|
-
|
|
167
|
+
bank.prepareForDeletion(logLevel);
|
|
162
168
|
delete sources[src][startTimeInSeconds];
|
|
163
169
|
}
|
|
164
170
|
}
|
|
165
171
|
};
|
|
172
|
+
let queue = Promise.resolve(undefined);
|
|
166
173
|
return {
|
|
167
|
-
requestKeyframeBank,
|
|
168
|
-
|
|
174
|
+
requestKeyframeBank: ({ packetSink, timestamp, videoSampleSink, src, logLevel, }) => {
|
|
175
|
+
queue = queue.then(() => requestKeyframeBank({
|
|
176
|
+
packetSink,
|
|
177
|
+
timestamp,
|
|
178
|
+
videoSampleSink,
|
|
179
|
+
src,
|
|
180
|
+
logLevel,
|
|
181
|
+
}));
|
|
182
|
+
return queue;
|
|
183
|
+
},
|
|
169
184
|
getCacheStats,
|
|
170
185
|
clearAll,
|
|
171
186
|
};
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
2
|
+
"name": "@remotion/media",
|
|
3
|
+
"version": "4.0.358",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"module": "dist/esm/index.mjs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/media"
|
|
9
|
+
},
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"author": "Jonny Burger <jonny@remotion.dev>, Hunain Ahmed <junaidhunain6@gmail.com>",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/remotion-dev/remotion/issues"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"if-node-18+": "node -e \"const [maj]=process.versions.node.split('.').map(Number); process.exit(maj>=18?0:1)\"",
|
|
17
|
+
"formatting": "prettier --experimental-cli src --check",
|
|
18
|
+
"lint": "eslint src",
|
|
19
|
+
"watch": "tsc -w",
|
|
20
|
+
"test": "node src/test/execute.mjs",
|
|
21
|
+
"make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"mediabunny": "1.23.0",
|
|
25
|
+
"remotion": "4.0.357",
|
|
26
|
+
"webdriverio": "9.19.2"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=16.8.0",
|
|
30
|
+
"react-dom": ">=16.8.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@remotion/eslint-config-internal": "4.0.357",
|
|
34
|
+
"@vitest/browser": "^3.2.4",
|
|
35
|
+
"eslint": "9.19.0",
|
|
36
|
+
"react": "19.0.0",
|
|
37
|
+
"react-dom": "19.0.0",
|
|
38
|
+
"vitest": "3.2.4"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"exports": {
|
|
45
|
+
".": {
|
|
46
|
+
"types": "./dist/index.d.ts",
|
|
47
|
+
"require": "./dist/index.js",
|
|
48
|
+
"module": "./dist/esm/index.mjs",
|
|
49
|
+
"import": "./dist/esm/index.mjs"
|
|
50
|
+
},
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
|
+
},
|
|
53
|
+
"description": "Experimental WebCodecs-based media tags",
|
|
54
|
+
"homepage": "https://remotion.dev/docs/media"
|
|
55
|
+
}
|