@@ -186,6 +186,12 @@ def load(self, buffer, extension):
186186
187187 self .probe ()
188188
189+ def _route (self , label , method , src_file , * args ):
190+ """Dispatch to a gif-transcode route (``method``), labelled for
191+ observability. Override to record per-route metrics/traces. Defaults
192+ to simply calling the method."""
193+ return method (src_file , * args )
194+
189195 def has_transparency (self ):
190196 if self .image :
191197 return has_transparency (self .image )
@@ -311,7 +317,7 @@ def transcode_to_gif(self, src_file):
311317 if (self .context .config .FFMPEG_GIF_PIPELINE == 'gifski'
312318 and self ._gifski_path () is not None ):
313319 return self ._transcode_to_gif_gifski (src_file )
314- return self ._gif_legacy ( src_file )
320+ return self ._route ( 'legacy' , self . _gif_legacy , src_file )
315321
316322 def _gifski_path (self ):
317323 configured = self .context .config .GIFSKI_PATH
@@ -329,27 +335,37 @@ def _transcode_to_gif_gifski(self, src_file):
329335 width , height = self .image_size
330336 max_target = self .context .config .GIFSKI_MAX_TARGET_PIXELS
331337 if max_target and width * height > max_target :
332- return self ._gif_legacy (src_file )
338+ return self ._gifski_oversized_target (src_file )
333339
334340 info = self .gif_info
335341 if info is None :
336342 if self .image :
337343 # Animated webp input (or an unparseable gif that PIL could
338344 # still open): per-frame timing is unknown to us, so use the
339345 # timing-exact legacy path.
340- return self ._gif_legacy ( src_file )
346+ return self ._route ( 'legacy' , self . _gif_legacy , src_file )
341347 # Video source: constant frame rate from ffprobe
342- return self ._gifski_y4m (src_file , self ._video_fps (), "0" )
348+ return self ._route (
349+ 'y4m' , self ._gifski_y4m , src_file , self ._video_fps (), "0" )
343350
344351 if not info .is_uniform_delay :
345352 # Variable frame delays can't be represented in a constant frame
346353 # rate y4m stream; the legacy path preserves them exactly.
347- return self ._gif_legacy ( src_file )
354+ return self ._route ( 'legacy' , self . _gif_legacy , src_file )
348355
349356 repeat = "-1" if info .loop_count is None else str (info .loop_count )
350357 if self ._gif_visibly_transparent (info ):
351- return self ._gifski_png_frames (src_file , info .uniform_fps , repeat )
352- return self ._gifski_y4m (src_file , info .uniform_fps , repeat )
358+ return self ._route (
359+ 'png' , self ._gifski_png_frames , src_file , info .uniform_fps , repeat )
360+ return self ._route (
361+ 'y4m' , self ._gifski_y4m , src_file , info .uniform_fps , repeat )
362+
363+ def _gifski_oversized_target (self , src_file ):
364+ """Chosen when the target output exceeds ``GIFSKI_MAX_TARGET_PIXELS``
365+ (gifski's quantizer memory grows with output size). Override to serve
366+ something faster (e.g. a quick low-quality pass with a background
367+ re-encode). Defaults to the bounded-memory legacy path."""
368+ return self ._route ('legacy_large' , self ._gif_legacy , src_file )
353369
354370 def _gif_visibly_transparent (self , info ):
355371 """GCE transparency flags over-approximate: optimized opaque GIFs
@@ -375,13 +391,30 @@ def _video_fps(self):
375391 fps = DEFAULT_VIDEO_GIF_FPS
376392 return min (fps , MAX_VIDEO_GIF_FPS )
377393
394+ def _gifski_quality (self ):
395+ """Quality (1-100) passed to gifski. Override to vary it per request
396+ (e.g. a low-quality fast pass)."""
397+ return self .context .config .GIFSKI_QUALITY
398+
399+ def _gifski_extra_args (self ):
400+ """Extra flags for the gifski command (e.g. ``['--fast']``). Override
401+ to add them; defaults to none."""
402+ return []
403+
404+ def _gifski_gifsicle_pass (self ):
405+ """Whether to run a final geometry-free ``gifsicle -O3`` pass over
406+ gifski's output. Override to vary it per request (e.g. skip it for a
407+ quick low-quality pass). Defaults to the ``GIFSKI_GIFSICLE_PASS``
408+ config."""
409+ return self .context .config .GIFSKI_GIFSICLE_PASS
410+
378411 def _gifski_cmd (self , out_file , fps ):
379- config = self .context .config
380412 width , height = self .image_size
381413 return [
382414 self ._gifski_path (),
383415 "--quiet" ,
384- "--quality" , "%s" % config .GIFSKI_QUALITY ,
416+ "--quality" , "%s" % self ._gifski_quality (),
417+ ] + self ._gifski_extra_args () + [
385418 "--fps" , "%g" % float (fps ),
386419 # gifski caps output at ~800x600 unless explicitly sized; frames
387420 # are already scaled to exactly this size by ffmpeg
@@ -407,7 +440,7 @@ def _gifski_y4m(self, src_file, fps, repeat):
407440 with named_tmp_file (suffix = ".gif" ) as out_file :
408441 gifski_cmd = self ._gifski_cmd (out_file , fps ) + ["--repeat" , repeat , "-" ]
409442 self ._run_pipeline (ffmpeg_cmd , gifski_cmd )
410- if self .context . config . GIFSKI_GIFSICLE_PASS :
443+ if self ._gifski_gifsicle_pass () :
411444 return self ._gifsicle_optimize_file (out_file )
412445 with open (out_file , mode = "rb" ) as f :
413446 return f .read ()
@@ -441,7 +474,7 @@ def _gifski_png_frames(self, src_file, fps, repeat):
441474 + frame_files
442475 )
443476 self .run_cmd (gifski_cmd )
444- if self .context . config . GIFSKI_GIFSICLE_PASS :
477+ if self ._gifski_gifsicle_pass () :
445478 return self ._gifsicle_optimize_file (out_file )
446479 with open (out_file , mode = "rb" ) as f :
447480 return f .read ()
0 commit comments