Skip to content

Commit

Permalink
[#303] fix framerate of gif and video recording and add tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
oomelianchuk committed Nov 13, 2024
1 parent e1897f0 commit 83aedce
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,19 @@ public synchronized void run()

long turns = 0;
long millis = 0;

long duration = 200;
// start screenshot loop
while (run)
{
long start = new Date().getTime();

File file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
writer.compressImageIfNeeded(file, recordingConfigurations.imageScaleFactor(), recordingConfigurations.imageQuality());
writer.write(file);

long duration = new Date().getTime() - start;
long delay = recordingConfigurations.oneImagePerMilliseconds() > duration ? recordingConfigurations.oneImagePerMilliseconds()
: duration;
writer.write(file, delay);
file.delete();
duration = new Date().getTime() - start;
millis += duration;
turns++;
long sleep = recordingConfigurations.oneImagePerMilliseconds() - duration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ protected GifSequenceWriter(RecordingConfigurations recordingConfigurations, Str
/**
* Configures metadata, which will be used for every gif sequence
*
* @param delay
* @param duration
* {@link Integer} value of milliseconds between the neighbor gif sequences
* @param loop
* {@link Boolean} value if the gif should be looped
* @throws IIOInvalidTreeException
*/
private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTreeException
private void configureRootMetadata(long duration, boolean loop) throws IIOInvalidTreeException
{
String metaFormatName = metadata.getNativeMetadataFormatName();
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName);
Expand All @@ -74,7 +74,7 @@ private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTre
graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
graphicsControlExtensionNode.setAttribute("delayTime", Integer.toString(delay / 10));
graphicsControlExtensionNode.setAttribute("delayTime", duration / 10 + "");
graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0");

IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
Expand Down Expand Up @@ -145,11 +145,12 @@ public void start() throws IOException
* Writes a gif sequence into gif file
*/
@Override
public void write(File image)
public void write(File image, long duration)
{
try
{
BufferedImage img = ImageIO.read(image);
configureRootMetadata(duration, false);
writer.writeToSequence(new IIOImage(img, null, metadata), params);
}
catch (IOException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ public class VideoWriter implements Writer
protected VideoWriter(RecordingConfigurations recordingConfigurations, String videoFileName) throws IOException
{
// check if ffmpeg binary is found
double framerate = 1 / ((double) recordingConfigurations.oneImagePerMilliseconds() / 1000);
p = new ProcessBuilder(((VideoRecordingConfigurations) recordingConfigurations).ffmpegBinaryPath(), "-h").start();

pb = new ProcessBuilder(((VideoRecordingConfigurations) recordingConfigurations).ffmpegBinaryPath(), "-y", "-f", "image2pipe", "-r", " 5/1", "-i", "pipe:0", "-c:v", "libx264", videoFileName);
pb = new ProcessBuilder(((VideoRecordingConfigurations) recordingConfigurations).ffmpegBinaryPath(), "-y", "-f", "image2pipe", "-r", " "
+ framerate
+ " ", "-i", "pipe:0", "-c:v", "libx264", videoFileName);
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(new File(((VideoRecordingConfigurations) recordingConfigurations).ffmpegLogFile())));
}
Expand All @@ -75,7 +77,7 @@ public void start() throws IOException
* Writes {@link File} into the FFmpeg {@link Process}
*/
@Override
public void write(File image)
public void write(File image, long duration)
{
byte[] imageBytes;
imageBytes = new byte[(int) image.length()];
Expand All @@ -85,7 +87,6 @@ public void write(File image)

ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(imageBytes));
BufferedImage img = ImageIO.read(iis);

ImageIO.write(img, "PNG", ffmpegInput);
}
catch (IOException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public default BufferedImage resizeImage(BufferedImage originalImage, double sca

public void start() throws IOException;

public void write(File image);
public void write(File image, long delay);

public void stop();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.xceptance.neodymium.junit4.testclasses.recording;

import java.io.IOException;
import java.util.UUID;

import org.junit.Test;
import org.junit.runner.RunWith;

import com.codeborne.selenide.Selenide;
import com.xceptance.neodymium.common.browser.Browser;
import com.xceptance.neodymium.common.recording.FilmTestExecution;
import com.xceptance.neodymium.junit4.NeodymiumRunner;

@Browser("Chrome_headless")
@RunWith(NeodymiumRunner.class)
public class AutomaticRecordingTest
{
public static String uuid;

public static boolean isGif;

@Test
public void test() throws IOException
{
uuid = UUID.randomUUID().toString();
if (isGif)
{
FilmTestExecution.startGifRecording(uuid);
}
else
{
FilmTestExecution.startVideoRecording(uuid);
}
Selenide.open("https://www.timeanddate.com/worldclock/germany/berlin");
Selenide.sleep(30000);

if (isGif)
{
FilmTestExecution.finishGifFilming(uuid, false);
}
else
{
FilmTestExecution.finishVideoFilming(uuid, false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.xceptance.neodymium.junit4.tests.recording.automatic;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import org.aeonbits.owner.ConfigFactory;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.JUnitCore;

import com.xceptance.neodymium.common.recording.FilmTestExecution;
import com.xceptance.neodymium.common.recording.config.RecordingConfigurations;
import com.xceptance.neodymium.junit4.testclasses.recording.AutomaticRecordingTest;
import com.xceptance.neodymium.junit4.tests.NeodymiumTest;

public class RecordingDurationTest extends NeodymiumTest
{
public double runTest(boolean isGif, String oneImagePerMilliseconds) throws IOException
{
AutomaticRecordingTest.isGif = isGif;
String format = isGif ? "gif" : "video";
FilmTestExecution.clearThreadContexts();
Map<String, String> properties1 = new HashMap<>();
properties1.put(format + ".filmAutomatically", "false");
properties1.put(format + ".enableFilming", "true");
properties1.put(format + ".deleteRecordingsAfterAddingToAllureReport", "false");
properties1.put(format + ".oneImagePerMilliseconds", oneImagePerMilliseconds);
final String fileLocation = "config/temp-" + format + "-" + oneImagePerMilliseconds + ".properties";
File tempConfigFile1 = new File("./" + fileLocation);
writeMapToPropertiesFile(properties1, tempConfigFile1);
ConfigFactory.setProperty(FilmTestExecution.TEMPORARY_CONFIG_FILE_PROPERTY_NAME, "file:" + fileLocation);
tempFiles.add(tempConfigFile1);
JUnitCore.runClasses(AutomaticRecordingTest.class);
RecordingConfigurations config = isGif ? FilmTestExecution.getContextGif() : FilmTestExecution.getContextVideo();
File recordingFile = new File(config.tempFolderToStoreRecording() + AutomaticRecordingTest.uuid + "." + config.format());
recordingFile.deleteOnExit();
Assert.assertTrue("the recording file doesn't exist", recordingFile.exists());
ProcessBuilder pb = new ProcessBuilder("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", recordingFile.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
String recordDuration = null;
for (String line = r.readLine(); StringUtils.isNotBlank(line); line = r.readLine())
{
recordDuration = line;
continue;
}
return Double.parseDouble(recordDuration);
}

@Test
public void testVideoRecording() throws IOException
{
double run1000 = runTest(false, "1000");
double run1500 = runTest(false, "1500");
Assert.assertEquals("Videos with different oneImagePerMilliseconds value should have approximaty the same length (1/1000 = " + run1000 + ", 1/1500 = "
+ run1500 + ")", run1000, run1500, 5.0);
}

@Test
public void testGifRecording() throws IOException
{
double run1000 = runTest(true, "1000");
double run1500 = runTest(true, "1500");
Assert.assertEquals("Gifs with different oneImagePerMilliseconds value should have approximaty the same length (1/1000 = " + run1000 + ", 1/1500 = "
+ run1500 + ")", run1000, run1500, 5.0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void start() throws IOException
}

@Override
public void write(File image)
public void write(File image, long delay)
{
screenshots.add(image);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ public void testWriting() throws IOException
File filePath = new File(pathToFile);

writer.start();
writer.write(TestImageGenerator.generateImage());
writer.write(TestImageGenerator.generateImage());
writer.write(TestImageGenerator.generateImage());
writer.write(TestImageGenerator.generateImage(), 100);
writer.write(TestImageGenerator.generateImage(), 100);
writer.write(TestImageGenerator.generateImage(), 100);
writer.stop();

Assert.assertTrue("writer haven't created a file", filePath.exists());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.xceptance.neodymium.junit5.testclasses.recording;

import java.io.IOException;
import java.util.UUID;

import com.codeborne.selenide.Selenide;
import com.xceptance.neodymium.common.browser.Browser;
import com.xceptance.neodymium.common.recording.FilmTestExecution;
import com.xceptance.neodymium.junit5.NeodymiumTest;

@Browser("Chrome_headless")
public class AutomaticRecordingTest
{
public static String uuid;

public static boolean isGif;

@NeodymiumTest
public void test() throws IOException
{
uuid = UUID.randomUUID().toString();
if (isGif)
{
FilmTestExecution.startGifRecording(uuid);
}
else
{
FilmTestExecution.startVideoRecording(uuid);
}
Selenide.open("https://www.timeanddate.com/worldclock/germany/berlin");
Selenide.sleep(30000);

if (isGif)
{
FilmTestExecution.finishGifFilming(uuid, false);
}
else
{
FilmTestExecution.finishVideoFilming(uuid, false);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.xceptance.neodymium.junit5.tests.recording.automatic;

import java.io.File;
import java.io.IOException;

import org.junit.Assert;
import org.junit.jupiter.api.AfterAll;
Expand All @@ -9,6 +10,7 @@
import com.xceptance.neodymium.common.recording.FilmTestExecution;
import com.xceptance.neodymium.common.recording.config.VideoRecordingConfigurations;
import com.xceptance.neodymium.junit4.tests.recording.AbstractRecordingTest;
import com.xceptance.neodymium.junit5.NeodymiumTest;

public class AutomaticVideoRecordingTest extends AbstractRecordingTest
{
Expand All @@ -24,9 +26,17 @@ public static void form()
configurationsClass = VideoRecordingConfigurations.class;
}

@Override
@NeodymiumTest
public void test()
{
super.test();
}

@AfterAll
public static void assertLogFileExists()
public static void assertLogFileExists() throws IOException
{
assertRecordingFileExists();
File logFile = new File(FilmTestExecution.getContextVideo().ffmpegLogFile());
Assert.assertTrue("the logfile for the automatic video recording test exists", logFile.exists());
logFile.delete();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.xceptance.neodymium.junit5.tests.recording.automatic;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import org.aeonbits.owner.ConfigFactory;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.jupiter.api.Test;

import com.xceptance.neodymium.common.recording.FilmTestExecution;
import com.xceptance.neodymium.common.recording.config.RecordingConfigurations;
import com.xceptance.neodymium.junit5.testclasses.recording.AutomaticRecordingTest;
import com.xceptance.neodymium.junit5.tests.AbstractNeodymiumTest;

public class RecordingDurationTest extends AbstractNeodymiumTest
{
public double runTest(boolean isGif, String oneImagePerMilliseconds) throws IOException
{
AutomaticRecordingTest.isGif = isGif;
String format = isGif ? "gif" : "video";
FilmTestExecution.clearThreadContexts();
Map<String, String> properties1 = new HashMap<>();
properties1.put(format + ".filmAutomatically", "false");
properties1.put(format + ".enableFilming", "true");
properties1.put(format + ".deleteRecordingsAfterAddingToAllureReport", "false");
properties1.put(format + ".oneImagePerMilliseconds", oneImagePerMilliseconds);
final String fileLocation = "config/temp-" + format + "-" + oneImagePerMilliseconds + ".properties";
File tempConfigFile1 = new File("./" + fileLocation);
writeMapToPropertiesFile(properties1, tempConfigFile1);
ConfigFactory.setProperty(FilmTestExecution.TEMPORARY_CONFIG_FILE_PROPERTY_NAME, "file:" + fileLocation);
tempFiles.add(tempConfigFile1);
run(AutomaticRecordingTest.class);
RecordingConfigurations config = isGif ? FilmTestExecution.getContextGif() : FilmTestExecution.getContextVideo();
File recordingFile = new File(config.tempFolderToStoreRecording() + AutomaticRecordingTest.uuid + "." + config.format());
recordingFile.deleteOnExit();
Assert.assertTrue("the recording file doesn't exist", recordingFile.exists());
ProcessBuilder pb = new ProcessBuilder("ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", recordingFile.getAbsolutePath());
pb.redirectErrorStream(true);
Process p = pb.start();
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
String recordDuration = null;
for (String line = r.readLine(); StringUtils.isNotBlank(line); line = r.readLine())
{
recordDuration = line;
continue;
}
return Double.parseDouble(recordDuration);
}

@Test
public void testVideoRecording() throws IOException
{
double run1000 = runTest(false, "1000");
double run1500 = runTest(false, "1500");
Assert.assertEquals("Videos with different oneImagePerMilliseconds value should have approximaty the same length (1/1000 = " + run1000 + ", 1/1500 = "
+ run1500 + ")", run1000, run1500, 5.0);
}

@Test
public void testGifRecording() throws IOException
{
double run1000 = runTest(true, "1000");
double run1500 = runTest(true, "1500");
Assert.assertEquals("Gifs with different oneImagePerMilliseconds value should have approximaty the same length (1/1000 = " + run1000 + ", 1/1500 = "
+ run1500 + ")", run1000, run1500, 5.0);
}
}
Loading

0 comments on commit 83aedce

Please sign in to comment.