Files
photoview/api/scanner/media_encoding/encode_photo.go
Googol Lee f88fcaba69 Use libmagic to identify files type. (#1124)
* Use libmagic to identify file types.

* Fix mime array for database querying.

* go mod tidy

* Fix typo

* Another fix for the scheduled image build workflow for tag case (#1137)

* Try to fix the incorrect tag processing

* more debug code

* try with context=git

* hopefully, final debug

* the final commit with no debug code

---------

Co-authored-by: Konstantin Koval

* Add benchmark.

* rollback workflows.

* Fix AI comments.

* Build image.

* Fix AI comments.

* Fix AI comments.

* Rollback build workflow.

---------

Co-authored-by: Kostiantyn <32730812+kkovaletp@users.noreply.github.com>
2024-12-14 22:07:03 +01:00

146 lines
4.0 KiB
Go

package media_encoding
import (
"context"
"fmt"
"image"
"image/jpeg"
"os"
"time"
"github.com/disintegration/imaging"
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/scanner/media_encoding/executable_worker"
"github.com/photoview/photoview/api/scanner/media_encoding/media_utils"
"github.com/photoview/photoview/api/scanner/media_type"
"github.com/pkg/errors"
"gopkg.in/vansante/go-ffprobe.v2"
_ "github.com/strukturag/libheif/go/heif"
"gorm.io/gorm"
)
var thumbFilter = map[models.ThumbnailFilter]imaging.ResampleFilter{
models.ThumbnailFilterNearestNeighbor: imaging.NearestNeighbor,
models.ThumbnailFilterBox: imaging.Box,
models.ThumbnailFilterLinear: imaging.Linear,
models.ThumbnailFilterMitchellNetravali: imaging.MitchellNetravali,
models.ThumbnailFilterCatmullRom: imaging.CatmullRom,
models.ThumbnailFilterLanczos: imaging.Lanczos,
}
func EncodeThumbnail(db *gorm.DB, inputPath string, outputPath string) (*media_utils.PhotoDimensions, error) {
var siteInfo models.SiteInfo
if err := db.First(&siteInfo).Error; err != nil {
return nil, err
}
inputImage, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
if err != nil {
return nil, err
}
dimensions := media_utils.PhotoDimensionsFromRect(inputImage.Bounds())
dimensions = dimensions.ThumbnailScale()
thumbImage := imaging.Resize(inputImage, dimensions.Width, dimensions.Height, thumbFilter[siteInfo.ThumbnailMethod])
if err = encodeImageJPEG(thumbImage, outputPath, 60); err != nil {
return nil, err
}
return &dimensions, nil
}
func encodeImageJPEG(image image.Image, outputPath string, jpegQuality int) error {
photo_file, err := os.Create(outputPath)
if err != nil {
return errors.Wrapf(err, "could not create file: %s", outputPath)
}
defer photo_file.Close()
err = jpeg.Encode(photo_file, image, &jpeg.Options{Quality: jpegQuality})
if err != nil {
return err
}
return nil
}
// EncodeMediaData is used to easily decode media data, with a cache so expensive operations are not repeated
type EncodeMediaData struct {
Media *models.Media
CounterpartPath *string
_photoImage image.Image
_contentType media_type.MediaType
_videoMetadata *ffprobe.ProbeData
}
func NewEncodeMediaData(media *models.Media) EncodeMediaData {
fileType := media_type.GetMediaType(media.Path)
return EncodeMediaData{
Media: media,
_contentType: fileType,
}
}
// ContentType reads the image to determine its content type
func (img *EncodeMediaData) ContentType() (media_type.MediaType, error) {
if img._contentType != media_type.TypeUnknown {
return img._contentType, nil
}
imgType := media_type.GetMediaType(img.Media.Path)
if imgType == media_type.TypeUnknown {
return imgType, fmt.Errorf("unknown type of %q", img.Media.Path)
}
img._contentType = imgType
return imgType, nil
}
func (img *EncodeMediaData) EncodeHighRes(outputPath string) error {
contentType, err := img.ContentType()
if err != nil {
return err
}
if !contentType.IsSupported() {
return errors.New("could not convert photo as file format is not supported")
}
// Use magick if there is no counterpart JPEG file to use instead
if contentType.IsImage() && !contentType.IsWebCompatible() {
imgPath := img.Media.Path
if img.CounterpartPath != nil {
imgPath = *img.CounterpartPath
}
err := executable_worker.Magick.EncodeJpeg(imgPath, outputPath, 70)
if err != nil {
return fmt.Errorf("failed to convert RAW photo %q to JPEG: %w", imgPath, err)
}
}
return nil
}
func (enc *EncodeMediaData) VideoMetadata() (*ffprobe.ProbeData, error) {
if enc._videoMetadata != nil {
return enc._videoMetadata, nil
}
ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelFn()
data, err := ffprobe.ProbeURL(ctx, enc.Media.Path)
if err != nil {
return nil, errors.Wrapf(err, "could not read video metadata (%s)", enc.Media.Title)
}
enc._videoMetadata = data
return enc._videoMetadata, nil
}