Skip to content

Commit b60d3ac

Browse files
Fixed conversion of variable refresh rate videos
Also added a new Docker ARG: VIDEO_BUFFER_SIZE with default value 600K, useful to quickly change directly in production the max size of the buffer used in 2pass video conversion, in case some files conversion result in videos greater than the maximum file size allowed
1 parent 44fa406 commit b60d3ac

7 files changed

Lines changed: 49 additions & 31 deletions

File tree

Railway.dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ FROM rob93c/stickerify:$DOCKER_TAG
33
ARG STICKERIFY_TOKEN
44
ARG LOG_LEVEL
55
ARG CONCURRENT_PROCESSES
6+
ARG VIDEO_BUFFER_SIZE
67
ENV STICKERIFY_TOKEN=$STICKERIFY_TOKEN \
78
LOG_LEVEL=$LOG_LEVEL \
8-
CONCURRENT_PROCESSES=$CONCURRENT_PROCESSES
9+
CONCURRENT_PROCESSES=$CONCURRENT_PROCESSES \
10+
VIDEO_BUFFER_SIZE=$VIDEO_BUFFER_SIZE

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ test {
6767
javaLauncher = providers.provider { new JlinkJavaLauncher(jlink.get()) }
6868

6969
useJUnitPlatform()
70+
jvmArgs('--enable-final-field-mutation=ALL-UNNAMED')
7071

7172
testLogging {
7273
events('started', 'passed', 'failed', 'skipped')
@@ -86,7 +87,7 @@ installDist.mustRunAfter(generateCohArchive)
8687

8788
application {
8889
mainClass = 'com.github.stickerifier.stickerify.runner.Main'
89-
applicationDefaultJvmArgs = ['-XX:+UseCompactObjectHeaders', '-XX:+UseShenandoahGC', '-XX:ShenandoahGCMode=generational']
90+
applicationDefaultJvmArgs = ['-XX:+UseCompactObjectHeaders', '-XX:+UseShenandoahGC', '-XX:ShenandoahGCMode=generational', '--enable-final-field-mutation=ALL-UNNAMED']
9091
}
9192

9293
distributions {

src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public final class MediaHelper {
5656
private static final int WEBP_ANIMATION_BIT_MASK = 0x02;
5757
private static final String WEBP_EXTENDED_FILE_FORMAT = "VP8X";
5858

59+
private static final String VIDEO_BUFFER_SIZE = getVideoBufferSize();
60+
5961
/**
6062
* Based on the type of passed-in file, it converts it into the proper media.
6163
* If no conversion was needed, {@code null} is returned.
@@ -476,16 +478,25 @@ private static File convertToWebm(File file) throws MediaException, InterruptedE
476478
"-i", file.getAbsolutePath(),
477479
"-vf", "scale='if(gt(iw,ih),%1$d,%2$d)':'if(gt(iw,ih),%2$d,%1$d)',fps='min(%3$d,source_fps)'".formatted(MAX_SIDE_LENGTH, VIDEO_KEEP_ASPECT_RATIO, MAX_VIDEO_FRAMES),
478480
"-c:v", "libvpx-" + VP9_CODEC,
479-
"-b:v", "650K",
481+
"-row-mt", "1",
482+
"-threads", "2",
483+
"-b:v", VIDEO_BUFFER_SIZE,
484+
"-maxrate", VIDEO_BUFFER_SIZE,
485+
"-bufsize", VIDEO_BUFFER_SIZE,
486+
"-qmin", "25",
487+
"-qmax", "63",
488+
"-g", "120",
489+
"-auto-alt-ref", "0",
480490
"-pix_fmt", "yuv420p",
481491
"-t", String.valueOf(MAX_VIDEO_DURATION_SECONDS),
482492
"-an",
493+
"-enc_time_base", "1/1000", // solves issues related to highly precise fps count
483494
"-passlogfile", logPrefix
484495
};
485496

486497
try {
487-
ProcessHelper.executeCommand(buildFfmpegCommand(baseCommand, "-pass", "1", "-f", "webm", OsConstants.NULL_FILE));
488-
ProcessHelper.executeCommand(buildFfmpegCommand(baseCommand, "-pass", "2", webmVideo.getAbsolutePath()));
498+
ProcessHelper.executeCommand(buildFfmpegCommand(baseCommand, "-cpu-used", "8", "-pass", "1", "-f", "webm", OsConstants.NULL_FILE));
499+
ProcessHelper.executeCommand(buildFfmpegCommand(baseCommand, "-cpu-used", "4", "-pass", "2", webmVideo.getAbsolutePath()));
489500
} catch (ProcessException e) {
490501
try {
491502
deleteFile(webmVideo);
@@ -519,6 +530,12 @@ private static String[] buildFfmpegCommand(String[] baseCommand, String... addit
519530
return commands;
520531
}
521532

533+
private static String getVideoBufferSize() {
534+
var videoBufferSize = System.getenv("VIDEO_BUFFER_SIZE");
535+
536+
return videoBufferSize == null ? "600K" : videoBufferSize;
537+
}
538+
522539
private MediaHelper() {
523540
throw new UnsupportedOperationException();
524541
}

src/main/resources/customUnixStartScript.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22

3-
# from https://github.com/gradle/gradle/blob/68744f918ea82142a63c2904d346473799814be6/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
3+
# from https://github.com/gradle/gradle/blob/41203b0be36cabf983b0ebe9e402e1f37a67b0cc/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
44

55
#
66
# Copyright © 2015 the original authors.
@@ -59,7 +59,7 @@
5959
# Darwin, MinGW, and NonStop.
6060
#
6161
# (3) This script is generated from the Groovy template
62-
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
62+
# https://github.com/gradle/gradle/blob/${gitRef}/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
6363
# within the Gradle project.
6464
#<% /*
6565
# ... and if you're reading this, this IS the template just mentioned.
@@ -92,7 +92,7 @@
9292
# (Template Engines) for details.
9393
#
9494
# (An example invocation of this template is from
95-
# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
95+
# https://github.com/gradle/gradle/blob/HEAD/platforms/software/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
9696
# within the Gradle project, which builds "gradlew".)
9797
# */ %>
9898
# You can find Gradle at https://github.com/gradle/gradle/.

src/main/resources/customWindowsStartScript.txt

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@rem from https://github.com/gradle/gradle/blob/68744f918ea82142a63c2904d346473799814be6/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
1+
@rem from https://github.com/gradle/gradle/blob/41203b0be36cabf983b0ebe9e402e1f37a67b0cc/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/windowsStartScript.txt
22

33
@rem
44
@rem Copyright 2015 the original author or authors.
@@ -25,8 +25,8 @@
2525
@rem
2626
@rem ##########################################################################
2727

28-
@rem Set local scope for the variables with windows NT shell
29-
if "%OS%"=="Windows_NT" setlocal
28+
@rem Set local scope for the variables, and ensure extensions are enabled
29+
setlocal EnableExtensions
3030

3131
set DIRNAME=%~dp0
3232
if "%DIRNAME%"=="" set DIRNAME=.\
@@ -57,7 +57,7 @@ echo. 1>&2
5757
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
5858
echo location of your Java installation. 1>&2
5959

60-
goto fail
60+
"%COMSPEC%" /c exit 1
6161

6262
:findJavaFromJavaHome
6363
set JAVA_HOME=%JAVA_HOME:"=%
@@ -71,7 +71,7 @@ echo. 1>&2
7171
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
7272
echo location of your Java installation. 1>&2
7373

74-
goto fail
74+
"%COMSPEC%" /c exit 1
7575

7676
:execute
7777
@rem Setup the command line
@@ -82,21 +82,10 @@ set CLASSPATH=$classpath
8282
<% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %>
8383

8484
@rem Execute ${applicationName}
85-
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %><% if ( classpath ) {%> -classpath "%CLASSPATH%"<% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>${mainClassName ?: entryPointArgs} %*
85+
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
86+
@rem which allows us to clear the local environment before executing the java command
87+
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %><% if ( classpath ) {%> -classpath "%CLASSPATH%"<% } %> <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>${mainClassName ?: entryPointArgs} %* & call :exitWithErrorLevel
8688

87-
:end
88-
@rem End local scope for the variables with windows NT shell
89-
if %ERRORLEVEL% equ 0 goto mainEnd
90-
91-
:fail
92-
rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of
93-
rem the _cmd.exe /c_ return code!
94-
set EXIT_CODE=%ERRORLEVEL%
95-
if %EXIT_CODE% equ 0 set EXIT_CODE=1
96-
if not ""=="%${exitEnvironmentVar}%" exit %EXIT_CODE%
97-
exit /b %EXIT_CODE%
98-
99-
:mainEnd
100-
if "%OS%"=="Windows_NT" endlocal
101-
102-
:omega
89+
:exitWithErrorLevel
90+
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
91+
"%COMSPEC%" /c exit %ERRORLEVEL%

src/test/java/com/github/stickerifier/stickerify/media/MediaHelperTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ private static void assertVideoConsistency(@Nullable File video, int expectedWid
176176
() -> assertThat("video's frame rate is not correct", videoInfo.frameRate(), is(equalTo(expectedFrameRate))),
177177
() -> assertThat("video must be encoded with the VP9 codec", videoInfo.codec(), is(equalTo(VP9_CODEC))),
178178
() -> assertThat("video's duration is not correct", formatInfo.duration(), is(equalTo(expectedDuration))),
179-
() -> assertThat("video's format must be matroska", formatInfo.format(), startsWith(MATROSKA_FORMAT)),
179+
() -> assertThat("video's format must be Matroska", formatInfo.format(), startsWith(MATROSKA_FORMAT)),
180180
() -> assertThat("video must have no audio stream", mediaInfo.audio(), is(nullValue())),
181181
() -> assertThat("video size should not exceed 256 KB", formatInfo.size(), is(lessThanOrEqualTo(MAX_VIDEO_FILE_SIZE)))
182182
);
@@ -272,6 +272,15 @@ void resizeAnimatedWebpVideo() {
272272
assertThat(ex.getMessage(), equalTo("The file with image/webp MIME type is not supported"));
273273
}
274274

275+
@Test
276+
@Tag(Tags.VIDEO)
277+
void resizeVideoWithHighlyAccurateFpsCount() throws Exception {
278+
var mp4Video = loadResource("highly_accurate_fps_count.mp4");
279+
var result = MediaHelper.convert(mp4Video);
280+
281+
assertVideoConsistency(result, 512, 434, 26F, 2.961F);
282+
}
283+
275284
@Test
276285
@Tag(Tags.ANIMATED_STICKER)
277286
void noAnimatedStickerConversionNeeded() throws Exception {
200 KB
Binary file not shown.

0 commit comments

Comments
 (0)