From 2b8b853c4db91b90b6fad2b51a7423e4cb7ae720 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Thu, 7 Nov 2024 17:04:53 +0100 Subject: [PATCH] feat(thumbnails): make ggp thumbnails work Signed-off-by: jkoberg --- services/thumbnails/pkg/errors/error.go | 2 + .../pkg/preprocessor/preprocessor.go | 55 ++++++++++++++++++- .../thumbnails/pkg/thumbnail/generator.go | 4 +- .../thumbnails/pkg/thumbnail/thumbnail.go | 25 +++++---- 4 files changed, 71 insertions(+), 15 deletions(-) diff --git a/services/thumbnails/pkg/errors/error.go b/services/thumbnails/pkg/errors/error.go index c411130ce5c..15e89bb59d3 100644 --- a/services/thumbnails/pkg/errors/error.go +++ b/services/thumbnails/pkg/errors/error.go @@ -9,6 +9,8 @@ var ( ErrInvalidType = errors.New("thumbnails: can't encode this type") // ErrNoEncoderForType represents the error when an encoder couldn't be found for a type. ErrNoEncoderForType = errors.New("thumbnails: no encoder for this type found") + // ErrNoGeneratorForType represents the error when a generator couldn't be found for a type. + ErrNoGeneratorForType = errors.New("thumbnails: no generator for this type found") // ErrNoImageFromAudioFile defines an error when an image cannot be extracted from an audio file ErrNoImageFromAudioFile = errors.New("thumbnails: could not extract image from audio file") // ErrNoConverterForExtractedImageFromGgsFile defines an error when the extracted image from an ggs file could not be converted diff --git a/services/thumbnails/pkg/preprocessor/preprocessor.go b/services/thumbnails/pkg/preprocessor/preprocessor.go index 409e2239a2c..752f5b2da2a 100644 --- a/services/thumbnails/pkg/preprocessor/preprocessor.go +++ b/services/thumbnails/pkg/preprocessor/preprocessor.go @@ -4,6 +4,8 @@ import ( "archive/zip" "bufio" "bytes" + "encoding/base64" + "encoding/json" "image" "image/draw" "image/gif" @@ -176,6 +178,57 @@ Scan: // Label for the scanner loop, so we can break it easily return img, scanner.Err() } +// GGPStruct is the layout of a ggp file (which is basically json) +type GGPStruct struct { + Sections []struct { + Cards []struct { + Element struct { + Image struct { + Base64Image string + } + } + } + } +} + +// GgpDecoder is a converter for the geogebra pinboard file +type GgpDecoder struct{} + +// Convert reads the ggp file and returns the first thumbnail image +func (j GgpDecoder) Convert(r io.Reader) (interface{}, error) { + ggp := &GGPStruct{} + err := json.NewDecoder(r).Decode(ggp) + if err != nil { + return nil, err + } + + elem, err := extractBase64ImageFromGGP(ggp) + if err != nil { + return nil, err + } + + b, err := base64.StdEncoding.DecodeString(elem) + if err != nil { + return nil, err + } + + img, _, err := image.Decode(bytes.NewReader(b)) + return img, err +} + +func extractBase64ImageFromGGP(ggp *GGPStruct) (string, error) { + if len(ggp.Sections) < 1 || len(ggp.Sections[0].Cards) < 1 { + return "", errors.New("cant find thumbnail in ggp file") + } + + raw := strings.Split(ggp.Sections[0].Cards[0].Element.Image.Base64Image, "base64,") + if len(raw) < 2 { + return "", errors.New("cant decode ggp thumbnail") + } + + return raw[1], nil +} + // Draw the word in the canvas. The mixX and maxX defines the drawable range // (X axis) where the word can be drawn (in case the word is too big and doesn't // fit in the canvas), and the incY defines the increment in the Y axis if we @@ -259,7 +312,7 @@ func ForType(mimeType string, opts map[string]interface{}) FileConverter { case "application/vnd.geogebra.slides": return GgsDecoder{"_slide0/geogebra_thumbnail.png"} case "application/vnd.geogebra.pinboard": - return GgsDecoder{"geogebra_thumbnail.png"} + return GgpDecoder{} case "image/gif": return GifDecoder{} case "audio/flac": diff --git a/services/thumbnails/pkg/thumbnail/generator.go b/services/thumbnails/pkg/thumbnail/generator.go index 9c90d33f389..5f8e7ae5b2c 100644 --- a/services/thumbnails/pkg/thumbnail/generator.go +++ b/services/thumbnails/pkg/thumbnail/generator.go @@ -89,11 +89,11 @@ func (g GifGenerator) imageToPaletted(img image.Image, p color.Palette) *image.P // or nil if the type is not supported. func GeneratorFor(fileType, processorID string) (Generator, error) { switch strings.ToLower(fileType) { - case typePng, typeJpg, typeJpeg, typeGgs: + case typePng, typeJpg, typeJpeg, typeGgs, typeGgp: return NewSimpleGenerator(fileType, processorID) case typeGif: return NewGifGenerator(fileType, processorID) default: - return nil, errors.ErrNoEncoderForType + return nil, errors.ErrNoGeneratorForType } } diff --git a/services/thumbnails/pkg/thumbnail/thumbnail.go b/services/thumbnails/pkg/thumbnail/thumbnail.go index 68559e5ea93..c85e671a2dc 100644 --- a/services/thumbnails/pkg/thumbnail/thumbnail.go +++ b/services/thumbnails/pkg/thumbnail/thumbnail.go @@ -13,18 +13,19 @@ import ( var ( // SupportedMimeTypes contains an all mimetypes which are supported by the thumbnailer. SupportedMimeTypes = map[string]struct{}{ - "image/png": {}, - "image/jpg": {}, - "image/jpeg": {}, - "image/gif": {}, - "image/bmp": {}, - "image/x-ms-bmp": {}, - "image/tiff": {}, - "text/plain": {}, - "audio/flac": {}, - "audio/mpeg": {}, - "audio/ogg": {}, - "application/vnd.geogebra.slides": {}, + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "image/bmp": {}, + "image/x-ms-bmp": {}, + "image/tiff": {}, + "text/plain": {}, + "audio/flac": {}, + "audio/mpeg": {}, + "audio/ogg": {}, + "application/vnd.geogebra.slides": {}, + "application/vnd.geogebra.pinboard": {}, } )