44 * for use with LLM providers that don't support GIF format
55 */
66
7- import gifFrames from "gif-frames " ;
7+ import { parseGIF , decompressFrames } from "gifuct-js " ;
88import sharp from "sharp" ;
99import { log } from "../misc/logger" ;
1010
@@ -168,13 +168,11 @@ async function extractFramesInternal(
168168 gifBuffer = gifSource ;
169169 }
170170
171- // 2. Get frame metadata to determine total frame count
172- // Extract all frames first to get accurate count
173- const allFramesData = await gifFrames ( {
174- url : gifBuffer ,
175- frames : "all" ,
176- cumulative : true , // Important: composite frames for GIFs with delta encoding
177- } ) ;
171+ // 2. Parse and decompress the GIF
172+ // parseGIF expects ArrayBuffer, convert Buffer to ArrayBuffer
173+ const uint8Array = new Uint8Array ( gifBuffer ) ;
174+ const gif = parseGIF ( uint8Array . buffer ) ;
175+ const allFramesData = decompressFrames ( gif , true ) ; // buildPatch=true for RGBA data
178176
179177 const totalFrames = allFramesData . length ;
180178 log . info ( `GIF Processor: Total frames in GIF: ${ totalFrames } ` ) ;
@@ -276,7 +274,7 @@ function calculateKeyframeIndices(
276274/**
277275 * Process a single GIF frame: convert to JPEG and compress
278276 *
279- * @param frameData - Frame data from gif-frames
277+ * @param frameData - Frame data from gifuct-js
280278 * @param frameNumber - Sequential frame number in the output (0-indexed)
281279 * @param totalOutputFrames - Total number of frames in the output (keyframes)
282280 * @param originalFrameIndex - Original frame index in the source GIF
@@ -285,28 +283,30 @@ function calculateKeyframeIndices(
285283 * @returns Processed frame with base64 data
286284 */
287285async function processFrame (
288- frameData : { getImage : ( ) => NodeJS . ReadableStream } ,
286+ frameData : {
287+ patch : Uint8ClampedArray ;
288+ dims : { width : number ; height : number ; top : number ; left : number } ;
289+ } ,
289290 frameNumber : number ,
290291 totalOutputFrames : number ,
291292 originalFrameIndex : number ,
292293 totalSourceFrames : number ,
293294 config : Required < GifProcessorConfig > ,
294295) : Promise < ProcessedGifFrame > {
295- // 1. Get the frame as a buffer
296- const frameStream = frameData . getImage ( ) ;
297- const chunks : Buffer [ ] = [ ] ;
298-
299- // Convert stream to buffer
300- await new Promise < void > ( ( resolve , reject ) => {
301- frameStream . on ( "data" , ( chunk ) => chunks . push ( Buffer . from ( chunk ) ) ) ;
302- frameStream . on ( "end" , ( ) => resolve ( ) ) ;
303- frameStream . on ( "error" , ( error ) => reject ( error ) ) ;
304- } ) ;
305-
306- const frameBuffer = Buffer . concat ( chunks ) ;
296+ // 1. Convert RGBA pixel data to raw buffer
297+ // gifuct-js provides patch as Uint8ClampedArray with RGBA pixel data
298+ const { patch, dims } = frameData ;
299+ const frameBuffer = Buffer . from ( patch . buffer ) ;
307300
308301 // 2. Process with sharp: resize and convert to JPEG
309- const processedBuffer = await sharp ( frameBuffer )
302+ // Create Sharp instance from raw RGBA pixel data
303+ const processedBuffer = await sharp ( frameBuffer , {
304+ raw : {
305+ width : dims . width ,
306+ height : dims . height ,
307+ channels : 4 , // RGBA
308+ } ,
309+ } )
310310 . resize ( config . maxWidth , undefined , {
311311 fit : "inside" , // Maintain aspect ratio
312312 withoutEnlargement : true , // Don't upscale small images
0 commit comments