Extracting frames from a video in JavaScript
Extracting frames from a video file, for example to display a filmstrip in an editing interface, can be done using Mediabunny.
Here's a extractFrames() function you can use copy and paste into your project:
extract-frames.tstsimport {ALL_FORMATS ,Input ,InputDisposedError ,UrlSource ,VideoSample ,VideoSampleSink } from 'mediabunny';typeOptions = {track : {width : number;height : number};container : string;durationInSeconds : number | null;};export typeExtractFramesTimestampsInSecondsFn = (options :Options ) =>Promise <number[]> | number[];export typeExtractFramesProps = {src : string;timestampsInSeconds : number[] |ExtractFramesTimestampsInSecondsFn ;onVideoSample : (sample :VideoSample ) => void;signal ?:AbortSignal ;};export async functionextractFrames ({src ,timestampsInSeconds ,onVideoSample ,signal }:ExtractFramesProps ):Promise <void> {usinginput = newInput ({formats :ALL_FORMATS ,source : newUrlSource (src ),});const [durationInSeconds ,format ,videoTrack ] = awaitPromise .all ([input .computeDuration (),input .getFormat (),input .getPrimaryVideoTrack ()]);if (!videoTrack ) {throw newError ('No video track found in the input');}if (signal ?.aborted ) {throw newError ('Aborted');}consttimestamps =typeoftimestampsInSeconds === 'function'? awaittimestampsInSeconds ({track : {width :videoTrack .displayWidth ,height :videoTrack .displayHeight ,},container :format .name ,durationInSeconds ,}):timestampsInSeconds ;if (timestamps .length === 0) {return;}if (signal ?.aborted ) {throw newError ('Aborted');}constsink = newVideoSampleSink (videoTrack );for await (usingvideoSample ofsink .samplesAtTimestamps (timestamps )) {if (signal ?.aborted ) {break;}if (!videoSample ) {continue;}onVideoSample (videoSample );}}
Example: Extract frames at specific times
tsawait extractFrames({src: 'https://remotion.media/video.mp4',timestampsInSeconds: [0, 1, 2, 3, 4],onVideoSample: (sample) => {// Draw frame to canvasconst canvas = document.createElement('canvas');canvas.width = sample.displayWidth;canvas.height = sample.displayHeight;const ctx = canvas.getContext('2d');sample.draw(ctx!, 0, 0);},});
Example: Create a filmstrip
Extract as many frames as fit in a canvas based on the video's aspect ratio:
extract-frames.tstsxconstcanvasWidth = 500;constcanvasHeight = 80;constfromSeconds = 0;consttoSeconds = 10;awaitextractFrames ({src : 'https://remotion.media/video.mp4',timestampsInSeconds : async ({track ,durationInSeconds }) => {constaspectRatio =track .width /track .height ;constamountOfFramesFit =Math .ceil (canvasWidth / (canvasHeight *aspectRatio ));constsegmentDuration =toSeconds -fromSeconds ;consttimestamps : number[] = [];for (leti = 0;i <amountOfFramesFit ;i ++) {timestamps .push (fromSeconds + (segmentDuration /amountOfFramesFit ) * (i + 0.5));}returntimestamps ;},onVideoSample : (sample ) => {// Process the sampleconsole .log (`Frame at ${sample .timestamp }s`);// Draw to canvas or process as neededconstcanvas =document .createElement ('canvas');canvas .width =sample .displayWidth ;canvas .height =sample .displayHeight ;constctx =canvas .getContext ('2d');sample .draw (ctx !, 0, 0);},});
Memory management
In the example above, the using keyword is used to automatically close the VideoSample and Input objects when they go out of scope. Make sure to keep no references to them to ensure proper cleanup.
Abort frame extraction
Pass an AbortSignal to cancel frame extraction:
extract-frames.tstsxconstcontroller = newAbortController ();// Cancel after 5 secondssetTimeout (() =>controller .abort (), 5000);try {awaitextractFrames ({src : 'https://remotion.media/video.mp4',timestampsInSeconds : [0, 1, 2, 3, 4],onVideoSample : (sample ) => {usingframe =sample ;constcanvas =document .createElement ('canvas');canvas .width =frame .displayWidth ;canvas .height =frame .displayHeight ;constctx =canvas .getContext ('2d');frame .draw (ctx !, 0, 0);},signal :controller .signal ,});console .log ('Frame extraction complete!');} catch (error ) {console .error ('Frame extraction was aborted or failed:',error );}
Setting a timeout
Here is how you can set a maximum duration for extracting frames:
Fail if not able to extract within 10 secondstsconstcontroller = newAbortController ();consttimeoutPromise = newPromise <never>((_ ,reject ) => {consttimeoutId =setTimeout (() => {controller .abort ();reject (newError ('Frame extraction timed out after 10 seconds'));}, 10000);controller .signal .addEventListener ('abort', () =>clearTimeout (timeoutId ), {once : true});});try {awaitPromise .race ([extractFrames ({src : 'https://remotion.media/video.mp4',timestampsInSeconds : [0, 1, 2, 3, 4],onVideoSample : (sample ) => {usingframe =sample ;constcanvas =document .createElement ('canvas');canvas .width =frame .displayWidth ;canvas .height =frame .displayHeight ;constctx =canvas .getContext ('2d');frame .draw (ctx !, 0, 0);},signal :controller .signal ,}),timeoutPromise ,]);console .log ('Frame extraction complete!');} catch (error ) {console .error ('Frame extraction was aborted or failed:',error );}
See also
- Extracting a single thumbnail - Extract a single frame from a video
- Mediabunny Documentation
- Packets & samples in Mediabunny
VideoSampleSinkAPI- Supported formats