test the media manager a bit, add shutdown logic

This commit is contained in:
tsmethurst 2022-01-10 18:36:09 +01:00
commit e0f9323b9a
37 changed files with 688 additions and 354 deletions

View file

@ -20,16 +20,22 @@ package media
import (
"bytes"
"context"
"errors"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"strings"
"time"
"github.com/buckket/go-blurhash"
"github.com/nfnt/resize"
"github.com/superseriousbusiness/exifremove/pkg/exifremove"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/uris"
)
const (
@ -38,13 +44,119 @@ const (
)
type ImageMeta struct {
image []byte
contentType string
width int
height int
size int
aspect float64
blurhash string
image []byte
width int
height int
size int
aspect float64
blurhash string
}
func (m *manager) preProcessImage(ctx context.Context, data []byte, contentType string, accountID string, ai *AdditionalInfo) (*Processing, error) {
if !supportedImage(contentType) {
return nil, fmt.Errorf("image type %s not supported", contentType)
}
if len(data) == 0 {
return nil, errors.New("image was of size 0")
}
id, err := id.NewRandomULID()
if err != nil {
return nil, err
}
extension := strings.Split(contentType, "/")[1]
// populate initial fields on the media attachment -- some of these will be overwritten as we proceed
attachment := &gtsmodel.MediaAttachment{
ID: id,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
StatusID: "",
URL: uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeOriginal), id, extension),
RemoteURL: "",
Type: gtsmodel.FileTypeImage,
FileMeta: gtsmodel.FileMeta{
Focus: gtsmodel.Focus{
X: 0,
Y: 0,
},
},
AccountID: accountID,
Description: "",
ScheduledStatusID: "",
Blurhash: "",
Processing: gtsmodel.ProcessingStatusReceived,
File: gtsmodel.File{
Path: fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeOriginal, id, extension),
ContentType: contentType,
UpdatedAt: time.Now(),
},
Thumbnail: gtsmodel.Thumbnail{
URL: uris.GenerateURIForAttachment(accountID, string(TypeAttachment), string(SizeSmall), id, mimeJpeg), // all thumbnails are encoded as jpeg,
Path: fmt.Sprintf("%s/%s/%s/%s.%s", accountID, TypeAttachment, SizeSmall, id, mimeJpeg), // all thumbnails are encoded as jpeg,
ContentType: mimeJpeg,
UpdatedAt: time.Now(),
},
Avatar: false,
Header: false,
}
// check if we have additional info to add to the attachment,
// and overwrite some of the attachment fields if so
if ai != nil {
if ai.CreatedAt != nil {
attachment.CreatedAt = *ai.CreatedAt
}
if ai.StatusID != nil {
attachment.StatusID = *ai.StatusID
}
if ai.RemoteURL != nil {
attachment.RemoteURL = *ai.RemoteURL
}
if ai.Description != nil {
attachment.Description = *ai.Description
}
if ai.ScheduledStatusID != nil {
attachment.ScheduledStatusID = *ai.ScheduledStatusID
}
if ai.Blurhash != nil {
attachment.Blurhash = *ai.Blurhash
}
if ai.Avatar != nil {
attachment.Avatar = *ai.Avatar
}
if ai.Header != nil {
attachment.Header = *ai.Header
}
if ai.FocusX != nil {
attachment.FileMeta.Focus.X = *ai.FocusX
}
if ai.FocusY != nil {
attachment.FileMeta.Focus.Y = *ai.FocusY
}
}
media := &Processing{
attachment: attachment,
rawData: data,
thumbstate: received,
fullSizeState: received,
database: m.db,
storage: m.storage,
}
return media, nil
}
func decodeGif(b []byte) (*ImageMeta, error) {
@ -106,8 +218,12 @@ func decodeImage(b []byte, contentType string) (*ImageMeta, error) {
// deriveThumbnail returns a byte slice and metadata for a thumbnail
// of a given jpeg, png, or gif, or an error if something goes wrong.
//
// Note that the aspect ratio of the image will be retained,
// so it will not necessarily be a square, even if x and y are set as the same value.
// If createBlurhash is true, then a blurhash will also be generated from a tiny
// version of the image. This costs precious CPU cycles, so only use it if you
// really need a blurhash and don't have one already.
//
// If createBlurhash is false, then the blurhash field on the returned ImageAndMeta
// will be an empty string.
func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageMeta, error) {
var i image.Image
var err error
@ -115,21 +231,20 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM
switch contentType {
case mimeImageJpeg:
i, err = jpeg.Decode(bytes.NewReader(b))
if err != nil {
return nil, err
}
case mimeImagePng:
i, err = png.Decode(bytes.NewReader(b))
if err != nil {
return nil, err
}
case mimeImageGif:
i, err = gif.Decode(bytes.NewReader(b))
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("content type %s not recognised", contentType)
err = fmt.Errorf("content type %s can't be thumbnailed", contentType)
}
if err != nil {
return nil, err
}
if i == nil {
return nil, errors.New("processed image was nil")
}
thumb := resize.Thumbnail(thumbnailMaxWidth, thumbnailMaxHeight, i, resize.NearestNeighbor)
@ -146,6 +261,8 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM
}
if createBlurhash {
// for generating blurhashes, it's more cost effective to lose detail rather than
// pass a big image into the blurhash algorithm, so make a teeny tiny version
tiny := resize.Thumbnail(32, 32, thumb, resize.NearestNeighbor)
bh, err := blurhash.Encode(4, 3, tiny)
if err != nil {
@ -156,6 +273,7 @@ func deriveThumbnail(b []byte, contentType string, createBlurhash bool) (*ImageM
out := &bytes.Buffer{}
if err := jpeg.Encode(out, thumb, &jpeg.Options{
// Quality isn't extremely important for thumbnails, so 75 is "good enough"
Quality: 75,
}); err != nil {
return nil, err