Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add webp support #105

Merged
merged 9 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ jobs:
with:
go-version: '1.20'

- name: Install Dependencies
run: |
sudo apt-get install -y libwebp-dev
go install honnef.co/go/tools/cmd/staticcheck@latest

- name: build
run: go build cmd/*go

- name: Install dependencies
run: go install honnef.co/go/tools/cmd/staticcheck@latest

- name: staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
Expand Down
11 changes: 8 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ WORKDIR /app/ui
COPY ui ./
RUN npm install && npm run build

FROM golang:1.20 AS builder
# switch to bullseye due to https://github.com/GoogleContainerTools/distroless/issues/1342
FROM golang:1.20-bullseye AS builder

WORKDIR /app

Expand All @@ -20,7 +21,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Minify Assets
# hadolint ignore=DL3008
RUN apt-get update \
&& apt-get install -y --no-install-recommends minify \
&& apt-get install -y --no-install-recommends minify libwebp-dev \
TheDen marked this conversation as resolved.
Show resolved Hide resolved
&& find ./public -type f \( \
-name "*.html" \
-o -name '*.js' \
Expand All @@ -29,10 +30,14 @@ RUN apt-get update \
-print0 | \
xargs -0 -I '{}' sh -c 'minify -o "{}" "{}"'

RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o ./bin/song-stitch cmd/*.go
RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o ./bin/song-stitch cmd/*.go

# hadolint ignore=DL3006
FROM gcr.io/distroless/base-debian11 AS build-release-stage
ARG ARCH=aarch64

# Copy dependency for webp
COPY --from=builder /usr/lib/${ARCH}-linux-gnu/libwebp.so* /usr/lib/${ARCH}-linux-gnu/

WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ If you would like to run SongStitch yourself, below are the instructions on how

There are currently 2 options to run SongStitch yourself.

1. Build and run the application locally. This requires you to have `go` and `npm` installed.
1. Build and run the application locally. This requires you to have the `go` and `npm` installed, and the `libwebp-dev` library.

2. Run the application inside the docker container. This requires `docker` to be installed.

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/ggicci/httpin v0.10.1
github.com/go-playground/validator/v10 v10.14.0
github.com/justinas/alice v1.2.0
github.com/kolesa-team/go-webp v1.0.4
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/rs/zerolog v1.29.1
)
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
Expand All @@ -57,6 +59,7 @@ github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
Expand All @@ -66,6 +69,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
Expand Down
33 changes: 21 additions & 12 deletions internal/api/api.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package api

import (
"bytes"
"context"
"errors"
"fmt"
"image"
"image/jpeg"
"net/http"
Expand All @@ -25,15 +27,15 @@ type CollageRequest struct {
DisplayAlbum bool `in:"query=album;default=false"`
DisplayTrack bool `in:"query=track;default=false"`
PlayCount bool `in:"query=playcount;default=false"`
Compress bool `in:"query=compress;default=false"`
Width uint `in:"query=width;default=0" validate:"gte=0,lte=3000"`
Height uint `in:"query=height;default=0" validate:"gte=0,lte=3000"`
Method string `in:"query=method;default=album" validate:"required,oneof=album artist track"`
FontSize int `in:"query=fontsize;default=12" validate:"gte=8,lte=30"`
BoldFont bool `in:"query=boldfont;default=false"`
Webp bool `in:"query=webp;default=false"`
}

func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image, error) {
func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image, *bytes.Buffer, error) {
count := request.Rows * request.Columns
imageSize := "extralarge"
imageDimension := 300
Expand All @@ -56,10 +58,10 @@ func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image
Resize: request.Width > 0 || request.Height > 0,
Width: request.Width,
Height: request.Height,
Compress: request.Compress,
ImageDimension: imageDimension,
FontSize: float64(request.FontSize),
BoldFont: request.BoldFont,
Webp: request.Webp,
Rows: request.Rows,
Columns: request.Columns,
}
Expand All @@ -74,7 +76,7 @@ func generateCollage(ctx context.Context, request *CollageRequest) (*image.Image
case constants.TRACK:
return collages.GenerateCollageForTrack(ctx, request.Username, period, count, imageSize, displayOptions)
default:
return nil, errors.New("invalid collage type")
return nil, nil, errors.New("invalid collage type")
}
}

Expand Down Expand Up @@ -103,15 +105,15 @@ func Collage(w http.ResponseWriter, r *http.Request) {
Bool("album", request.DisplayAlbum).
Bool("track", request.DisplayTrack).
Bool("playcount", request.PlayCount).
Bool("compress", request.Compress).
Uint("width", request.Width).
Uint("height", request.Height).
Str("method", request.Method).
Int("fontsize", request.FontSize).
Bool("boldfont", request.BoldFont).
Bool("webp", request.Webp).
Msg("Generating collage")

image, err := generateCollage(ctx, request)
image, buffer, err := generateCollage(ctx, request)
if ctx.Err() != nil {
logger.Warn().Err(ctx.Err()).Msg("Context cancelled")
// 499 is the http status code for client closed request
Expand All @@ -133,17 +135,24 @@ func Collage(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", "image/jpeg")
err = jpeg.Encode(w, *image, nil)
if ctx.Err() != nil {
logger.Warn().Err(ctx.Err()).Msg("Context cancelled")
// 499 is the http status code for client closed request
http.Error(w, "Context cancelled", 499)
return
}
if err != nil {
logger.Error().Err(err).Msg("Error occurred encoding collage")
http.Error(w, "An error occurred processing your request", http.StatusInternalServerError)
return

if request.Webp {
w.Header().Set("Content-Type", "image/webp")
w.Write(buffer.Bytes())
fmt.Println("Setting webp")
} else {
w.Header().Set("Content-Type", "image/jpeg")
err = jpeg.Encode(w, *image, nil)
if err != nil {
logger.Error().Err(err).Msg("Error occurred encoding collage")
http.Error(w, "An error occurred processing your request", http.StatusInternalServerError)
return
}
}
}
8 changes: 6 additions & 2 deletions internal/collages/album.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package collages

import (
"bytes"
"context"
"errors"
"image"
Expand Down Expand Up @@ -57,10 +58,13 @@ func (a *LastFMTopAlbums) GetTotalFetched() int {
return len(a.TopAlbums.Albums)
}

func GenerateCollageForAlbum(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
func GenerateCollageForAlbum(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
if count > 225 {
return nil, nil, constants.ErrTooManyImages
}
albums, err := getAlbums(ctx, username, period, count, imageSize)
if err != nil {
return nil, err
return nil, nil, err
}

generator.DownloadImages(ctx, albums)
Expand Down
7 changes: 4 additions & 3 deletions internal/collages/artist.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package collages

import (
"bytes"
"context"
"errors"
"image"
Expand Down Expand Up @@ -51,13 +52,13 @@ func (a *LastFMTopArtists) GetTotalFetched() int {
return len(a.TopArtists.Artists)
}

func GenerateCollageForArtist(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
func GenerateCollageForArtist(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
if count > 100 {
return nil, constants.ErrTooManyImages
return nil, nil, constants.ErrTooManyImages
}
artists, err := getArtists(ctx, username, period, count, imageSize)
if err != nil {
return nil, err
return nil, nil, err
}

generator.DownloadImages(ctx, artists)
Expand Down
7 changes: 4 additions & 3 deletions internal/collages/track.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package collages

import (
"bytes"
"context"
"errors"
"image"
Expand Down Expand Up @@ -59,13 +60,13 @@ func (t *LastFMTopTracks) GetTotalFetched() int {
return len(t.TopTracks.Tracks)
}

func GenerateCollageForTrack(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, error) {
func GenerateCollageForTrack(ctx context.Context, username string, period constants.Period, count int, imageSize string, displayOptions generator.DisplayOptions) (*image.Image, *bytes.Buffer, error) {
if count > 25 {
return nil, constants.ErrTooManyImages
return nil, nil, constants.ErrTooManyImages
}
tracks, err := getTracks(ctx, username, period, count, imageSize)
if err != nil {
return nil, err
return nil, nil, err
}

generator.DownloadImages(ctx, tracks)
Expand Down
32 changes: 18 additions & 14 deletions internal/generator/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"context"
"fmt"
"image"
"image/jpeg"
"net/url"
"path/filepath"
"strings"
"time"

"github.com/fogleman/gg"
"github.com/kolesa-team/go-webp/encoder"
"github.com/kolesa-team/go-webp/webp"

"github.com/nfnt/resize"
"github.com/rs/zerolog"
)
Expand All @@ -30,6 +32,7 @@ type DisplayOptions struct {
Rows int
Columns int
ImageDimension int
Webp bool
}

const (
Expand Down Expand Up @@ -105,16 +108,17 @@ func resizeImage(ctx context.Context, img *image.Image, width uint, height uint)
return &result
}

func compressImage(collage *image.Image, quality int) (image.Image, error) {
var buf bytes.Buffer
err := jpeg.Encode(&buf, *collage, &jpeg.Options{Quality: quality})
func webpEncode(buf *bytes.Buffer, collage *image.Image, quality float32) error {
options, err := encoder.NewLossyEncoderOptions(encoder.PresetDefault, quality)
if err != nil {
return nil, err
return err
}
return jpeg.Decode(bytes.NewReader(buf.Bytes()))

err = webp.Encode(buf, *collage, options)
return err
}

func CreateCollage[T Drawable](ctx context.Context, collageElements []T, displayOptions DisplayOptions) (*image.Image, error) {
func CreateCollage[T Drawable](ctx context.Context, collageElements []T, displayOptions DisplayOptions) (*image.Image, *bytes.Buffer, error) {
start := time.Now()
logger := zerolog.Ctx(ctx)

Expand Down Expand Up @@ -144,15 +148,15 @@ func CreateCollage[T Drawable](ctx context.Context, collageElements []T, display
collage = *resizeImage(ctx, &collage, displayOptions.Width, displayOptions.Height)
}

if displayOptions.Compress {
collageCompressed, err := compressImage(&collage, compressionQuality)
collageBuffer := new(bytes.Buffer)

if displayOptions.Webp {
err := webpEncode(collageBuffer, &collage, compressionQuality)
if err != nil {
// Skip and just serve the non-compressed image
logger.Err(err).Msg("Unable to compress image")
} else {
collage = collageCompressed
logger.Err(err).Msg("Unable to create Webp image")
}
}

logger.Info().Dur("duration", time.Since(start)).Int("rows", displayOptions.Rows).Int("columns", displayOptions.Columns).Msg("Collage created")
return &collage, nil
return &collage, collageBuffer, nil
}
14 changes: 7 additions & 7 deletions ui/src/lib/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
.min(1),
advancedOptions: z.boolean().optional(),
showTextSize: z.boolean().optional(),
lossyCompression: z.boolean().optional(),
WebPLossyCompression: z.boolean().optional(),
showBoldtext: z.boolean().optional(),
textSize: z.string().optional(),
});
Expand Down Expand Up @@ -66,8 +66,8 @@
if (values.showTextSize) {
params.append('fontsize', values.textSize);
}
if (values.lossyCompression) {
params.append('compress', values.lossyCompression.toString());
if (values.WebPLossyCompression) {
params.append('webp', values.WebPLossyCompression.toString());
}
if (values.showBoldtext) {
params.append('boldfont', values.showBoldtext.toString());
Expand Down Expand Up @@ -99,7 +99,7 @@
showTextSize: false,
textSize: '12',
showBoldtext: false,
lossyCompression: false,
WebPLossyCompression: false,
},
});

Expand Down Expand Up @@ -237,10 +237,10 @@
</div>
{/if}
<Checkbox
text="Lossy Compress Image"
name="lossyCompression"
text="WebP Compressed Image"
name="WebPLossyCompression"
visible={$data.advancedOptions}
bind:checked={$data.lossyCompression}
bind:checked={$data.WebPLossyCompression}
/>
</div>
</fieldset>
Expand Down
2 changes: 2 additions & 0 deletions vendor/github.com/kolesa-team/go-webp/AUTHORS

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

Loading