diff options
author | 2024-08-02 11:46:41 +0000 | |
---|---|---|
committer | 2024-08-02 12:46:41 +0100 | |
commit | 94e87610c4ce9bbb1c614a61bab29c1422fed11b (patch) | |
tree | 2e06b8ce64212140e796f6077ba841b6cc678501 /internal | |
parent | [feature] Allow import of following and blocks via CSV (#3150) (diff) | |
download | gotosocial-94e87610c4ce9bbb1c614a61bab29c1422fed11b.tar.xz |
[chore] add back exif-terminator and use only for jpeg,png,webp (#3161)
* add back exif-terminator and use only for jpeg,png,webp
* fix arguments passed to terminateExif()
* pull in latest exif-terminator
* fix test
* update processed img
---------
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Diffstat (limited to 'internal')
-rw-r--r-- | internal/media/ffmpeg.go | 62 | ||||
-rw-r--r-- | internal/media/manager_test.go | 2 | ||||
-rw-r--r-- | internal/media/metadata.go | 96 | ||||
-rw-r--r-- | internal/media/processingmedia.go | 4 | ||||
-rw-r--r-- | internal/media/test/test-png-alphachannel-processed.png | bin | 18904 -> 18832 bytes | |||
-rw-r--r-- | internal/media/util.go | 19 |
6 files changed, 125 insertions, 58 deletions
diff --git a/internal/media/ffmpeg.go b/internal/media/ffmpeg.go index e26ec78d7..72ee1bc33 100644 --- a/internal/media/ffmpeg.go +++ b/internal/media/ffmpeg.go @@ -21,7 +21,6 @@ import ( "context" "encoding/json" "errors" - "os" "path" "strconv" "strings" @@ -36,28 +35,21 @@ import ( "github.com/tetratelabs/wazero" ) -// ffmpegClearMetadata generates a copy (in-place) of input media with all metadata cleared. -func ffmpegClearMetadata(ctx context.Context, filepath string) error { - var outpath string - +// ffmpegClearMetadata generates a copy of input media with all metadata cleared. +// NOTE: given that we are not performing an encode, this only clears global level metadata, +// any metadata encoded into the media stream itself will not be cleared. This is the best we +// can do without absolutely tanking performance by requiring transcodes :( +func ffmpegClearMetadata(ctx context.Context, outpath, inpath string) error { // Get directory from filepath. - dirpath := path.Dir(filepath) + dirpath := path.Dir(inpath) - // Generate cleaned output path MAINTAINING extension. - if i := strings.IndexByte(filepath, '.'); i != -1 { - outpath = filepath[:i] + "_cleaned" + filepath[i:] - } else { - return gtserror.New("input file missing extension") - } - - // Clear metadata with ffmpeg. - if err := ffmpeg(ctx, dirpath, + return ffmpeg(ctx, dirpath, // Only log errors. "-loglevel", "error", // Input file path. - "-i", filepath, + "-i", inpath, // Drop all metadata. "-map_metadata", "-1", @@ -71,16 +63,7 @@ func ffmpegClearMetadata(ctx context.Context, filepath string) error { // Output. outpath, - ); err != nil { - return err - } - - // Move the new output file path to original location. - if err := os.Rename(outpath, filepath); err != nil { - return gtserror.Newf("error renaming %s -> %s: %w", outpath, filepath, err) - } - - return nil + ) } // ffmpegGenerateThumb generates a thumbnail webp from input media of any type, useful for any media. @@ -390,18 +373,33 @@ func (res *result) GetFileType() (gtsmodel.FileType, string) { // ImageMeta extracts image metadata contained within ffprobe'd media result streams. func (res *result) ImageMeta() (width int, height int, framerate float32) { for _, stream := range res.video { + // Use widest found width. if stream.width > width { width = stream.width } + + // Use tallest found height. if stream.height > height { height = stream.height } + + // Use lowest non-zero (valid) framerate. if fr := float32(stream.framerate); fr > 0 { if framerate == 0 || fr < framerate { framerate = fr } } } + + // If image is rotated by + // any odd multiples of 90, + // flip width / height to + // get the correct scale. + switch res.rotation { + case -90, 90, -270, 270: + width, height = height, width + } + return } @@ -486,14 +484,6 @@ func (res *ffprobeResult) Process() (*result, error) { stream: stream{codec: s.CodecName}, }) case "video": - // Determine proper display dimensions, - // taking account of rotation data. - width, height := displayDimensions( - s.Width, - s.Height, - r.rotation, - ) - // Parse stream framerate, bearing in // mind that some static container formats // (e.g. jpeg) still return a framerate, so @@ -521,8 +511,8 @@ func (res *ffprobeResult) Process() (*result, error) { // Append video stream data to result. r.video = append(r.video, videoStream{ stream: stream{codec: s.CodecName}, - width: width, - height: height, + width: s.Width, + height: s.Height, framerate: framerate, }) } diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go index 68de74dd6..7a4c865f4 100644 --- a/internal/media/manager_test.go +++ b/internal/media/manager_test.go @@ -711,7 +711,7 @@ func (suite *ManagerTestSuite) TestPngAlphaChannelProcess() { }, attachment.FileMeta.Small) suite.Equal("image/png", attachment.File.ContentType) suite.Equal("image/webp", attachment.Thumbnail.ContentType) - suite.Equal(18904, attachment.File.FileSize) + suite.Equal(18832, attachment.File.FileSize) suite.Equal(2630, attachment.Thumbnail.FileSize) suite.Equal("LBOW$@%i-=aj%go#RSRP_1av~Tt2", attachment.Blurhash) diff --git a/internal/media/metadata.go b/internal/media/metadata.go new file mode 100644 index 000000000..3816b2826 --- /dev/null +++ b/internal/media/metadata.go @@ -0,0 +1,96 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 ( + "context" + "os" + "strings" + + terminator "codeberg.org/superseriousbusiness/exif-terminator" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/log" +) + +// clearMetadata performs our best-attempt at cleaning metadata from +// input file. Depending on file type this may perform a full EXIF clean, +// or just a clean of global container level metadata records. +func clearMetadata(ctx context.Context, filepath string) error { + var ext, outpath string + + // Generate cleaned output path MAINTAINING extension. + if i := strings.IndexByte(filepath, '.'); i != -1 { + outpath = filepath[:i] + "_cleaned" + filepath[i:] + ext = filepath[i+1:] + } else { + return gtserror.New("input file missing extension") + } + + switch ext { + case "jpeg", "png", "webp": + // For these few file types, we actually support + // cleaning exif data using a native Go library. + log.Debug(ctx, "cleaning with exif-terminator") + err := terminateExif(outpath, filepath, ext) + if err != nil { + return err + } + default: + // For all other types, best-effort clean with ffmpeg. + log.Debug(ctx, "cleaning with ffmpeg -map_metadata -1") + err := ffmpegClearMetadata(ctx, outpath, filepath) + if err != nil { + return err + } + } + + // Move the new output file path to original location. + if err := os.Rename(outpath, filepath); err != nil { + return gtserror.Newf("error renaming %s -> %s: %w", outpath, filepath, err) + } + + return nil +} + +// terminateExif cleans exif data from file at input path, into file +// at output path, exusing given file extension to determine cleaning. +func terminateExif(outpath, inpath string, ext string) error { + // Open input file at given path. + inFile, err := os.Open(inpath) + if err != nil { + return gtserror.Newf("error opening input file %s: %w", inpath, err) + } + + // Open output file at given path. + outFile, err := os.Create(outpath) + if err != nil { + return gtserror.Newf("error opening output file %s: %w", outpath, err) + } + + // Terminate EXIF data from 'inFile' -> 'outFile'. + err = terminator.TerminateInto(outFile, inFile, ext) + if err != nil { + return gtserror.Newf("error terminating exif data: %w", err) + } + + // Done with files. + _ = inFile.Close() + _ = outFile.Close() + + return nil +} diff --git a/internal/media/processingmedia.go b/internal/media/processingmedia.go index 5d1d47b97..504cda11e 100644 --- a/internal/media/processingmedia.go +++ b/internal/media/processingmedia.go @@ -203,8 +203,8 @@ func (p *ProcessingMedia) store(ctx context.Context) error { switch p.media.Type { case gtsmodel.FileTypeImage, gtsmodel.FileTypeVideo: - // Pass file through ffmpeg clearing metadata (e.g. EXIF). - if err := ffmpegClearMetadata(ctx, temppath); err != nil { + // Attempt to clean as metadata from file as possible. + if err := clearMetadata(ctx, temppath); err != nil { return gtserror.Newf("error cleaning metadata: %w", err) } diff --git a/internal/media/test/test-png-alphachannel-processed.png b/internal/media/test/test-png-alphachannel-processed.png Binary files differindex cb3857e9c..164507ced 100644 --- a/internal/media/test/test-png-alphachannel-processed.png +++ b/internal/media/test/test-png-alphachannel-processed.png diff --git a/internal/media/util.go b/internal/media/util.go index 7e84b4cdc..fa5c2bfd6 100644 --- a/internal/media/util.go +++ b/internal/media/util.go @@ -35,25 +35,6 @@ import ( "github.com/disintegration/imaging" ) -// displayDimensions takes account of the -// given rotation data to return width and -// height values as the image will be displayed. -func displayDimensions( - width, height int, - rotation int, -) (int, int) { - // If image is rotated by - // any odd multiples of 90, - // flip width / height to - // get the correct scale. - switch rotation { - case -90, 90, -270, 270: - width, height = height, width - } - - return width, height -} - // thumbSize returns the dimensions to use for an input // image of given width / height, for its outgoing thumbnail. // This attempts to maintains the original image aspect ratio. |