Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes from deprecated xuggler to humble-video. #410

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions SeleniumGridExtras/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,23 @@
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- <dependency> Not used anywhere
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency> -->
<dependency>
<groupId>xuggle</groupId>
<artifactId>xuggle-xuggler</artifactId>
<version>5.4</version>
<groupId>io.humble</groupId>
<artifactId>humble-video-noarch</artifactId>
<version>0.2.1</version>
</dependency>
<dependency>
<groupId>org.boofcv</groupId>
<artifactId>xuggler</artifactId>
<version>0.16</version>
<groupId>io.humble</groupId>
<artifactId>humble-video-arch-x86_64-pc-linux-gnu6</artifactId>
<version>0.2.1</version>
</dependency>
<dependency> <!-- selenium-api-2.53.1 comes with guava 19.0 -->
<artifactId>guava</artifactId> <!-- Needed for UpgradeGridExtrasTask.getSanitizedReleaseList -->
<groupId>com.google.guava</groupId>
<type>jar</type>
<version>23.0</version>
</dependency>
<!-- <dependency> Not used anywhere
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20090211</version>
</dependency> -->

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
Expand Down Expand Up @@ -154,6 +145,11 @@
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
<!-- Add manifest entries used to identify what's in this bundle -->
<Humble-Native-App>humble-video</Humble-Native-App>
<Humble-Native-Root>.</Humble-Native-Root>
<Humble-Native-Paths>.</Humble-Native-Paths>
<Humble-Native-Bundles>x86_64-pc-linux-gnu6</Humble-Native-Bundles>
</manifestEntries>
</archive>
<descriptorRefs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import com.groupon.seleniumgridextras.utilities.ScreenshotUtility;
import com.groupon.seleniumgridextras.utilities.TimeStampUtility;
import com.groupon.seleniumgridextras.videorecording.ImageProcessor;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IRational;

import io.humble.ferry.*;
import io.humble.video.*;
import io.humble.video.awt.*;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.apache.log4j.Logger;

Expand All @@ -19,7 +18,6 @@
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;


