app/util/ThumbnailGenerator.scala (61 lines of code) (raw):
package util
import java.awt.RenderingHints
import java.awt.image.{BufferedImage, ColorConvertOp}
import java.io._
import java.net.URL
import com.google.api.client.http.InputStreamContent
import com.gu.media.logging.Logging
import com.gu.media.model.{Image, ImageAsset}
import javax.imageio.ImageIO
case class ThumbnailGenerator(logoFile: File) extends Logging {
// YouTube have a file size limit of 2MB
// see https://developers.google.com/youtube/v3/docs/thumbnails/set
// use a slightly smaller file from Grid so we can add a branding overlay
private val MAX_SIZE = 1.7 * 1000 * 1000
private lazy val logo = ImageIO.read(logoFile)
private def getGridImageAsset(image: Image): ImageAsset = {
image.assets
.filter(asset => asset.size.nonEmpty && asset.size.get < MAX_SIZE)
.maxBy(_.size.get)
}
private def imageAssetToBufferedImage(imageAsset: ImageAsset): BufferedImage = {
val image = ImageIO.read(new URL(imageAsset.file))
val rgbImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_3BYTE_BGR)
new ColorConvertOp(null).filter(image, rgbImage)
}
private def overlayImages(bgImage: BufferedImage, bgImageMimeType: String, atomId: String): ByteArrayInputStream = {
val logoWidth: Double = List(bgImage.getWidth() / 3.0, logo.getWidth()).min
val logoHeight: Double = logo.getHeight() / (logo.getWidth() / logoWidth)
// amount of padding (px) on left and bottom of logo
val PADDING = (bgImage.getHeight() / 18.0).toInt
val logoX = PADDING
val logoY = bgImage.getHeight() - logoHeight.toInt - PADDING
val graphics = bgImage.createGraphics
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
graphics.drawImage(bgImage, 0, 0, null)
log.info(s"Creating branded thumbnail for atom $atomId. Image dims ${bgImage.getWidth()} x ${bgImage.getHeight()}. Logo dims ${logoWidth.toInt} x ${logoHeight.toInt} (x:$logoX, y:$logoY)")
graphics.drawImage(logo, logoX, logoY, logoWidth.toInt, logoHeight.toInt, null)
graphics.dispose()
val os = new ByteArrayOutputStream()
// Although this list isn't exhaustive of all image types,
// it is exhaustive of the current formats supported by Grid.
// NB: Grid only returns mime-type as image/* for the master crop,
// smaller crop renditions are just `jpg` or `png`.
// TODO find a better way to convert mime-type to ImageIO write format
val imageIOWriteFormat = bgImageMimeType match {
case "image/png" | "png" => "png"
case _ => "jpg"
}
ImageIO.write(bgImage, imageIOWriteFormat, os)
new ByteArrayInputStream(os.toByteArray)
}
def getBrandedThumbnail(image: Image, atomId: String): InputStreamContent = {
val imageAsset = getGridImageAsset(image)
val gridImage = imageAssetToBufferedImage(imageAsset)
val mimeType = imageAsset.mimeType.getOrElse("image/jpeg")
new InputStreamContent(
mimeType,
new BufferedInputStream(overlayImages(gridImage, mimeType, atomId))
)
}
def getThumbnail(image: Image): InputStreamContent = {
val imageAsset = getGridImageAsset(image)
val mimeType = imageAsset.mimeType.getOrElse("image/jpeg")
val url = new URL(imageAsset.file)
new InputStreamContent(
mimeType,
new BufferedInputStream(url.openStream())
)
}
}