Skip to content

Commit

Permalink
generate animated thumbnails for animated gifs
Browse files Browse the repository at this point in the history
  • Loading branch information
David Christofas committed Mar 4, 2022
1 parent 0849563 commit d6182a4
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 79 deletions.
82 changes: 43 additions & 39 deletions protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"type": "string",
"enum": [
"PNG",
"JPG"
"JPG",
"GIF"
],
"default": "PNG",
"description": "The file types to which the thumbnail can get encoded to."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ message GetThumbnailRequest {
enum ThumbnailType {
PNG = 0; // Represents PNG type
JPG = 1; // Represents JPG type
GIF = 2; // Represents GIF type
}
// The type to which the thumbnail should get encoded to.
ThumbnailType thumbnail_type = 2;
Expand Down
19 changes: 16 additions & 3 deletions thumbnails/pkg/preprocessor/preprocessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"image"
"image/draw"
"image/gif"
"io"
"math"
"mime"
Expand All @@ -16,24 +17,34 @@ import (
)

type FileConverter interface {
Convert(r io.Reader) (image.Image, error)
Convert(r io.Reader) (interface{}, error)
}

type ImageDecoder struct{}

func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) {
func (i ImageDecoder) Convert(r io.Reader) (interface{}, error) {
img, _, err := image.Decode(r)
if err != nil {
return nil, errors.Wrap(err, `could not decode the image`)
}
return img, nil
}

type GifDecoder struct{}

func (i GifDecoder) Convert(r io.Reader) (interface{}, error) {
img, err := gif.DecodeAll(r)
if err != nil {
return nil, errors.Wrap(err, `could not decode the image`)
}
return img, nil
}

type TxtToImageConverter struct {
fontLoader *FontLoader
}

func (t TxtToImageConverter) Convert(r io.Reader) (image.Image, error) {
func (t TxtToImageConverter) Convert(r io.Reader) (interface{}, error) {
img := image.NewRGBA(image.Rect(0, 0, 640, 480))

imgBounds := img.Bounds()
Expand Down Expand Up @@ -183,6 +194,8 @@ func ForType(mimeType string, opts map[string]interface{}) FileConverter {
return TxtToImageConverter{
fontLoader: fontLoader,
}
case "image/gif":
return GifDecoder{}
default:
return ImageDecoder{}
}
Expand Down
20 changes: 13 additions & 7 deletions thumbnails/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,23 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb
g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type")
return nil
}
encoder := thumbnail.EncoderForType(req.ThumbnailType.String())
if encoder == nil {
generator, err := thumbnail.GeneratorForType(req.ThumbnailType.String())
if err != nil {
g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type")
return nil
}
encoder, err := thumbnail.EncoderForType(req.ThumbnailType.String())
if err != nil {
g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type")
return nil
}

var thumb []byte
var err error
switch {
case req.GetWebdavSource() != nil:
thumb, err = g.handleWebdavSource(ctx, req, encoder)
thumb, err = g.handleWebdavSource(ctx, req, generator, encoder)
case req.GetCs3Source() != nil:
thumb, err = g.handleCS3Source(ctx, req, encoder)
thumb, err = g.handleCS3Source(ctx, req, generator, encoder)
default:
g.logger.Error().Msg("no image source provided")
return merrors.BadRequest(g.serviceID, "image source is missing")
Expand All @@ -97,7 +101,7 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb
return nil
}

func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) {
func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) {
src := req.GetCs3Source()
sRes, err := g.stat(src.Path, src.Authorization)
if err != nil {
Expand All @@ -106,6 +110,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh

tr := thumbnail.Request{
Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)),
Generator: generator,
Encoder: encoder,
Checksum: sRes.GetInfo().GetChecksum().GetSum(),
}
Expand Down Expand Up @@ -136,7 +141,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh
return thumb, nil
}

func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) {
func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) {
src := req.GetWebdavSource()
imgURL, err := url.Parse(src.Url)
if err != nil {
Expand Down Expand Up @@ -182,6 +187,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge
}
tr := thumbnail.Request{
Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)),
Generator: generator,
Encoder: encoder,
Checksum: sRes.GetInfo().GetChecksum().GetSum(),
}
Expand Down
70 changes: 57 additions & 13 deletions thumbnails/pkg/thumbnail/encoding.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
package thumbnail

import (
"errors"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"strings"
)

const (
typePng = "png"
typeJpg = "jpg"
typeJpeg = "jpeg"
typeGif = "gif"
)

var (
// ErrInvalidType represents the error when a type can't be encoded.
ErrInvalidType = errors.New("can't encode this type")
// ErrNoEncoderForType represents the error when an encoder couldn't be found for a type.
ErrNoEncoderForType = errors.New("no encoder for this type found")
)

// Encoder encodes the thumbnail to a specific format.
type Encoder interface {
// Encode encodes the image to a format.
Encode(io.Writer, image.Image) error
Encode(io.Writer, interface{}) error
// Types returns the formats suffixes.
Types() []string
// MimeType returns the mimetype used by the encoder.
Expand All @@ -22,13 +38,17 @@ type Encoder interface {
type PngEncoder struct{}

// Encode encodes to png format
func (e PngEncoder) Encode(w io.Writer, i image.Image) error {
return png.Encode(w, i)
func (e PngEncoder) Encode(w io.Writer, img interface{}) error {
m, ok := img.(image.Image)
if !ok {
return ErrInvalidType
}
return png.Encode(w, m)
}

// Types returns the png suffix
func (e PngEncoder) Types() []string {
return []string{"png"}
return []string{typePng}
}

// MimeType returns the mimetype for png files.
Expand All @@ -40,29 +60,53 @@ func (e PngEncoder) MimeType() string {
type JpegEncoder struct{}

// Encode encodes to jpg
func (e JpegEncoder) Encode(w io.Writer, i image.Image) error {
return jpeg.Encode(w, i, nil)
func (e JpegEncoder) Encode(w io.Writer, img interface{}) error {
m, ok := img.(image.Image)
if !ok {
return ErrInvalidType
}
return jpeg.Encode(w, m, nil)
}

// Types returns the jpg suffixes.
func (e JpegEncoder) Types() []string {
return []string{"jpeg", "jpg"}
return []string{typeJpeg, typeJpg}
}

// MimeType returns the mimetype for jpg files.
func (e JpegEncoder) MimeType() string {
return "image/jpeg"
}

type GifEncoder struct{}

func (e GifEncoder) Encode(w io.Writer, img interface{}) error {
g, ok := img.(*gif.GIF)
if !ok {
return ErrInvalidType
}
return gif.EncodeAll(w, g)
}

func (e GifEncoder) Types() []string {
return []string{typeGif}
}

func (e GifEncoder) MimeType() string {
return "image/gif"
}

// EncoderForType returns the encoder for a given file type
// or nil if the type is not supported.
func EncoderForType(fileType string) Encoder {
func EncoderForType(fileType string) (Encoder, error) {
switch strings.ToLower(fileType) {
case "png":
return PngEncoder{}
case "jpg", "jpeg":
return JpegEncoder{}
case typePng:
return PngEncoder{}, nil
case typeJpg, typeJpeg:
return JpegEncoder{}, nil
case typeGif:
return GifEncoder{}, nil
default:
return nil
return nil, ErrNoEncoderForType
}
}
2 changes: 1 addition & 1 deletion thumbnails/pkg/thumbnail/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestEncoderForType(t *testing.T) {
}

for k, v := range table {
e := EncoderForType(k)
e, _ := EncoderForType(k)
if e != v {
t.Fail()
}
Expand Down
Loading

0 comments on commit d6182a4

Please sign in to comment.