Last active
August 29, 2015 14:02
-
-
Save tyrcho/6d8f1825cfff810d2c63 to your computer and use it in GitHub Desktop.
mp4 video from screenshots in scala
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>Snippets</groupId> | |
<artifactId>Snippets</artifactId> | |
<version>0.0.1-SNAPSHOT</version> | |
<build> | |
<sourceDirectory>src</sourceDirectory> | |
<plugins> | |
<plugin> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>3.1</version> | |
<configuration> | |
<source>1.7</source> | |
<target>1.7</target> | |
</configuration> | |
</plugin> | |
<plugin> | |
<groupId>net.alchim31.maven</groupId> | |
<artifactId>scala-maven-plugin</artifactId> | |
<version>3.1.0</version> | |
<executions> | |
<execution> | |
<goals> | |
<goal>compile</goal> | |
<goal>testCompile</goal> | |
</goals> | |
</execution> | |
</executions> | |
<configuration> | |
<jvmArgs> | |
<jvmArg>-Xms64m</jvmArg> | |
<jvmArg>-Xmx400m</jvmArg> | |
</jvmArgs> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
<dependencies> | |
<dependency> | |
<groupId>org.jcodec</groupId> | |
<artifactId>jcodec</artifactId> | |
<version>0.1.5</version> | |
</dependency> | |
<dependency> | |
<groupId>org.clapper</groupId> | |
<artifactId>grizzled-slf4j_2.10</artifactId> | |
<version>1.0.1</version> | |
</dependency> | |
<dependency> | |
<groupId>org.slf4j</groupId> | |
<artifactId>slf4j-log4j12</artifactId> | |
<version>1.7.2</version> | |
</dependency> | |
</dependencies> | |
</project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.GraphicsEnvironment | |
import java.awt.Rectangle | |
import java.awt.Robot | |
import java.awt.image.BufferedImage | |
import java.io.File | |
import java.nio.ByteBuffer | |
import java.util.ArrayList | |
import java.util.Arrays | |
import scala.Array.canBuildFrom | |
import org.jcodec.codecs.h264.H264Encoder | |
import org.jcodec.codecs.h264.H264Utils | |
import org.jcodec.common.NIOUtils | |
import org.jcodec.common.model.ColorSpace | |
import org.jcodec.common.model.Picture | |
import org.jcodec.containers.mp4.Brand | |
import org.jcodec.containers.mp4.MP4Packet | |
import org.jcodec.containers.mp4.TrackType | |
import org.jcodec.containers.mp4.muxer.MP4Muxer | |
import org.jcodec.scale.AWTUtil | |
import org.jcodec.scale.RgbToYuv420 | |
import grizzled.slf4j.Logging | |
import javax.imageio.ImageIO | |
object Screenshots extends App with Logging { | |
val folder = "c:/temp/screens" | |
val rollVideo = 30 * 25 // 30 sec | |
val delay = 200 // 2 sec between shots | |
def encoder(c: Int)(screen: Int) = new SequenceEncoder(new File(s"$folder/video-$screen-$c.mp4")) | |
new Thread { | |
override def run() = { | |
var i = 0 | |
var videoId = 0 | |
var encoders = 1 to 2 map encoder(0) | |
while (true) { | |
i += 1 | |
val id = i % rollVideo | |
if (id == 0) { | |
videoId += 1 | |
for (enc <- encoders) enc.finish() | |
encoders = 1 to 2 map encoder(videoId) | |
} | |
Thread.sleep(delay) | |
info(s"capturing image #$i") | |
for ((img, enc) <- screenshotImages zip encoders) | |
enc.encodeImage(img) | |
} | |
} | |
}.start() | |
def screenshotImages = | |
for (device <- GraphicsEnvironment.getLocalGraphicsEnvironment.getScreenDevices) yield { | |
val mode = device.getDisplayMode | |
val bounds = new Rectangle(0, 0, mode.getWidth, mode.getHeight) | |
new Robot(device).createScreenCapture(bounds) | |
} | |
def takeScreenshots(j: Int): Unit = | |
for ((img, i) <- screenshotImages.zipWithIndex) { | |
val filename = s"$folder/$i/saved$j.png" | |
new File(filename).mkdirs | |
val outputfile = new File(filename) | |
ImageIO.write(img, "png", outputfile) | |
println("saved to " + filename) | |
} | |
} | |
class SequenceEncoder(out: File) { | |
val ch = NIOUtils.writableFileChannel(out) | |
val transform = new RgbToYuv420(0, 0) | |
val encoder = new H264Encoder() | |
val spsList = new ArrayList[ByteBuffer]() | |
val ppsList = new ArrayList[ByteBuffer]() | |
val _out = ByteBuffer.allocate(1920 * 1080 * 6) | |
var frameNo = 0 | |
val muxer = new MP4Muxer(ch, Brand.MP4) | |
val outTrack = muxer.addTrackForCompressed(TrackType.VIDEO, 25) | |
def encodeImage(bi: BufferedImage) { | |
val toEncode = Picture.create(bi.getWidth, bi.getHeight, ColorSpace.YUV420) | |
for (i <- 0 until 3) Arrays.fill(toEncode.getData()(i), 0) | |
transform.transform(AWTUtil.fromBufferedImage(bi), toEncode) | |
_out.clear() | |
val result = encoder.encodeFrame(_out, toEncode) | |
spsList.clear() | |
ppsList.clear() | |
H264Utils.encodeMOVPacket(result, spsList, ppsList) | |
outTrack.addFrame(new MP4Packet(result, frameNo, 25, 1, frameNo, true, null, frameNo, 0)) | |
frameNo += 1 | |
} | |
def finish() { | |
outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList)) | |
muxer.writeHeader() | |
NIOUtils.closeQuietly(ch) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment