Changeset f70e010 in Klonkt


Ignore:
Timestamp:
07/01/2026 06:08:33 AM (2 months ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
6053c6c
Parents:
1bc0886
Message:

fix(thumbnails): skip ffmpeg for animated remote WebP/GIF (serve original)

getRemoteThumbnail ran ffmpeg on every remote image, but ffmpeg-static can't
decode an animated WebP → the downscale failed for every animated remote cover,
logging '[thumb-remote] downscale failed' and wasting a fetch+write+ffmpeg per
cache-miss. It then fell back to the original anyway. Now detect an animated
WebP/GIF from the fetched buffer (same check as the local isAnimatedSrc) and
return null up front → the route serves the ORIGINAL, keeping the animation,
without the doomed ffmpeg call or the error log.

  • src/services/ThumbnailService.js — isAnimatedBuf() guard in getRemoteThumbnail

Co-Authored-By: Claude <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ThumbnailService.js

    r1bc0886 rf70e010  
    165165 * @returns {Promise<string|null>} cached path, or null.
    166166 */
     167// Same animation check as isAnimatedSrc but on an in-memory buffer (remote fetch): ffmpeg-static
     168// can't decode an animated WebP, and flattening a GIF/animated WebP to one frame loses its motion.
     169function isAnimatedBuf(buf) {
     170  try {
     171    if (!buf || buf.length < 21) return false;
     172    if (buf.toString('ascii', 0, 3) === 'GIF') return true; // any GIF (a flattened one loses its animation)
     173    // Animated WebP: RIFF…WEBP with a VP8X chunk whose flags byte (20) has the animation bit (0x02).
     174    if (buf.toString('ascii', 0, 4) === 'RIFF' && buf.toString('ascii', 8, 12) === 'WEBP'
     175        && buf.toString('ascii', 12, 16) === 'VP8X' && (buf[20] & 0x02) !== 0) return true;
     176    return false;
     177  } catch { return false; }
     178}
    167179export async function getRemoteThumbnail(url, width) {
    168180  if (!THUMB_SIZES.has(width) || !ffmpegPath || !url) return null;
     
    185197  }
    186198  if (buf.length > 12 * 1024 * 1024) return null;
     199  // ffmpeg-static can't decode an animated WebP (the doomed downscale just logs an error), and a
     200  // flattened GIF/animated WebP loses its motion → skip it and let the route serve the ORIGINAL
     201  // (keeps the animation; mirrors the local path's isAnimatedSrc guard).
     202  if (isAnimatedBuf(buf)) return null;
    187203
    188204  await fs.promises.mkdir(path.dirname(cached), { recursive: true });
Note: See TracChangeset for help on using the changeset viewer.