mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 22:29:07 +00:00
Batch exiftool runs + scanner package restructuring
This commit is contained in:
176
api/scanner/media_encoding/encode_photo.go
Normal file
176
api/scanner/media_encoding/encode_photo.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package media_encoding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/photoview/photoview/api/utils"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/vansante/go-ffprobe.v2"
|
||||
|
||||
_ "github.com/strukturag/libheif/go/heif"
|
||||
)
|
||||
|
||||
func EncodeThumbnail(inputPath string, outputPath string) (*media_utils.PhotoDimensions, error) {
|
||||
|
||||
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, imaging.NearestNeighbor)
|
||||
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
|
||||
_photoImage image.Image
|
||||
_contentType *media_type.MediaType
|
||||
_videoMetadata *ffprobe.ProbeData
|
||||
}
|
||||
|
||||
// ContentType reads the image to determine its content type
|
||||
func (img *EncodeMediaData) ContentType() (*media_type.MediaType, error) {
|
||||
if img._contentType != nil {
|
||||
return img._contentType, nil
|
||||
}
|
||||
|
||||
imgType, err := media_type.GetMediaType(img.Media.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 darktable if there is no counterpart JPEG file to use instead
|
||||
if contentType.IsRaw() && img.Media.CounterpartPath == nil {
|
||||
if executable_worker.DarktableCli.IsInstalled() {
|
||||
err := executable_worker.DarktableCli.EncodeJpeg(img.Media.Path, outputPath, 70)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("could not convert photo as no RAW converter was found")
|
||||
}
|
||||
} else {
|
||||
image, err := img.photoImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encodeImageJPEG(image, outputPath, 70)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// photoImage reads and decodes the image file and saves it in a cache so the photo in only decoded once
|
||||
func (img *EncodeMediaData) photoImage() (image.Image, error) {
|
||||
if img._photoImage != nil {
|
||||
return img._photoImage, nil
|
||||
}
|
||||
|
||||
var photoPath string
|
||||
if img.Media.CounterpartPath != nil {
|
||||
photoPath = *img.Media.CounterpartPath
|
||||
} else {
|
||||
photoPath = img.Media.Path
|
||||
}
|
||||
|
||||
photoImg, err := img.decodeImage(photoPath)
|
||||
if err != nil {
|
||||
return nil, utils.HandleError("image decoding", err)
|
||||
}
|
||||
|
||||
img._photoImage = photoImg
|
||||
return img._photoImage, nil
|
||||
}
|
||||
|
||||
func (img *EncodeMediaData) decodeImage(imagePath string) (image.Image, error) {
|
||||
file, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to open file to decode image (%s)", imagePath)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
mediaType, err := img.ContentType()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get media content type needed to decode it (%s)", imagePath)
|
||||
}
|
||||
|
||||
var decodedImage image.Image
|
||||
|
||||
if *mediaType == media_type.TypeHeic {
|
||||
decodedImage, _, err = image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode HEIF image (%s)", imagePath)
|
||||
}
|
||||
} else {
|
||||
decodedImage, err = imaging.Decode(file, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode image (%s)", imagePath)
|
||||
}
|
||||
}
|
||||
|
||||
return decodedImage, 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
|
||||
}
|
||||
Reference in New Issue
Block a user