Feature: AVIF support (#6288)

This commit is contained in:
Gykes
2025-11-27 14:19:32 -06:00
committed by GitHub
parent 4ef3a605dd
commit 90d1b2df2d
6 changed files with 43 additions and 11 deletions

View File

@@ -4,6 +4,8 @@ import (
"context"
"fmt"
"image"
"path/filepath"
"strings"
_ "image/gif"
_ "image/jpeg"
@@ -28,6 +30,11 @@ func (d *Decorator) Decorate(ctx context.Context, fs models.FS, f models.File) (
// ignore clips in non-OsFS filesystems as ffprobe cannot read them
// TODO - copy to temp file if not an OsFS
if _, isOs := fs.(*file.OsFS); !isOs {
// AVIF images inside zip files are not supported
if strings.ToLower(filepath.Ext(base.Path)) == ".avif" {
logger.Warnf("Skipping AVIF image in zip file: %s", base.Path)
return f, nil
}
logger.Debugf("assuming ImageFile for non-OsFS file %q", base.Path)
return decorateFallback(fs, f)
}

View File

@@ -22,12 +22,8 @@ const ffmpegImageQuality = 5
var vipsPath string
var once sync.Once
var (
ErrUnsupportedImageFormat = errors.New("unsupported image format")
// ErrNotSupportedForThumbnail is returned if the image format is not supported for thumbnail generation
ErrNotSupportedForThumbnail = errors.New("unsupported image format for thumbnail")
)
// ErrNotSupportedForThumbnail is returned if the image format is not supported for thumbnail generation
var ErrNotSupportedForThumbnail = errors.New("unsupported image format for thumbnail")
type ThumbnailEncoder struct {
FFMpeg *ffmpeg.FFMpeg
@@ -83,8 +79,9 @@ func (e *ThumbnailEncoder) GetThumbnail(f models.File, maxSize int) ([]byte, err
data := buf.Bytes()
format := ""
if imageFile, ok := f.(*models.ImageFile); ok {
format := imageFile.Format
format = imageFile.Format
animated := imageFile.Format == formatGif
// #2266 - if image is webp, then determine if it is animated
@@ -96,6 +93,15 @@ func (e *ThumbnailEncoder) GetThumbnail(f models.File, maxSize int) ([]byte, err
if animated {
return nil, fmt.Errorf("%w: %s", ErrNotSupportedForThumbnail, format)
}
// AVIF cannot be read from stdin, must use file path
// AVIF in zip files is not supported
if format == "avif" {
if f.Base().ZipFileID != nil {
return nil, fmt.Errorf("%w: AVIF in zip file", ErrNotSupportedForThumbnail)
}
return e.ffmpegImageThumbnailPath(f.Base().Path, maxSize)
}
}
// Videofiles can only be thumbnailed with ffmpeg
@@ -130,16 +136,32 @@ func (e *ThumbnailEncoder) GetPreview(inPath string, outPath string, maxSize int
}
func (e *ThumbnailEncoder) ffmpegImageThumbnail(image *bytes.Buffer, maxSize int) ([]byte, error) {
args := transcoder.ImageThumbnail("-", transcoder.ImageThumbnailOptions{
options := transcoder.ImageThumbnailOptions{
OutputFormat: ffmpeg.ImageFormatJpeg,
OutputPath: "-",
MaxDimensions: maxSize,
Quality: ffmpegImageQuality,
})
}
args := transcoder.ImageThumbnail("-", options)
return e.FFMpeg.GenerateOutput(context.TODO(), args, image)
}
// ffmpegImageThumbnailPath generates a thumbnail from a file path (used for AVIF which can't be piped)
func (e *ThumbnailEncoder) ffmpegImageThumbnailPath(inputPath string, maxSize int) ([]byte, error) {
options := transcoder.ImageThumbnailOptions{
OutputFormat: ffmpeg.ImageFormatJpeg,
OutputPath: "-",
MaxDimensions: maxSize,
Quality: ffmpegImageQuality,
}
args := transcoder.ImageThumbnail(inputPath, options)
return e.FFMpeg.GenerateOutput(context.TODO(), args, nil)
}
func (e *ThumbnailEncoder) getClipPreview(inPath string, outPath string, maxSize int, clipDuration float64, frameRate float64) error {
var thumbFilter ffmpeg.VideoFilter
thumbFilter = thumbFilter.ScaleMaxSize(maxSize)