<commit_before>package net.hearthstats.modules.video

import java.awt.geom.AffineTransform
import java.awt.image.{ AffineTransformOp, BufferedImage }
import java.io.File
import scala.concurrent.{ Future, Promise }
import scala.concurrent.ExecutionContext.Implicits.global
import com.xuggle.mediatool.{ IMediaWriter, ToolFactory }
import grizzled.slf4j.Logging
import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO
import com.xuggle.xuggler.IRational
import scala.util.control.NonFatal
import akka.actor.ActorSystem
import akka.actor.Actor
import akka.actor.Props
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.pattern.ask

class SequenceEncoder extends VideoEncoder with Logging {
  val system = ActorSystem("video")

  def newVideo(framesPerSec: Double, videoWidth: Int, videoHeight: Int) = new OngoingVideo {
    val actor = system.actorOf(Props(new EncodeActor))

    implicit val timeout = Timeout(2.seconds)

    def encodeImage(bi: BufferedImage, timeMs: Long): Future[Unit] = (actor ? (bi, timeMs)).mapTo[Unit]

    /**
     * Returns the name of the compressed file.
     */
    def finish(): Future[String] =
      actor.ask(Finish)(60 seconds).asInstanceOf[Future[String]]

    case object Finish

    class EncodeActor extends Actor {
      val video = File.createTempFile("HSReplay", ".mp4").getAbsolutePath
      var closed = false
      var writer: IMediaWriter = _

      def receive = {
        case (bi: BufferedImage, timeMs: Long) =>
          val encodingStart = System.nanoTime
          if (!closed) {
            try {
              val resized = resize(bi, videoWidth, videoHeight)
              if (writer == null) {
                writer = createWriter(resized)
                val w = resized.getWidth
                val h = resized.getHeight
                info(s"writing to $video : ${w}x$h @$framesPerSec")
              }
              writer.encodeVideo(0, resized, timeMs, TimeUnit.MILLISECONDS)
              val duration = (System.nanoTime - encodingStart) / 1000000
              info(s"encoded until $timeMs ms, took $duration ms")
            } catch {
              case NonFatal(e) => warn(s"could not encode an image into video", e)
              //normally only happens with screenshots used in tests
            }
          }
          sender ! ()
        case Finish =>
          if (!closed) {
            closed = true
            writer.close()
            info(s"Video $video finished encoding")
            sender ! video
          }
      }

      def resize(bi: BufferedImage, x: Int, y: Int): BufferedImage = {
        val (h, w) = (bi.getHeight, bi.getWidth)
        val filtered =
          if (w <= x && h <= y) bi
          else {
            val scale = Math.min(x.toFloat / w, y.toFloat / h)
            val at = AffineTransform.getScaleInstance(scale, scale)
            val scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR)
            scaleOp.filter(bi, null)
          }
        val res = new BufferedImage(filtered.getWidth, filtered.getHeight(), BufferedImage.TYPE_3BYTE_BGR) // type required by xuggle
        res.getGraphics.drawImage(filtered, 0, 0, null)
        res
      }

      private def createWriter(bi: BufferedImage) = {
        val writer = ToolFactory.makeWriter(video)
        val w = if (bi.getWidth % 2 == 0) bi.getWidth else bi.getWidth + 1
        val h = if (bi.getHeight % 2 == 0) bi.getHeight else bi.getHeight + 1
        writer.addVideoStream(0, 0, IRational.make((framesPerSec * 1000).toInt, 1000), w, h)
        writer
      }
    }
  }
}
<commit_msg>relies on xuggler to resize images, seems much more efficient !<commit_after>package net.hearthstats.modules.video

import java.awt.geom.AffineTransform
import java.awt.image.{ AffineTransformOp, BufferedImage }
import java.io.File
import scala.concurrent.{ Future, Promise }
import scala.concurrent.ExecutionContext.Implicits.global
import com.xuggle.mediatool.{ IMediaWriter, ToolFactory }
import grizzled.slf4j.Logging
import java.util.concurrent.TimeUnit
import javax.imageio.ImageIO
import com.xuggle.xuggler.IRational
import scala.util.control.NonFatal
import akka.actor.ActorSystem
import akka.actor.Actor
import akka.actor.Props
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.pattern.ask
import com.xuggle.xuggler.video.ConverterFactory
import com.xuggle.xuggler.IPixelFormat
import com.xuggle.xuggler.video.ArgbConverter

class SequenceEncoder extends VideoEncoder with Logging {
  val system = ActorSystem("video")

  def newVideo(framesPerSec: Double, videoWidth: Int, videoHeight: Int) = new OngoingVideo {
    val actor = system.actorOf(Props(new EncodeActor))

    implicit val timeout = Timeout(2.seconds)

    def encodeImage(bi: BufferedImage, timeMs: Long): Future[Unit] = (actor ? (bi, timeMs)).mapTo[Unit]

    /**
     * Returns the name of the compressed file.
     */
    def finish(): Future[String] =
      actor.ask(Finish)(60 seconds).asInstanceOf[Future[String]]

    case object Finish

    class EncodeActor extends Actor {
      val video = File.createTempFile("HSReplay", ".mp4").getAbsolutePath
      var closed = false
      var writer: IMediaWriter = _

      def receive = {
        case (bi: BufferedImage, timeMs: Long) =>
          val encodingStart = System.nanoTime
          if (!closed) {
            try {
              val image = new BufferedImage(bi.getWidth, bi.getHeight(), BufferedImage.TYPE_3BYTE_BGR)
              image.getGraphics().drawImage(bi, 0, 0, null)
              if (writer == null) {
                writer = createWriter
                val w = image.getWidth
                val h = image.getHeight
                info(s"writing to $video : ${w}x$h @$framesPerSec")
              }
              writer.encodeVideo(0, image, timeMs, TimeUnit.MILLISECONDS)
              val duration = (System.nanoTime - encodingStart) / 1000000
              info(s"encoded until $timeMs ms, took $duration ms")
            } catch {
              case NonFatal(e) => warn(s"could not encode an image into video", e)
              //normally only happens with screenshots used in tests
            }
          }
          sender ! ()
        case Finish =>
          if (!closed) {
            closed = true
            writer.close()
            info(s"Video $video finished encoding")
            sender ! video
          }
      }

      private def createWriter = {
        val writer = ToolFactory.makeWriter(video)
        writer.addVideoStream(0, 0, IRational.make((framesPerSec * 1000).toInt, 1000), videoWidth, videoHeight)
        writer
      }
    }
  }
}

{'fraction_non_alphanumeric': 0.09814759486106961, 'fraction_numerical': 0.008664475649835674, 'mean_word_length': 2.4725103734439835, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5529298', 'n_tokens_mistral': 2024, 'n_tokens_neox': 1937, 'n_words': 506}