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
+}