public class VideoRecorderCallable implements Callable {
Expand All @@ -38,10 +36,10 @@ public class VideoRecorderCallable implements Callable {


final private static
IRational
FRAME_RATE =
IRational.make(RuntimeConfig.getConfig().getVideoRecording().getFrames(),
RuntimeConfig.getConfig().getVideoRecording().getSecondsPerFrame());
Rational
FRAME_RATE =
Rational.make(RuntimeConfig.getConfig().getVideoRecording().getSecondsPerFrame(),
RuntimeConfig.getConfig().getVideoRecording().getFrames());
private static Dimension dimension;


Expand All @@ -61,6 +59,10 @@ public VideoRecorderCallable(String sessionID, int timeout) {

@Override
public String call() throws Exception {
if (Boolean.getBoolean("memory.debug")) {
JNIMemoryManager.getMgr().setMemoryDebugging(true);
}

//Probably overkill to null these out, but i'm playing it safe until proven otherwise
this.nodeName =
"Node: " + RuntimeConfig.getOS().getHostName() + " (" + RuntimeConfig.getHostIp()
Expand All @@ -78,55 +80,190 @@ public String call() throws Exception {
// Note we're writing to a temp file. This is to prevent it from being
// downloaded while we're mid-write.
final File tempFile = new File(outputDir, sessionId + ".temp.mp4");
final
IMediaWriter
writer =
ToolFactory.makeWriter(tempFile.getAbsolutePath());

/** First we create a muxer using the passed in filename and formatname if given. */
Muxer muxer = Muxer.make(tempFile.getAbsolutePath(), null, /*formatname*/null);

/** Now, we need to decide what type of codec to use to encode video. Muxers
* have limited sets of codecs they can use. We're going to pick the first one that
* works, or if the user supplied a codec name, we're going to force-fit that
* in instead.
*/
MuxerFormat format = muxer.getFormat();

final Codec codec = Codec.findEncodingCodec(Codec.ID.CODEC_ID_H264);

// We tell it we're going to add one video stream, with id 0,
// at position 0, and that it will have a fixed frame rate of
// FRAME_RATE.
writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_H264,
FRAME_RATE,
screenBounds.width, screenBounds.height);

/**
* Now that we know what codec, we need to create an encoder
*/
Encoder encoder = Encoder.make(codec);

/**
* Video encoders need to know at a minimum:
* width
* height
* pixel format
* Some also need to know frame-rate (older codecs that had a fixed rate at which video files could
* be written needed this). There are many other options you can set on an encoder, but we're
* going to keep it simpler here.
*/
encoder.setWidth(screenBounds.width);
encoder.setHeight(screenBounds.height);
// We are going to use 420P as the format because that's what most video formats these days use
final PixelFormat.Type pixelformat = PixelFormat.Type.PIX_FMT_YUV420P;
encoder.setPixelFormat(pixelformat);
encoder.setTimeBase(FRAME_RATE);

/** An annoynace of some formats is that they need global (rather than per-stream) headers,
* and in that case you have to tell the encoder. And since Encoders are decoupled from
* Muxers, there is no easy way to know this beyond
*/
if (format.getFlag(MuxerFormat.Flag.GLOBAL_HEADER))
encoder.setFlag(Encoder.Flag.FLAG_GLOBAL_HEADER, true);

/** Open the encoder. */
encoder.open(null, null);

/** Add this stream to the muxer. */
muxer.addNewStream(encoder);

/** And open the muxer for business. */
muxer.open(null, null);

int n = muxer.getNumStreams();
MuxerStream[] muxerStreams = new MuxerStream[n];
Coder[] coder = new Coder[n];
for (int i = 0; i < n; i++) {
muxerStreams[i] = muxer.getStream(i);
if (muxerStreams[i] != null) {
coder[i] = muxerStreams[i].getCoder();
}
}

/** Next, we need to make sure we have the right MediaPicture format objects
* to encode data with. Java (and most on-screen graphics programs) use some
* variant of Red-Green-Blue image encoding (a.k.a. RGB or BGR). Most video
* codecs use some variant of YCrCb formatting. So we're going to have to
* convert. To do that, we'll introduce a MediaPictureConverter object later. object.
*/
MediaPictureConverter converter = null;
final MediaPicture picture = MediaPicture.make(
encoder.getWidth(),
encoder.getHeight(),
pixelformat);
picture.setTimeBase(FRAME_RATE);

logger
.info("Starting video recording for session " + getSessionId() + " to " + outputDir
.getAbsolutePath());
.info("Starting video recording for session " + getSessionId() + " to " + outputDir
.getAbsolutePath());

MediaPacket packet = MediaPacket.make();
try {
int imageFrame = 1;
long startTime = System.nanoTime();
addTitleFrame(writer);
int imageFrame = 0;

{
BufferedImage titleFrame
= ImageProcessor.createTitleFrame(
dimension,
BufferedImage.TYPE_3BYTE_BGR,
"Session :" + this.sessionId,
"Host :" + RuntimeConfig.getOS().getHostName() + " ("
+ RuntimeConfig.getHostIp() + ")",
getTimestamp().toString());
if (converter == null)
converter = MediaPictureConverterFactory
.createConverter(titleFrame, picture);
converter.toPicture(picture, titleFrame, imageFrame++);

do
{
encoder.encode(packet, picture);
if (packet.isComplete())
muxer.write(packet, false);
}
while (packet.isComplete());
try
{
Thread.sleep(2);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

while (stopActionNotCalled() && idleTimeoutNotReached()) {

// take the screen shot
BufferedImage
screenshot =
ScreenshotUtility.getResizedScreenshot(dimension.width, dimension.height);
screenshot =
ScreenshotUtility.getResizedScreenshot(dimension.width, dimension.height);

screenshot = ImageProcessor.addTextCaption(screenshot,
"Session: " + this.sessionId,
"Host: " + this.nodeName,
"Timestamp: " + getTimestamp().toString(),
this.lastAction
"Session: " + this.sessionId,
"Host: " + this.nodeName,
"Timestamp: " + getTimestamp().toString(),
this.lastAction
);

// convert to the right image type
BufferedImage bgrScreen = convertToType(screenshot,
BufferedImage.TYPE_3BYTE_BGR);
BufferedImage.TYPE_3BYTE_BGR);

// encode the image
writer.encodeVideo(0, bgrScreen,
System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
/** This is LIKELY not in YUV420P format, so we're going to convert it using some handy utilities. */
if (converter == null)
converter = MediaPictureConverterFactory.createConverter(bgrScreen, picture);
converter.toPicture(picture, bgrScreen, imageFrame++);

do {
encoder.encode(packet, picture);
if (packet.isComplete())
muxer.write(packet, false);
} while (packet.isComplete());

// sleep for framerate milliseconds
Thread.sleep((long) (1000 / FRAME_RATE.getDouble()));
Thread.sleep((long) (1000 * FRAME_RATE.getDouble()));

}
} finally {
writer.close();
/** Encoders, like decoders, sometimes cache pictures so it can do the right key-frame optimizations.
* So, they need to be flushed as well. As with the decoders, the convention is to pass in a null
* input until the output is not complete.
*/
do {
encoder.encode(packet, null);
if (packet.isComplete())
muxer.write(packet, false);
} while (packet.isComplete());

/** Finally, let's clean up after ourselves. */
muxer.close();

muxer.delete();
converter.delete();
packet.delete();
format.delete();

muxer = null;
converter = null;
packet = null;
format = null;

for (int i=0; i < muxerStreams.length; i++) {
if (muxerStreams[i] != null) {
muxerStreams[i].delete();
muxerStreams[i] = null;
}
if (coder[i] != null) {
coder[i].delete();
coder[i] = null;
}
}

// Now, rename our temporary file to the final filename, so that the downloaders can detect it
final File finalFile = new File(outputDir, sessionId + ".mp4");
Expand All @@ -143,26 +280,12 @@ public String call() throws Exception {
}
}

return getSessionId();
}

protected void addTitleFrame(IMediaWriter writer) {
writer.encodeVideo(0,
ImageProcessor
.createTitleFrame(dimension, BufferedImage.TYPE_3BYTE_BGR,
"Session :" + this.sessionId,
"Host :" + RuntimeConfig.getOS().getHostName() + " ("
+ RuntimeConfig.getHostIp() + ")",
getTimestamp().toString()),
0,
TimeUnit.NANOSECONDS);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
if (Boolean.getBoolean("memory.debug")) {
logger.info("number of alive objects:" + JNIMemoryManager.getMgr().getNumPinnedObjects());
}
}

return getSessionId();
}

public void lastAction(String action) {
this.lastActionTimestamp = getTimestamp();
Expand All @@ -180,7 +303,7 @@ public void stop() {
protected void setOutputDirExists(String sessionId) {
if (!outputDir.exists()) {
System.out.println(
"Root Video output dir does not exist, creating it here " + outputDir.getAbsolutePath());
"Root Video output dir does not exist, creating it here " + outputDir.getAbsolutePath());
outputDir.mkdir();
}
}
Expand Down Expand Up @@ -217,17 +340,17 @@ public static boolean isResolutionDivisibleByTwo(Dimension d) {
protected void dynamicallySetDimension() {
try {
BufferedImage
sample =
ScreenshotUtility
.getResizedScreenshot(RuntimeConfig.getConfig().getVideoRecording().getWidth(),
RuntimeConfig.getConfig().getVideoRecording().getHeight());
sample =
ScreenshotUtility
.getResizedScreenshot(RuntimeConfig.getConfig().getVideoRecording().getWidth(),
RuntimeConfig.getConfig().getVideoRecording().getHeight());
dimension = new Dimension(sample.getWidth(), sample.getHeight());
} catch (AWTException e) {
e.printStackTrace();
logger.equals(e);
dimension =
new Dimension(RuntimeConfig.getConfig().getVideoRecording().getWidth(),
RuntimeConfig.getConfig().getVideoRecording().getHeight());
new Dimension(RuntimeConfig.getConfig().getVideoRecording().getWidth(),
RuntimeConfig.getConfig().getVideoRecording().getHeight());
}

}
Expand Down Expand Up @@ -260,7 +383,9 @@ public static BufferedImage convertToType(BufferedImage sourceImage,
else {
image = new BufferedImage(sourceImage.getWidth(),
sourceImage.getHeight(), targetType);
image.getGraphics().drawImage(sourceImage, 0, 0, null);
Graphics g = image.getGraphics();
g.drawImage(sourceImage, 0, 0, null);
g.dispose();
}

return image;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public static BufferedImage createTitleFrame(Dimension dimension, int imageType,
g.setFont(new Font("TimesRoman", Font.PLAIN, 14));
g.drawString("" + line2, firstLineX, secondLineY);
g.drawString("" + line3, firstLineX, thirdLineY);
g.dispose();

return image;
}
Expand Down
Loading