Skip to content

Commit

Permalink
feat: generate blur data URIs for songs
Browse files Browse the repository at this point in the history
Signed-off-by: Matt Gleich <[email protected]>
  • Loading branch information
gleich committed Dec 6, 2024
1 parent 47f195c commit 623334e
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 46 deletions.
31 changes: 24 additions & 7 deletions internal/apis/applemusic/song.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package applemusic

import (
"bytes"
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"

"github.com/gleich/lcp-v2/internal/images"
"github.com/gleich/lumber/v3"
)

Expand All @@ -18,6 +21,7 @@ type song struct {
ReleaseDate string `json:"release_date"`
DurationInMillis int `json:"duration_in_millis"`
AlbumArtURL string `json:"album_art_url"`
AlbumArtBlur string `json:"album_art_blur"`
URL string `json:"url"`
ID string `json:"id"`
}
Expand Down Expand Up @@ -64,19 +68,32 @@ func songFromSongResponse(s songResponse) song {
s.Attributes.URL = u
}

albumArtURL := strings.ReplaceAll(strings.ReplaceAll(
s.Attributes.Artwork.URL,
"{w}",
strconv.Itoa(s.Attributes.Artwork.Width),
), "{h}", strconv.Itoa(s.Attributes.Artwork.Height))

resp, err := http.Get(albumArtURL)
if err != nil {
lumber.Error(err, "failed to fetch song album art", s.Attributes.Name)
}
var b bytes.Buffer
_, err = b.ReadFrom(resp.Body)
if err != nil {
lumber.Error(err, "failed to read data from request")
}

return song{
Track: s.Attributes.Name,
Artist: s.Attributes.ArtistName,
Album: s.Attributes.AlbumName,
Genres: s.Attributes.GenreNames,
ReleaseDate: s.Attributes.ReleaseDate,
DurationInMillis: s.Attributes.DurationInMillis,
AlbumArtURL: strings.ReplaceAll(strings.ReplaceAll(
s.Attributes.Artwork.URL,
"{w}",
strconv.Itoa(s.Attributes.Artwork.Width),
), "{h}", strconv.Itoa(s.Attributes.Artwork.Height)),
URL: s.Attributes.URL,
ID: s.ID,
AlbumArtURL: albumArtURL,
AlbumArtBlur: images.BlurDataURI(images.BlurImage(b.Bytes())),
URL: s.Attributes.URL,
ID: s.ID,
}
}
4 changes: 3 additions & 1 deletion internal/apis/strava/activities.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"
"time"

"github.com/gleich/lcp-v2/internal/images"
"github.com/gleich/lumber/v3"
"github.com/minio/minio-go/v7"
)
Expand Down Expand Up @@ -106,7 +107,8 @@ func fetchActivities(minioClient minio.Client, tokens tokens) ([]activity, error
if a.HasMap {
mapData := fetchMap(stravaActivity.Map.SummaryPolyline)
uploadMap(minioClient, stravaActivity.ID, mapData)
a.MapBlurImage = mapBlurData(mapData)
mapBlurURI := images.BlurDataURI(images.BlurImage(mapData))
a.MapBlurImage = &mapBlurURI
imgurl := fmt.Sprintf(
"https://minio-api.dev.mattglei.ch/mapbox-maps/%d.png",
a.ID,
Expand Down
40 changes: 2 additions & 38 deletions internal/apis/strava/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ package strava
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"image/png"
"net/http"
"net/url"

"github.com/buckket/go-blurhash"
"github.com/gleich/lcp-v2/internal/secrets"
"github.com/gleich/lumber/v3"
"github.com/minio/minio-go/v7"
Expand All @@ -26,12 +23,13 @@ func fetchMap(polyline string) []byte {
params = url.Values{"access_token": {secrets.SECRETS.MapboxAccessToken}}
)
url := fmt.Sprintf(
"https://api.mapbox.com/styles/v1/mattgleich/clxxsfdfm002401qj7jcxh47e/static/path-%f+%s(%s)/auto/%dx%d@2x?"+params.Encode(),
"https://api.mapbox.com/styles/v1/mattgleich/clxxsfdfm002401qj7jcxh47e/static/path-%f+%s(%s)/auto/%dx%d@2x?%s",
lineWidth,
lineColor,
url.QueryEscape(polyline),
width,
height,
params.Encode(),
)
resp, err := http.Get(url)
if err != nil {
Expand All @@ -49,40 +47,6 @@ func fetchMap(polyline string) []byte {
return b.Bytes()
}

func mapBlurData(data []byte) *string {
reader := bytes.NewReader(data)
parsedPNG, err := png.Decode(reader)
if err != nil {
lumber.Error(err, "decoding PNG failed")
return nil
}

width := parsedPNG.Bounds().Dx()
height := parsedPNG.Bounds().Dy()
blurData, err := blurhash.Encode(4, 3, parsedPNG)
if err != nil {
lumber.Error(err, "encoding png into blurhash failed")
return nil
}

scaleDownFactor := 25
blurImage, err := blurhash.Decode(blurData, width/scaleDownFactor, height/scaleDownFactor, 1)
if err != nil {
lumber.Error(err, "decoding blurhash data into img failed")
return nil
}
blurImageBuffer := new(bytes.Buffer)
err = png.Encode(blurImageBuffer, blurImage)
if err != nil {
lumber.Error(err, "creating png based off blurred image failed")
return nil
}
blurDataURI := "data:image/png;base64," + base64.StdEncoding.EncodeToString(
blurImageBuffer.Bytes(),
)
return &blurDataURI
}

func uploadMap(minioClient minio.Client, id uint64, data []byte) {
reader := bytes.NewReader(data)
size := int64(len(data))
Expand Down
46 changes: 46 additions & 0 deletions internal/images/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package images

import (
"bytes"
"encoding/base64"
"fmt"
"image/png"

"github.com/buckket/go-blurhash"
"github.com/gleich/lumber/v3"
)

func BlurImage(data []byte) []byte {
reader := bytes.NewReader(data)
parsedPNG, err := png.Decode(reader)
if err != nil {
lumber.Error(err, "decoding PNG failed")
return nil
}

width := parsedPNG.Bounds().Dx()
height := parsedPNG.Bounds().Dy()
blurData, err := blurhash.Encode(4, 3, parsedPNG)
if err != nil {
lumber.Error(err, "encoding png into blurhash failed")
return nil
}

scaleDownFactor := 25
blurImage, err := blurhash.Decode(blurData, width/scaleDownFactor, height/scaleDownFactor, 1)
if err != nil {
lumber.Error(err, "decoding blurhash data into img failed")
return nil
}
blurImageBuffer := new(bytes.Buffer)
err = png.Encode(blurImageBuffer, blurImage)
if err != nil {
lumber.Error(err, "creating png based off blurred image failed")
return nil
}
return blurImageBuffer.Bytes()
}

func BlurDataURI(data []byte) string {
return fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(data))
}

0 comments on commit 623334e

Please sign in to comment.