Skip to content

Commit

Permalink
Add a utility to just run the thumbnailer (#633)
Browse files Browse the repository at this point in the history
  • Loading branch information
turt2live authored Dec 26, 2024
1 parent 02b492f commit 84bcb70
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/ipfs
/dev/conduit-db
/dev/psql
/dev/thumb_*
/vcpkg
/libheif

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added

* Allow guests to access uploaded media, as per [MSC4189](https://github.com/matrix-org/matrix-spec-proposals/pull/4189).
* The thumbnailer can now be run independently with the `thumbnailer` binary. See `thumbnailer -help` for details.

### Changed

Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ COPY --from=builder \
/opt/bin/s3_consistency_check \
/opt/bin/combine_signing_keys \
/opt/bin/generate_signing_key \
/opt/bin/thumbnailer \
/usr/local/bin/

COPY ./config.sample.yaml /etc/media-repo.yaml.sample
Expand Down
92 changes: 92 additions & 0 deletions cmd/utilities/thumbnailer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"flag"
"io"
"os"

"github.com/t2bot/matrix-media-repo/common/config"
"github.com/t2bot/matrix-media-repo/common/logging"
"github.com/t2bot/matrix-media-repo/common/rcontext"
"github.com/t2bot/matrix-media-repo/thumbnailing"
"github.com/t2bot/matrix-media-repo/util"
)

func main() {
configPath := flag.String("config", "media-repo.yaml", "The path to the configuration")
inFile := flag.String("i", "", "The input file to thumbnail")
outFile := flag.String("o", "", "The output file to write the thumbnail to. Note that MMR chooses the output file format and won't inspect the extension type supplied here. Typically, output format is PNG.")
targetWidth := flag.Int("w", 512, "The target width of the thumbnail")
targetHeight := flag.Int("h", 512, "The target height of the thumbnail")
targetMethod := flag.String("m", "scale", "The method to use for resizing. Can be 'scale' or 'crop'")
targetAnimated := flag.Bool("a", false, "Whether the thumbnail should be animated (if supported)")
forceMime := flag.String("f", "", "Force the mime type of the input file, ignoring the detected mime type")
flag.Parse()

if inFile == nil || *inFile == "" {
panic("No input file specified")
}
if outFile == nil || *outFile == "" {
panic("No output file specified")
}

// Override config path with config for Docker users
configEnv := os.Getenv("REPO_CONFIG")
if configEnv != "" {
configPath = &configEnv
}

config.Runtime.IsImportProcess = true // prevents us from creating media by accident
config.Path = *configPath

var err error
err = logging.Setup(
config.Get().General.LogDirectory,
config.Get().General.LogColors,
config.Get().General.JsonLogs,
config.Get().General.LogLevel,
)
if err != nil {
panic(err)
}
ctx := rcontext.Initial()

ctx.Log.WithField("width", *targetWidth).WithField("height", *targetHeight).WithField("method", *targetMethod).WithField("animated", *targetAnimated).Info("Thumbnailing options:")

// Read source image
f, err := os.Open(*inFile)
if err != nil {
panic(err)
}
defer f.Close()

mime, err := util.DetectMimeType(f)
if err != nil {
panic(err)
}
ctx.Log.WithField("mime", mime).Info("Detected mime type")

if forceMime != nil && *forceMime != "" {
mime = *forceMime
ctx.Log.WithField("mime", mime).Warn("Forcing mime type")
}

ctx.Log.Info("Generating thumbnail")
t, err := thumbnailing.GenerateThumbnail(f, mime, *targetWidth, *targetHeight, *targetMethod, *targetAnimated, ctx)
if err != nil {
panic(err)
}

ctx.Log.WithField("animated", t.Animated).WithField("contentType", t.ContentType).Info("Writing generated thumbnail")

f2, err := os.Create(*outFile)
if err != nil {
panic(err)
}
defer f2.Close()
if _, err = io.Copy(f2, t.Reader); err != nil {
panic(err)
}

ctx.Log.Info("Done!")
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ require (
github.com/didip/tollbooth/v7 v7.0.2
github.com/docker/go-connections v0.5.0
github.com/go-redsync/redsync/v4 v4.13.0
github.com/h2non/filetype v1.1.3
github.com/julienschmidt/httprouter v1.3.0
github.com/minio/minio-go/v7 v7.0.82
github.com/panjf2000/ants/v2 v2.10.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRid
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
Expand Down
47 changes: 47 additions & 0 deletions util/mime_detect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package util

import (
"io"
"net/http"
"strings"

"github.com/h2non/filetype"
)

func DetectMimeType(r io.ReadSeeker) (string, error) {
buf := make([]byte, 512)

current, err := r.Seek(0, io.SeekCurrent)
if err != nil {
return "", err
}
restore := func() error {
if _, err2 := r.Seek(current, io.SeekStart); err2 != nil {
return err2
}
return nil
}

if _, err := r.Seek(0, io.SeekStart); err != nil {
return "", err
}
if _, err := r.Read(buf); err != nil {
return "", err
}

kind, err := filetype.Match(buf)
if err != nil || kind == filetype.Unknown {
// Try against http library upon error
contentType := http.DetectContentType(buf)
contentType = strings.Split(contentType, ";")[0]

// http should return an octet-stream anyway, but just in case:
if contentType == "" {
contentType = "application/octet-stream"
}

return contentType, restore()
}

return kind.MIME.Value, restore()
}

0 comments on commit 84bcb70

Please sign in to comment.