diff --git a/controllers/thumbnail_controller/thumbnail_controller.go b/controllers/thumbnail_controller/thumbnail_controller.go
index 0858ad44bf9901e9d05b4ca668a2a2a51879d306..dea0ab35eee146d947e0b430da4af578972913a5 100644
--- a/controllers/thumbnail_controller/thumbnail_controller.go
+++ b/controllers/thumbnail_controller/thumbnail_controller.go
@@ -33,7 +33,10 @@ var supportedThumbnailTypes = []string{
 }
 
 // Of the SupportedThumbnailTypes, these are the 'animated' types
-var animatedTypes = []string{"image/gif"}
+var animatedTypes = []string{
+	"image/gif",
+	"image/png",
+}
 
 var localCache = cache.New(30*time.Second, 60*time.Second)
 
diff --git a/controllers/thumbnail_controller/thumbnail_resource_handler.go b/controllers/thumbnail_controller/thumbnail_resource_handler.go
index 883f67251e4d2c268a5d082fd871626e370c668a..f781912390f4a10e8ef723fbf86d4d5e2bf62b2a 100644
--- a/controllers/thumbnail_controller/thumbnail_resource_handler.go
+++ b/controllers/thumbnail_controller/thumbnail_resource_handler.go
@@ -18,6 +18,7 @@ import (
 
 	"github.com/chai2010/webp"
 	"github.com/disintegration/imaging"
+	"github.com/kettek/apng"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/common"
@@ -222,8 +223,8 @@ func GenerateThumbnail(media *types.Media, width int, height int, method string,
 
 	contentType := "image/png"
 	imgData := &bytes.Buffer{}
-	if allowAnimated && animated {
-		ctx.Log.Info("Generating animated thumbnail")
+	if allowAnimated && animated && media.ContentType == "image/gif" {
+		ctx.Log.Info("Generating animated gif thumbnail")
 		contentType = "image/gif"
 
 		// Animated GIFs are a bit more special because we need to do it frame by frame.
@@ -290,6 +291,69 @@ func GenerateThumbnail(media *types.Media, width int, height int, method string,
 			ctx.Log.Error("Error generating animated thumbnail: " + err.Error())
 			return nil, err
 		}
+	} else if allowAnimated && animated && media.ContentType == "image/png" {
+		ctx.Log.Info("Generating animated png thumbnail")
+		contentType = "image/png"
+
+		// scale animated pngs frame by frame
+
+		mediaStream, err := datastore.DownloadStream(ctx, media.DatastoreId, media.Location)
+		if err != nil {
+			ctx.Log.Error("Error resolving datastore path: ", err)
+			return nil, err
+		}
+		defer cleanup.DumpAndCloseStream(mediaStream)
+
+		p, err := apng.DecodeAll(mediaStream)
+		if err != nil {
+			ctx.Log.Error("Error generating animated thumbnail: " + err.Error())
+			return nil, err
+		}
+
+		// prepare a blank frame to use as swap space
+		frameImg := image.NewRGBA(p.Frames[0].Image.Bounds())
+
+		widthRatio := float64(width) / float64(p.Frames[0].Image.Bounds().Dx())
+		heightRatio := float64(width) / float64(p.Frames[0].Image.Bounds().Dy())
+
+		for i := range p.Frames {
+			frame := p.Frames[i]
+			img := frame.Image
+
+			// Clear the transparency of the previous frame
+			if frame.DisposeOp == apng.DISPOSE_OP_NONE {
+				frame.DisposeOp = apng.DISPOSE_OP_BACKGROUND
+			} else {
+				draw.Draw(frameImg, frameImg.Bounds(), image.Transparent, image.ZP, draw.Src)
+			}
+
+			// Copy the frame to a new image and use that
+			draw.Draw(frameImg, image.Rectangle{image.Point{frame.XOffset, frame.YOffset}, image.Point{img.Bounds().Dx(), img.Bounds().Dy()}}, img, image.ZP, draw.Over)
+
+			// Do the thumbnailing on the copied frame
+			frameThumb, err := thumbnailFrame(frameImg, method, width, height, imaging.Linear, nil)
+			if err != nil {
+				ctx.Log.Error("Error generating animated thumbnail frame: " + err.Error())
+				return nil, err
+			}
+			p.Frames[i].Image = frameThumb
+			newXOffset := int(math.Floor(float64(frame.XOffset) * widthRatio))
+			newYOffset := int(math.Floor(float64(frame.YOffset) * heightRatio))
+			// we need to make sure that these are still in the image bounds
+			if p.Frames[0].Image.Bounds().Dx() <= newXOffset + frameThumb.Bounds().Dx() {
+				newXOffset = p.Frames[0].Image.Bounds().Dx() - frameThumb.Bounds().Dx()
+			}
+			if p.Frames[0].Image.Bounds().Dy() <= newYOffset + frameThumb.Bounds().Dy() {
+				newYOffset = p.Frames[0].Image.Bounds().Dy() - frameThumb.Bounds().Dy()
+			}
+			p.Frames[i].XOffset = newXOffset
+			p.Frames[i].YOffset = newYOffset
+		}
+		err = apng.Encode(imgData, p)
+		if err != nil {
+			ctx.Log.Error("Error generating animated thumbnail: " + err.Error())
+			return nil, err
+		}
 	} else {
 		src, err = thumbnailFrame(src, method, width, height, imaging.Linear, orientation)
 		if err != nil {
@@ -402,15 +466,32 @@ func pickImageFrame(media *types.Media, ctx rcontext.RequestContext) (image.Imag
 	}
 	defer cleanup.DumpAndCloseStream(mediaStream)
 
-	g, err := gif.DecodeAll(mediaStream)
-	if err != nil {
-		ctx.Log.Error("Error picking frame: " + err.Error())
-		return nil, err
+	stillFrameRatio := float64(ctx.Config.Thumbnails.StillFrame)
+	getFrameIndex := func (numFrames int) int {
+		frameIndex := int(math.Floor(math.Min(1, math.Max(0, stillFrameRatio)) * float64(numFrames)))
+		ctx.Log.Info("Picking frame ", frameIndex, " for animated file")
+		return frameIndex
 	}
 
-	stillFrameRatio := float64(ctx.Config.Thumbnails.StillFrame)
-	frameIndex := int(math.Floor(math.Min(1, math.Max(0, stillFrameRatio)) * float64(len(g.Image))))
-	ctx.Log.Info("Picking frame ", frameIndex, " for animated file")
+	if media.ContentType == "image/gif" {
+		g, err := gif.DecodeAll(mediaStream)
+		if err != nil {
+			ctx.Log.Error("Error picking frame: " + err.Error())
+			return nil, err
+		}
 
-	return g.Image[frameIndex], nil
+		frameIndex := getFrameIndex(len(g.Image))
+		return g.Image[frameIndex], nil
+	}
+	if media.ContentType == "image/png" {
+		p, err := apng.DecodeAll(mediaStream)
+		if err != nil {
+			ctx.Log.Error("Error picking frame: " + err.Error())
+			return nil, err
+		}
+
+		frameIndex := getFrameIndex(len(p.Frames))
+		return p.Frames[frameIndex].Image, nil
+	}
+	return nil, errors.New("Unknown animation type: " + media.ContentType)
 }
diff --git a/go.mod b/go.mod
index ba50122e115e0fc89b5dffcbae21a64b08d1ff8a..7ba66ab05a129bb7543a1f8b09a82e92db15dab8 100644
--- a/go.mod
+++ b/go.mod
@@ -34,6 +34,7 @@ require (
 	github.com/ipfs/interface-go-ipfs-core v0.2.6
 	github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
 	github.com/jonboulle/clockwork v0.1.0 // indirect
+	github.com/kettek/apng v0.0.0-20191108220231-414630eed80f
 	github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
 	github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
 	github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
diff --git a/go.sum b/go.sum
index cfa9fededeee46e85f0584d833423457dcfbdfb1..ce5d5e0deba2c81041784c60672fc962f1dad98a 100644
--- a/go.sum
+++ b/go.sum
@@ -358,6 +358,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/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
+github.com/kettek/apng v0.0.0-20191108220231-414630eed80f h1:dnCYnTSltLuPMfc7dMrkz2uBUcEf/OFBR8yRh3oRT98=
+github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=