summaryrefslogtreecommitdiff
path: root/internal/media
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-05-17 19:06:58 +0200
committerLibravatar GitHub <noreply@github.com>2021-05-17 19:06:58 +0200
commit6cd033449fd328410128bc3e840f4b8d3d74f052 (patch)
tree9868db8439533e078dea5bb34ae4949e9460f2cb /internal/media
parentupdate progress (diff)
downloadgotosocial-6cd033449fd328410128bc3e840f4b8d3d74f052.tar.xz
Refine statuses (#26)
Remote media is now dereferenced and attached properly to incoming federated statuses. Mentions are now dereferenced and attached properly to incoming federated statuses. Small fixes to status visibility. Allow URL params for filtering statuses: // ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account. // PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account. // MaxIDKey is for specifying the maximum ID of the status to retrieve. // MediaOnlyKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account. Add endpoint for fetching an account's statuses.
Diffstat (limited to 'internal/media')
-rw-r--r--internal/media/handler.go (renamed from internal/media/media.go)264
-rw-r--r--internal/media/handler_test.go (renamed from internal/media/media_test.go)0
-rw-r--r--internal/media/mock_MediaHandler.go59
-rw-r--r--internal/media/processicon.go141
-rw-r--r--internal/media/processimage.go128
-rw-r--r--internal/media/processvideo.go23
-rw-r--r--internal/media/test/test-jpeg-processed.jpgbin300156 -> 771517 bytes
-rw-r--r--internal/media/test/test-jpeg-thumbnail.jpgbin6790 -> 29611 bytes
-rw-r--r--internal/media/util.go8
9 files changed, 335 insertions, 288 deletions
diff --git a/internal/media/media.go b/internal/media/handler.go
index 84f4ef554..8bbff9c46 100644
--- a/internal/media/media.go
+++ b/internal/media/handler.go
@@ -19,8 +19,10 @@
package media
import (
+ "context"
"errors"
"fmt"
+ "net/url"
"strings"
"time"
@@ -30,6 +32,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/storage"
+ "github.com/superseriousbusiness/gotosocial/internal/transport"
)
// Size describes the *size* of a piece of media
@@ -68,13 +71,21 @@ type Handler interface {
// ProcessLocalAttachment takes a new attachment and the requesting account, checks it out, removes exif data from it,
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media,
- // and then returns information to the caller about the attachment.
- ProcessLocalAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error)
+ // and then returns information to the caller about the attachment. It's the caller's responsibility to put the returned struct
+ // in the database.
+ ProcessAttachment(attachment []byte, accountID string, remoteURL string) (*gtsmodel.MediaAttachment, error)
// ProcessLocalEmoji takes a new emoji and a shortcode, cleans it up, puts it in storage, and creates a new
// *gts.Emoji for it, then returns it to the caller. It's the caller's responsibility to put the returned struct
// in the database.
ProcessLocalEmoji(emojiBytes []byte, shortcode string) (*gtsmodel.Emoji, error)
+
+ // ProcessRemoteAttachment takes a transport, a bare-bones current attachment, and an accountID that the attachment belongs to.
+ // It then dereferences the attachment (ie., fetches the attachment bytes from the remote server), ensuring that the bytes are
+ // the correct content type. It stores the attachment in whatever storage backend the Handler has been initalized with, and returns
+ // information to the caller about the new attachment. It's the caller's responsibility to put the returned struct
+ // in the database.
+ ProcessRemoteAttachment(t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error)
}
type mediaHandler struct {
@@ -136,27 +147,24 @@ func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID strin
return ma, nil
}
-// ProcessLocalAttachment takes a new attachment and the requesting account, checks it out, removes exif data from it,
+// ProcessAttachment takes a new attachment and the owning account, checks it out, removes exif data from it,
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media,
// and then returns information to the caller about the attachment.
-func (mh *mediaHandler) ProcessLocalAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error) {
+func (mh *mediaHandler) ProcessAttachment(attachment []byte, accountID string, remoteURL string) (*gtsmodel.MediaAttachment, error) {
contentType, err := parseContentType(attachment)
if err != nil {
return nil, err
}
mainType := strings.Split(contentType, "/")[0]
switch mainType {
- case MIMEVideo:
- if !SupportedVideoType(contentType) {
- return nil, fmt.Errorf("video type %s not supported", contentType)
- }
- if len(attachment) == 0 {
- return nil, errors.New("video was of size 0")
- }
- if len(attachment) > mh.config.MediaConfig.MaxVideoSize {
- return nil, fmt.Errorf("video size %d bytes exceeded max video size of %d bytes", len(attachment), mh.config.MediaConfig.MaxVideoSize)
- }
- return mh.processVideoAttachment(attachment, accountID, contentType)
+ // case MIMEVideo:
+ // if !SupportedVideoType(contentType) {
+ // return nil, fmt.Errorf("video type %s not supported", contentType)
+ // }
+ // if len(attachment) == 0 {
+ // return nil, errors.New("video was of size 0")
+ // }
+ // return mh.processVideoAttachment(attachment, accountID, contentType, remoteURL)
case MIMEImage:
if !SupportedImageType(contentType) {
return nil, fmt.Errorf("image type %s not supported", contentType)
@@ -164,10 +172,7 @@ func (mh *mediaHandler) ProcessLocalAttachment(attachment []byte, accountID stri
if len(attachment) == 0 {
return nil, errors.New("image was of size 0")
}
- if len(attachment) > mh.config.MediaConfig.MaxImageSize {
- return nil, fmt.Errorf("image size %d bytes exceeded max image size of %d bytes", len(attachment), mh.config.MediaConfig.MaxImageSize)
- }
- return mh.processImageAttachment(attachment, accountID, contentType)
+ return mh.processImageAttachment(attachment, accountID, contentType, remoteURL)
default:
break
}
@@ -287,221 +292,26 @@ func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) (
return e, nil
}
-/*
- HELPER FUNCTIONS
-*/
-
-func (mh *mediaHandler) processVideoAttachment(data []byte, accountID string, contentType string) (*gtsmodel.MediaAttachment, error) {
- return nil, nil
-}
-
-func (mh *mediaHandler) processImageAttachment(data []byte, accountID string, contentType string) (*gtsmodel.MediaAttachment, error) {
- var clean []byte
- var err error
- var original *imageAndMeta
- var small *imageAndMeta
-
- switch contentType {
- case MIMEJpeg, MIMEPng:
- if clean, err = purgeExif(data); err != nil {
- return nil, fmt.Errorf("error cleaning exif data: %s", err)
- }
- original, err = deriveImage(clean, contentType)
- if err != nil {
- return nil, fmt.Errorf("error parsing image: %s", err)
- }
- case MIMEGif:
- clean = data
- original, err = deriveGif(clean, contentType)
- if err != nil {
- return nil, fmt.Errorf("error parsing gif: %s", err)
- }
- default:
- return nil, errors.New("media type unrecognized")
+func (mh *mediaHandler) ProcessRemoteAttachment(t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error) {
+ if currentAttachment.RemoteURL == "" {
+ return nil, errors.New("no remote URL on media attachment to dereference")
}
-
- small, err = deriveThumbnail(clean, contentType, 256, 256)
+ remoteIRI, err := url.Parse(currentAttachment.RemoteURL)
if err != nil {
- return nil, fmt.Errorf("error deriving thumbnail: %s", err)
- }
-
- // now put it in storage, take a new uuid for the name of the file so we don't store any unnecessary info about it
- extension := strings.Split(contentType, "/")[1]
- newMediaID := uuid.NewString()
-
- URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
- originalURL := fmt.Sprintf("%s/%s/attachment/original/%s.%s", URLbase, accountID, newMediaID, extension)
- smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.jpeg", URLbase, accountID, newMediaID) // all thumbnails/smalls are encoded as jpeg
-
- // we store the original...
- originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, Attachment, Original, newMediaID, extension)
- if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
- return nil, fmt.Errorf("storage error: %s", err)
- }
-
- // and a thumbnail...
- smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.jpeg", mh.config.StorageConfig.BasePath, accountID, Attachment, Small, newMediaID) // all thumbnails/smalls are encoded as jpeg
- if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
- return nil, fmt.Errorf("storage error: %s", err)
- }
-
- ma := &gtsmodel.MediaAttachment{
- ID: newMediaID,
- StatusID: "",
- URL: originalURL,
- RemoteURL: "",
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- Type: gtsmodel.FileTypeImage,
- FileMeta: gtsmodel.FileMeta{
- Original: gtsmodel.Original{
- Width: original.width,
- Height: original.height,
- Size: original.size,
- Aspect: original.aspect,
- },
- Small: gtsmodel.Small{
- Width: small.width,
- Height: small.height,
- Size: small.size,
- Aspect: small.aspect,
- },
- },
- AccountID: accountID,
- Description: "",
- ScheduledStatusID: "",
- Blurhash: original.blurhash,
- Processing: 2,
- File: gtsmodel.File{
- Path: originalPath,
- ContentType: contentType,
- FileSize: len(original.image),
- UpdatedAt: time.Now(),
- },
- Thumbnail: gtsmodel.Thumbnail{
- Path: smallPath,
- ContentType: MIMEJpeg, // all thumbnails/smalls are encoded as jpeg
- FileSize: len(small.image),
- UpdatedAt: time.Now(),
- URL: smallURL,
- RemoteURL: "",
- },
- Avatar: false,
- Header: false,
+ return nil, fmt.Errorf("error parsing attachment url %s: %s", currentAttachment.RemoteURL, err)
}
- return ma, nil
-
-}
-
-func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType Type, accountID string) (*gtsmodel.MediaAttachment, error) {
- var isHeader bool
- var isAvatar bool
-
- switch mediaType {
- case Header:
- isHeader = true
- case Avatar:
- isAvatar = true
- default:
- return nil, errors.New("header or avatar not selected")
- }
-
- var clean []byte
- var err error
-
- var original *imageAndMeta
- switch contentType {
- case MIMEJpeg:
- if clean, err = purgeExif(imageBytes); err != nil {
- return nil, fmt.Errorf("error cleaning exif data: %s", err)
- }
- original, err = deriveImage(clean, contentType)
- case MIMEPng:
- if clean, err = purgeExif(imageBytes); err != nil {
- return nil, fmt.Errorf("error cleaning exif data: %s", err)
- }
- original, err = deriveImage(clean, contentType)
- case MIMEGif:
- clean = imageBytes
- original, err = deriveGif(clean, contentType)
- default:
- return nil, errors.New("media type unrecognized")
- }
-
- if err != nil {
- return nil, fmt.Errorf("error parsing image: %s", err)
+ // for content type, we assume we don't know what to expect...
+ expectedContentType := "*/*"
+ if currentAttachment.File.ContentType != "" {
+ // ... and then narrow it down if we do
+ expectedContentType = currentAttachment.File.ContentType
}
- small, err := deriveThumbnail(clean, contentType, 256, 256)
+ attachmentBytes, err := t.DereferenceMedia(context.Background(), remoteIRI, expectedContentType)
if err != nil {
- return nil, fmt.Errorf("error deriving thumbnail: %s", err)
+ return nil, fmt.Errorf("dereferencing remote media with url %s: %s", remoteIRI.String(), err)
}
- // now put it in storage, take a new uuid for the name of the file so we don't store any unnecessary info about it
- extension := strings.Split(contentType, "/")[1]
- newMediaID := uuid.NewString()
-
- URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
- originalURL := fmt.Sprintf("%s/%s/%s/original/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
- smallURL := fmt.Sprintf("%s/%s/%s/small/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
-
- // we store the original...
- originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Original, newMediaID, extension)
- if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
- return nil, fmt.Errorf("storage error: %s", err)
- }
-
- // and a thumbnail...
- smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Small, newMediaID, extension)
- if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
- return nil, fmt.Errorf("storage error: %s", err)
- }
-
- ma := &gtsmodel.MediaAttachment{
- ID: newMediaID,
- StatusID: "",
- URL: originalURL,
- RemoteURL: "",
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- Type: gtsmodel.FileTypeImage,
- FileMeta: gtsmodel.FileMeta{
- Original: gtsmodel.Original{
- Width: original.width,
- Height: original.height,
- Size: original.size,
- Aspect: original.aspect,
- },
- Small: gtsmodel.Small{
- Width: small.width,
- Height: small.height,
- Size: small.size,
- Aspect: small.aspect,
- },
- },
- AccountID: accountID,
- Description: "",
- ScheduledStatusID: "",
- Blurhash: original.blurhash,
- Processing: 2,
- File: gtsmodel.File{
- Path: originalPath,
- ContentType: contentType,
- FileSize: len(original.image),
- UpdatedAt: time.Now(),
- },
- Thumbnail: gtsmodel.Thumbnail{
- Path: smallPath,
- ContentType: contentType,
- FileSize: len(small.image),
- UpdatedAt: time.Now(),
- URL: smallURL,
- RemoteURL: "",
- },
- Avatar: isAvatar,
- Header: isHeader,
- }
-
- return ma, nil
+ return mh.ProcessAttachment(attachmentBytes, accountID, currentAttachment.RemoteURL)
}
diff --git a/internal/media/media_test.go b/internal/media/handler_test.go
index 03dcdc21d..03dcdc21d 100644
--- a/internal/media/media_test.go
+++ b/internal/media/handler_test.go
diff --git a/internal/media/mock_MediaHandler.go b/internal/media/mock_MediaHandler.go
deleted file mode 100644
index 10fffbba4..000000000
--- a/internal/media/mock_MediaHandler.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Code generated by mockery v2.7.4. DO NOT EDIT.
-
-package media
-
-import (
- mock "github.com/stretchr/testify/mock"
- "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
-)
-
-// MockMediaHandler is an autogenerated mock type for the MediaHandler type
-type MockMediaHandler struct {
- mock.Mock
-}
-
-// ProcessAttachment provides a mock function with given fields: img, accountID
-func (_m *MockMediaHandler) ProcessAttachment(img []byte, accountID string) (*gtsmodel.MediaAttachment, error) {
- ret := _m.Called(img, accountID)
-
- var r0 *gtsmodel.MediaAttachment
- if rf, ok := ret.Get(0).(func([]byte, string) *gtsmodel.MediaAttachment); ok {
- r0 = rf(img, accountID)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*gtsmodel.MediaAttachment)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func([]byte, string) error); ok {
- r1 = rf(img, accountID)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// SetHeaderOrAvatarForAccountID provides a mock function with given fields: img, accountID, headerOrAvi
-func (_m *MockMediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
- ret := _m.Called(img, accountID, headerOrAvi)
-
- var r0 *gtsmodel.MediaAttachment
- if rf, ok := ret.Get(0).(func([]byte, string, string) *gtsmodel.MediaAttachment); ok {
- r0 = rf(img, accountID, headerOrAvi)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*gtsmodel.MediaAttachment)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func([]byte, string, string) error); ok {
- r1 = rf(img, accountID, headerOrAvi)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
diff --git a/internal/media/processicon.go b/internal/media/processicon.go
new file mode 100644
index 000000000..962d1c6d8
--- /dev/null
+++ b/internal/media/processicon.go
@@ -0,0 +1,141 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package media
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType Type, accountID string) (*gtsmodel.MediaAttachment, error) {
+ var isHeader bool
+ var isAvatar bool
+
+ switch mediaType {
+ case Header:
+ isHeader = true
+ case Avatar:
+ isAvatar = true
+ default:
+ return nil, errors.New("header or avatar not selected")
+ }
+
+ var clean []byte
+ var err error
+
+ var original *imageAndMeta
+ switch contentType {
+ case MIMEJpeg:
+ if clean, err = purgeExif(imageBytes); err != nil {
+ return nil, fmt.Errorf("error cleaning exif data: %s", err)
+ }
+ original, err = deriveImage(clean, contentType)
+ case MIMEPng:
+ if clean, err = purgeExif(imageBytes); err != nil {
+ return nil, fmt.Errorf("error cleaning exif data: %s", err)
+ }
+ original, err = deriveImage(clean, contentType)
+ case MIMEGif:
+ clean = imageBytes
+ original, err = deriveGif(clean, contentType)
+ default:
+ return nil, errors.New("media type unrecognized")
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("error parsing image: %s", err)
+ }
+
+ small, err := deriveThumbnail(clean, contentType, 256, 256)
+ if err != nil {
+ return nil, fmt.Errorf("error deriving thumbnail: %s", err)
+ }
+
+ // now put it in storage, take a new uuid for the name of the file so we don't store any unnecessary info about it
+ extension := strings.Split(contentType, "/")[1]
+ newMediaID := uuid.NewString()
+
+ URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
+ originalURL := fmt.Sprintf("%s/%s/%s/original/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
+ smallURL := fmt.Sprintf("%s/%s/%s/small/%s.%s", URLbase, accountID, mediaType, newMediaID, extension)
+
+ // we store the original...
+ originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Original, newMediaID, extension)
+ if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
+ return nil, fmt.Errorf("storage error: %s", err)
+ }
+
+ // and a thumbnail...
+ smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, mediaType, Small, newMediaID, extension)
+ if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
+ return nil, fmt.Errorf("storage error: %s", err)
+ }
+
+ ma := &gtsmodel.MediaAttachment{
+ ID: newMediaID,
+ StatusID: "",
+ URL: originalURL,
+ RemoteURL: "",
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ Type: gtsmodel.FileTypeImage,
+ FileMeta: gtsmodel.FileMeta{
+ Original: gtsmodel.Original{
+ Width: original.width,
+ Height: original.height,
+ Size: original.size,
+ Aspect: original.aspect,
+ },
+ Small: gtsmodel.Small{
+ Width: small.width,
+ Height: small.height,
+ Size: small.size,
+ Aspect: small.aspect,
+ },
+ },
+ AccountID: accountID,
+ Description: "",
+ ScheduledStatusID: "",
+ Blurhash: original.blurhash,
+ Processing: 2,
+ File: gtsmodel.File{
+ Path: originalPath,
+ ContentType: contentType,
+ FileSize: len(original.image),
+ UpdatedAt: time.Now(),
+ },
+ Thumbnail: gtsmodel.Thumbnail{
+ Path: smallPath,
+ ContentType: contentType,
+ FileSize: len(small.image),
+ UpdatedAt: time.Now(),
+ URL: smallURL,
+ RemoteURL: "",
+ },
+ Avatar: isAvatar,
+ Header: isHeader,
+ }
+
+ return ma, nil
+}
diff --git a/internal/media/processimage.go b/internal/media/processimage.go
new file mode 100644
index 000000000..dd8bff02c
--- /dev/null
+++ b/internal/media/processimage.go
@@ -0,0 +1,128 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package media
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+func (mh *mediaHandler) processImageAttachment(data []byte, accountID string, contentType string, remoteURL string) (*gtsmodel.MediaAttachment, error) {
+ var clean []byte
+ var err error
+ var original *imageAndMeta
+ var small *imageAndMeta
+
+ switch contentType {
+ case MIMEJpeg, MIMEPng:
+ if clean, err = purgeExif(data); err != nil {
+ return nil, fmt.Errorf("error cleaning exif data: %s", err)
+ }
+ original, err = deriveImage(clean, contentType)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing image: %s", err)
+ }
+ case MIMEGif:
+ clean = data
+ original, err = deriveGif(clean, contentType)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing gif: %s", err)
+ }
+ default:
+ return nil, errors.New("media type unrecognized")
+ }
+
+ small, err = deriveThumbnail(clean, contentType, 256, 256)
+ if err != nil {
+ return nil, fmt.Errorf("error deriving thumbnail: %s", err)
+ }
+
+ // now put it in storage, take a new uuid for the name of the file so we don't store any unnecessary info about it
+ extension := strings.Split(contentType, "/")[1]
+ newMediaID := uuid.NewString()
+
+ URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
+ originalURL := fmt.Sprintf("%s/%s/attachment/original/%s.%s", URLbase, accountID, newMediaID, extension)
+ smallURL := fmt.Sprintf("%s/%s/attachment/small/%s.jpeg", URLbase, accountID, newMediaID) // all thumbnails/smalls are encoded as jpeg
+
+ // we store the original...
+ originalPath := fmt.Sprintf("%s/%s/%s/%s/%s.%s", mh.config.StorageConfig.BasePath, accountID, Attachment, Original, newMediaID, extension)
+ if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
+ return nil, fmt.Errorf("storage error: %s", err)
+ }
+
+ // and a thumbnail...
+ smallPath := fmt.Sprintf("%s/%s/%s/%s/%s.jpeg", mh.config.StorageConfig.BasePath, accountID, Attachment, Small, newMediaID) // all thumbnails/smalls are encoded as jpeg
+ if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
+ return nil, fmt.Errorf("storage error: %s", err)
+ }
+
+ ma := &gtsmodel.MediaAttachment{
+ ID: newMediaID,
+ StatusID: "",
+ URL: originalURL,
+ RemoteURL: remoteURL,
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ Type: gtsmodel.FileTypeImage,
+ FileMeta: gtsmodel.FileMeta{
+ Original: gtsmodel.Original{
+ Width: original.width,
+ Height: original.height,
+ Size: original.size,
+ Aspect: original.aspect,
+ },
+ Small: gtsmodel.Small{
+ Width: small.width,
+ Height: small.height,
+ Size: small.size,
+ Aspect: small.aspect,
+ },
+ },
+ AccountID: accountID,
+ Description: "",
+ ScheduledStatusID: "",
+ Blurhash: original.blurhash,
+ Processing: 2,
+ File: gtsmodel.File{
+ Path: originalPath,
+ ContentType: contentType,
+ FileSize: len(original.image),
+ UpdatedAt: time.Now(),
+ },
+ Thumbnail: gtsmodel.Thumbnail{
+ Path: smallPath,
+ ContentType: MIMEJpeg, // all thumbnails/smalls are encoded as jpeg
+ FileSize: len(small.image),
+ UpdatedAt: time.Now(),
+ URL: smallURL,
+ RemoteURL: "",
+ },
+ Avatar: false,
+ Header: false,
+ }
+
+ return ma, nil
+
+}
diff --git a/internal/media/processvideo.go b/internal/media/processvideo.go
new file mode 100644
index 000000000..a2debf648
--- /dev/null
+++ b/internal/media/processvideo.go
@@ -0,0 +1,23 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package media
+
+// func (mh *mediaHandler) processVideoAttachment(data []byte, accountID string, contentType string, remoteURL string) (*gtsmodel.MediaAttachment, error) {
+// return nil, nil
+// }
diff --git a/internal/media/test/test-jpeg-processed.jpg b/internal/media/test/test-jpeg-processed.jpg
index 81dab59c7..33c75ac4a 100644
--- a/internal/media/test/test-jpeg-processed.jpg
+++ b/internal/media/test/test-jpeg-processed.jpg
Binary files differ
diff --git a/internal/media/test/test-jpeg-thumbnail.jpg b/internal/media/test/test-jpeg-thumbnail.jpg
index b419a86dd..b87b2eb79 100644
--- a/internal/media/test/test-jpeg-thumbnail.jpg
+++ b/internal/media/test/test-jpeg-thumbnail.jpg
Binary files differ
diff --git a/internal/media/util.go b/internal/media/util.go
index f4f2819af..1178649ea 100644
--- a/internal/media/util.go
+++ b/internal/media/util.go
@@ -206,7 +206,9 @@ func deriveImage(b []byte, contentType string) (*imageAndMeta, error) {
}
out := &bytes.Buffer{}
- if err := jpeg.Encode(out, i, nil); err != nil {
+ if err := jpeg.Encode(out, i, &jpeg.Options{
+ Quality: 100,
+ }); err != nil {
return nil, err
}
@@ -256,7 +258,9 @@ func deriveThumbnail(b []byte, contentType string, x uint, y uint) (*imageAndMet
aspect := float64(width) / float64(height)
out := &bytes.Buffer{}
- if err := jpeg.Encode(out, thumb, nil); err != nil {
+ if err := jpeg.Encode(out, thumb, &jpeg.Options{
+ Quality: 100,
+ }); err != nil {
return nil, err
}
return &imageAndMeta{