diff --git a/assets/default-artwork.png b/assets/default-artwork.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea3ef740b13c7774fc2720b1535f46567668db8 Binary files /dev/null and b/assets/default-artwork.png differ diff --git a/go.mod b/go.mod index 8e975d729fd3a643bf7c4140dee8db592725b565..0ab2f08e21930f1812bfd3a660e7f163d6dc8add 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/buckket/go-blurhash v1.0.3 github.com/cenk/backoff v2.2.1+incompatible // indirect github.com/cupcake/sigil v0.0.0-20131127230922-6bf9722f2ae8 + github.com/dhowden/tag v0.0.0-20200412032933-5d76b8eaae27 github.com/didip/tollbooth v4.0.2+incompatible github.com/disintegration/imaging v1.6.2 github.com/djherbis/stream v1.3.0 diff --git a/go.sum b/go.sum index bb8c70b13bf6cca58da9978d60b5e92dd7e22485..9d3c1f2bb7f3d40a32a532c5c53dc84606fa1b0a 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9 h1:h2Ul3Ym2iVZWMQGYmulVUJ4LSkBm1erp9mUkPwtMoLg= github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhowden/tag v0.0.0-20200412032933-5d76b8eaae27 h1:Z6xaGRBbqfLR797upHuzQ6w4zg33BLKfAKtVCcmMDgg= +github.com/dhowden/tag v0.0.0-20200412032933-5d76b8eaae27/go.mod h1:SniNVYuaD1jmdEEvi+7ywb1QFR7agjeTdGKyFb0p7Rw= github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= diff --git a/thumbnailing/i/flac.go b/thumbnailing/i/flac.go index 213511cdd31ab1cb7dd70022a3c92f7b6cde6493..17e743b23f5c254da54c4290c6cb408f83a9578b 100644 --- a/thumbnailing/i/flac.go +++ b/thumbnailing/i/flac.go @@ -7,6 +7,7 @@ import ( "github.com/faiface/beep/flac" "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" + "github.com/turt2live/matrix-media-repo/thumbnailing/u" "github.com/turt2live/matrix-media-repo/util" ) @@ -40,7 +41,7 @@ func (d flacGenerator) GenerateThumbnail(b []byte, contentType string, width int } defer audio.Close() - return mp3Generator{}.GenerateFromStream(audio, format, width, height) + return mp3Generator{}.GenerateFromStream(audio, format, u.GetID3Tags(b), width, height) } func (d flacGenerator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestContext) (*m.AudioInfo, error) { diff --git a/thumbnailing/i/mp3.go b/thumbnailing/i/mp3.go index 2a9c8eb9724f01afc73e1aacf5365fb0465053db..8df8924209954cc3ecc4f1bc133343d4bf7d5d32 100644 --- a/thumbnailing/i/mp3.go +++ b/thumbnailing/i/mp3.go @@ -5,15 +5,21 @@ import ( "errors" "image" "image/color" + "image/draw" "io" "io/ioutil" "math" + "path" + "github.com/dhowden/tag" "github.com/disintegration/imaging" "github.com/faiface/beep" "github.com/faiface/beep/mp3" + "github.com/sirupsen/logrus" + "github.com/turt2live/matrix-media-repo/common/config" "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" + "github.com/turt2live/matrix-media-repo/thumbnailing/u" "github.com/turt2live/matrix-media-repo/util" ) @@ -45,9 +51,9 @@ func (d mp3Generator) GenerateThumbnail(b []byte, contentType string, width int, if err != nil { return nil, err } - defer audio.Close() - return d.GenerateFromStream(audio, format, width, height) + + return d.GenerateFromStream(audio, format, u.GetID3Tags(b), width, height) } func (d mp3Generator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestContext) (*m.AudioInfo, error) { @@ -98,8 +104,60 @@ func (d mp3Generator) GetDataFromStream(audio beep.StreamSeekCloser, format beep }, nil } -func (d mp3Generator) GenerateFromStream(audio beep.StreamSeekCloser, format beep.Format, width int, height int) (*m.Thumbnail, error) { - info, err := d.GetDataFromStream(audio, format, width) +func (d mp3Generator) GenerateFromStream(audio beep.StreamSeekCloser, format beep.Format, meta tag.Metadata, width int, height int) (*m.Thumbnail, error) { + bgColor := color.RGBA{A: 255, R: 41, G: 57, B: 92} + fgColor := color.RGBA{A: 255, R: 240, G: 240, B: 240} + + img := image.NewRGBA(image.Rect(0, 0, width, height)) + padding := 16 + + sq := int(math.Round(float64(height) * 0.66)) + var artworkImg image.Image + if meta != nil && meta.Picture() != nil { + artwork, _, _ := image.Decode(bytes.NewBuffer(meta.Picture().Data)) + if artwork != nil { + artworkImg, _ = pngGenerator{}.GenerateThumbnailImageOf(artwork, sq, sq, "crop", rcontext.Initial()) + } + } + + ax := sq + ay := sq + + if artworkImg != nil { + ax = artworkImg.Bounds().Max.X + ay = artworkImg.Bounds().Max.Y + } + + dy := (height / 2) - (ay / 2) + dx := padding + ddy := ay + dy + ddx := ax + dx + r := image.Rect(dx, dy, ddx, ddy) + + if artworkImg == nil { + i, _ := ioutil.ReadFile(path.Join(config.Runtime.AssetsPath, "default-artwork.png")) + if i != nil { + tmp, _, _ := image.Decode(bytes.NewBuffer(i)) + if tmp != nil { + artworkImg, _ = pngGenerator{}.GenerateThumbnailImageOf(tmp, ax, ay, "crop", rcontext.Initial()) + } + } + if artworkImg == nil { + logrus.Warn("Falling back to black square for artwork") + tmp := image.NewRGBA(image.Rect(0, 0, ax, ay)) + for x := 0; x < tmp.Bounds().Max.X; x++ { + for y := 0; y < tmp.Bounds().Max.Y; y++ { + tmp.Set(x, y, color.Black) + } + } + artworkImg = tmp + } + } + + draw.Draw(img, r, artworkImg, image.Pt(0, 0), draw.Over) + + waveformX := padding + r.Max.X + info, err := d.GetDataFromStream(audio, format, width-waveformX-padding) if err != nil { return nil, errors.New("beep-visual: error sampling audio: " + err.Error()) } @@ -114,9 +172,7 @@ func (d mp3Generator) GenerateFromStream(audio beep.StreamSeekCloser, format bee averagedSamples = append(averagedSamples, avg) } - // Now that we have samples, generate a plot - img := image.NewRGBA(image.Rect(0, 0, width, height)) - padding := 16 + // Now that we have samples and artwork, generate a plot center := height / 2 for x, s := range averagedSamples { distance := int(math.Round(float64((height-padding)/2) * math.Abs(s))) @@ -127,15 +183,25 @@ func (d mp3Generator) GenerateFromStream(audio beep.StreamSeekCloser, format bee above = false } for y := 0; y < height; y++ { - col := color.RGBA{A: 255, R: 41, G: 57, B: 92} + col := bgColor isWithin := y <= center && y >= px if !above { isWithin = y >= center && y <= px } if isWithin { - col = color.RGBA{A: 255, R: 240, G: 240, B: 240} + col = fgColor + } + img.Set(x+waveformX, y, col) + } + } + + // Fill in the background + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + c := img.RGBAAt(x, y) + if c.A == 0 { + img.Set(x, y, bgColor) } - img.Set(x, y, col) } } diff --git a/thumbnailing/i/ogg.go b/thumbnailing/i/ogg.go index 18c29d0f77deac72e337dab6c32de14730452b11..ef2eaba2f564b084e00ca185bd77e539d9475db4 100644 --- a/thumbnailing/i/ogg.go +++ b/thumbnailing/i/ogg.go @@ -7,6 +7,7 @@ import ( "github.com/faiface/beep/vorbis" "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" + "github.com/turt2live/matrix-media-repo/thumbnailing/u" "github.com/turt2live/matrix-media-repo/util" ) @@ -40,7 +41,7 @@ func (d oggGenerator) GenerateThumbnail(b []byte, contentType string, width int, } defer audio.Close() - return mp3Generator{}.GenerateFromStream(audio, format, width, height) + return mp3Generator{}.GenerateFromStream(audio, format, u.GetID3Tags(b), width, height) } func (d oggGenerator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestContext) (*m.AudioInfo, error) { diff --git a/thumbnailing/i/wav.go b/thumbnailing/i/wav.go index 677375fbe9a57be8005f917425e37eca11f588dc..7d63b5ab422d8cf081ed9c7e8ff108dca10de87e 100644 --- a/thumbnailing/i/wav.go +++ b/thumbnailing/i/wav.go @@ -7,6 +7,7 @@ import ( "github.com/faiface/beep/wav" "github.com/turt2live/matrix-media-repo/common/rcontext" "github.com/turt2live/matrix-media-repo/thumbnailing/m" + "github.com/turt2live/matrix-media-repo/thumbnailing/u" "github.com/turt2live/matrix-media-repo/util" ) @@ -40,7 +41,7 @@ func (d wavGenerator) GenerateThumbnail(b []byte, contentType string, width int, } defer audio.Close() - return mp3Generator{}.GenerateFromStream(audio, format, width, height) + return mp3Generator{}.GenerateFromStream(audio, format, u.GetID3Tags(b), width, height) } func (d wavGenerator) GetAudioData(b []byte, nKeys int, ctx rcontext.RequestContext) (*m.AudioInfo, error) { diff --git a/thumbnailing/u/metadata.go b/thumbnailing/u/metadata.go new file mode 100644 index 0000000000000000000000000000000000000000..e8245adc202066409bf0bdc57821ae98dcfabd94 --- /dev/null +++ b/thumbnailing/u/metadata.go @@ -0,0 +1,12 @@ +package u + +import ( + "bytes" + + "github.com/dhowden/tag" +) + +func GetID3Tags(b []byte) tag.Metadata { + meta, _ := tag.ReadFrom(bytes.NewReader(b)) + return meta +}