diff options
| author | Matthew Horan <[email protected]> | 2021-11-30 23:09:32 -0500 |
|---|---|---|
| committer | Alexander Rakoczy <[email protected]> | 2022-01-29 15:01:00 -0500 |
| commit | 89e98276d47d2df0fd8546857944121b4b742b68 (patch) | |
| tree | d8c3119b91e44056d74531a9cafa7a42e18f32d4 | |
| parent | 4a6e64b918d7bdb77ee42c5f14ea7663e5802d08 (diff) | |
Rotate JPEG images based on the Exif Orientation
Signed-off-by: Alexander Rakoczy <[email protected]>
| -rw-r--r-- | cmd/server/main.go | 82 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 5 |
3 files changed, 57 insertions, 32 deletions
diff --git a/cmd/server/main.go b/cmd/server/main.go index 41dbce0..335ef90 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -8,10 +8,10 @@ import ( "crypto/sha256" "encoding/base64" "encoding/hex" - "encoding/json" "errors" "flag" "fmt" + "image/jpeg" "io" "io/fs" "io/ioutil" @@ -32,6 +32,7 @@ import ( "cloud.google.com/go/secretmanager/apiv1" "cloud.google.com/go/storage" "git.toothrot.net/i.dis.band/internal" + "github.com/disintegration/imageorient" secretmanager2 "google.golang.org/genproto/googleapis/cloud/secretmanager/v1" ) @@ -115,8 +116,7 @@ const ( const maxUpload = 100 * MiB type exifTag struct { - SourceFile string - Orientation string + SourceFile string } func upload(b *storage.BucketHandle) http.Handler { @@ -171,22 +171,63 @@ func upload(b *storage.BucketHandle) http.Handler { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + if _, err := file.Seek(0, io.SeekStart); err != nil { + log.Printf("file.Seek(0, io.SeekStart) = _, %v", err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + buff := make([]byte, 512) + bytesRead, err := file.Read(buff) + if err != nil { + log.Printf("io.Read(%v) = %v, %v", buff, bytesRead, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + dct := http.DetectContentType(buff[:bytesRead]) tmp.Close() file.Close() r.MultipartForm.RemoveAll() - or, err := orientation(r.Context(), tmp.Name()) + out, err := commandContext(r.Context(), "exiftool", "-icc_profile", "-b", "-w", "icc", tmp.Name()) if err != nil { - log.Printf("orientation(%q) = %v, %v", tmp.Name(), or, err) + log.Printf("commandContext(): %v, %q", err, string(out)) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return } - out, err := commandContext(r.Context(), "exiftool", "-all=", "--icc_profile:all", "-overwrite_original_in_place", tmp.Name()) + if dct == "image/jpeg" { + f, err := os.Open(tmp.Name()) + if err != nil { + log.Printf("os.Open(%v) = %v, %v", tmp.Name(), f, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + img, _, err := imageorient.Decode(f) + if err != nil { + log.Printf("imageorient.Decode(%v) = %v, _, %v", f, img, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + f, err = os.OpenFile(tmp.Name(), os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + log.Printf("os.OpenFile(%v, os.O_WRONLY|os.O_TRUNC, 0600) = %v, %v", tmp.Name(), f, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + err = jpeg.Encode(f, img, nil) + if err != nil { + log.Printf("jpeg.Encode(%v, %v, nil) = %v", f, img, err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + f.Close() + } + out, err = commandContext(r.Context(), "exiftool", "-all=", "-icc_profile<=%d%f.icc", "-overwrite_original_in_place", tmp.Name()) if err != nil { log.Printf("commandContext(): %v, %q", err, string(out)) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - if err := setOrientation(r.Context(), tmp.Name(), or); err != nil { - log.Printf("setOrientation(%q) = %v", tmp.Name(), err) - } ct := head.Header.Get("content-type") ext, err := mime.ExtensionsByType(ct) @@ -236,29 +277,6 @@ func commandContext(ctx context.Context, name string, arg ...string) ([]byte, er return stdOutErr.Bytes(), nil } -func orientation(ctx context.Context, filename string) (string, error) { - o, err := commandContext(ctx, "exiftool", "-j", "-Orientation", filename) - if err != nil { - return "", err - } - var tags []exifTag - if err := json.Unmarshal(o, &tags); err != nil || len(tags) == 0 { - return "", err - } - log.Println(tags) - return tags[0].Orientation, nil -} - -func setOrientation(ctx context.Context, filename string, or string) error { - args := []string{fmt.Sprintf("-Orientation=%s", or), "-overwrite_original_in_place", filename} - out, err := commandContext(ctx, "exiftool", args...) - if err != nil { - log.Printf("setOrientation: %v, %+v, %q", err, args, out) - return err - } - return nil -} - func filename() string { b := make([]byte, 8) n, err := rand.Read(b) @@ -10,6 +10,8 @@ require ( ) require ( + github.com/disintegration/gift v1.2.1 // indirect + github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect @@ -63,6 +63,11 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/gift v1.1.2/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec h1:YrB6aVr9touOt75I9O1SiancmR2GMg45U9UYf0gtgWg= +github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec/go.mod h1:K0KBFIr1gWu/C1Gp10nFAcAE4hsB7JxE6OgLijrJ8Sk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= |
