summaryrefslogtreecommitdiff
path: root/internal/media/image.go
diff options
context:
space:
mode:
authorLibravatar tsmethurst <tobi.smethurst@protonmail.com>2022-01-10 18:36:09 +0100
committerLibravatar tsmethurst <tobi.smethurst@protonmail.com>2022-01-10 18:36:09 +0100
commite0f9323b9aa98b55f3557086f7b0a17047943f39 (patch)
tree5a2bbcb84b87d2530e804067d72c1bea672412a1 /internal/media/image.go
parentadd async test (diff)
downloadgotosocial-e0f9323b9aa98b55f3557086f7b0a17047943f39.tar.xz
test the media manager a bit, add shutdown logic
Diffstat (limited to 'internal/media/image.go')
-rw-r--r--internal/media/image.go156
1 files changed, 137 insertions, 19 deletions
diff --git a/internal/media/image.go b/internal/media/image.go
index 4c0b28c02..074dd3839 100644
--- a/internal/media/image.go
+++ b/internal/media/image.go
@@ -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