Skip to content

Commit 4e76cf3

Browse files
Set max duration 1 frame less than 3 seconds
1 parent be6961c commit 4e76cf3

File tree

5 files changed

+49
-64
lines changed

5 files changed

+49
-64
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ therefore counting towards the four-contribution goal.
118118
* The bot is deployed on [Railway](https://railway.app?referralCode=rob)
119119
* The official documentation of the Telegram Bot API can be found [here](https://core.telegram.org/bots)
120120
* The library used by the bot to work with Telegram is [Java Telegram Bot API](https://github.com/pengrad/java-telegram-bot-api)
121-
* Video and image conversions use [FFmpeg](https://ffmpeg.org/) and [JAVE2](https://github.com/a-schild/jave2)
121+
* Video and image conversions use [FFmpeg](https://ffmpeg.org/)
122122
* Animated sticker validation uses [Gson](https://github.com/google/gson)
123123
* MIME type analysis is performed using [Apache Tika](https://tika.apache.org/)
124124

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ public final class MediaConstraints {
1111

1212
static final int MAX_SIDE_LENGTH = 512;
1313
static final int MAX_VIDEO_FRAMES = 30;
14-
static final int MAX_VIDEO_DURATION_MILLIS = 3000;
14+
static final float MAX_VIDEO_DURATION_SECONDS = 3.0F - (1.0F / MAX_VIDEO_FRAMES);
1515
static final String VP9_CODEC = "vp9";
1616
static final String MATROSKA_FORMAT = "matroska";
1717
static final long MAX_IMAGE_FILE_SIZE = 512_000L;
1818
static final long MAX_VIDEO_FILE_SIZE = 256_000L;
1919
static final long MAX_ANIMATION_FILE_SIZE = 64_000L;
2020
static final int MAX_ANIMATION_FRAME_RATE = 60;
21-
static final int MAX_ANIMATION_DURATION_SECONDS = 3;
21+
static final float MAX_ANIMATION_DURATION_SECONDS = 3.0F;
2222

2323
private MediaConstraints() {
2424
throw new UnsupportedOperationException();

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

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_ANIMATION_FRAME_RATE;
77
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_IMAGE_FILE_SIZE;
88
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_SIDE_LENGTH;
9-
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_DURATION_MILLIS;
9+
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_DURATION_SECONDS;
1010
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_FILE_SIZE;
1111
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_FRAMES;
1212
import static com.github.stickerifier.stickerify.media.MediaConstraints.VP9_CODEC;
@@ -132,29 +132,25 @@ private static boolean isVideoCompliant(File file) throws FileOperationException
132132

133133
var formatInfo = mediaInfo.format();
134134
if (formatInfo == null) {
135-
return false; // not a video, maybe throw
135+
return false;
136136
}
137137

138-
var duration = formatInfo.durationAsMillis();
139-
if (duration == null) {
140-
return false; // not a video, maybe throw
138+
if (formatInfo.duration() == null) {
139+
return false;
141140
}
142141

143142
var videoInfo = mediaInfo.video();
144143
if (videoInfo == null) {
145-
return false; // not a video, maybe throw
144+
return false;
146145
}
147146

148-
var size = formatInfo.sizeAsLong();
149-
var frameRate = videoInfo.frameRateAsFloat();
150-
151147
return isSizeCompliant(videoInfo.width(), videoInfo.height())
152-
&& frameRate <= MAX_VIDEO_FRAMES
148+
&& videoInfo.frameRate() <= MAX_VIDEO_FRAMES
153149
&& VP9_CODEC.equals(videoInfo.codec())
154-
&& duration <= MAX_VIDEO_DURATION_MILLIS
150+
&& Float.parseFloat(formatInfo.duration()) <= MAX_VIDEO_DURATION_SECONDS
155151
&& mediaInfo.audio() == null
156152
&& formatInfo.format().startsWith(MATROSKA_FORMAT)
157-
&& size <= MAX_VIDEO_FILE_SIZE;
153+
&& Long.parseLong(formatInfo.size()) <= MAX_VIDEO_FILE_SIZE;
158154
}
159155

160156
/**
@@ -189,43 +185,33 @@ static MultimediaInfo retrieveMultimediaInfo(File file) throws CorruptedFileExce
189185
record MultimediaInfo(List<StreamInfo> streams, @Nullable FormatInfo format) {
190186
@Nullable StreamInfo audio() {
191187
return streams.stream()
192-
.filter(s -> StreamInfo.TYPE_AUDIO.equals(s.type))
188+
.filter(s -> s.type == CodecType.AUDIO)
193189
.findFirst()
194190
.orElse(null);
195191
}
196192

197193
@Nullable StreamInfo video() {
198194
return streams.stream()
199-
.filter(s -> StreamInfo.TYPE_VIDEO.equals(s.type))
195+
.filter(s -> s.type == CodecType.VIDEO)
200196
.findFirst()
201197
.orElse(null);
202198
}
203199
}
204-
record StreamInfo(@SerializedName("codec_name") String codec, @SerializedName("codec_type") String type, int width, int height, @SerializedName("avg_frame_rate") String frameRateFraction) {
205-
private static final String TYPE_AUDIO = "audio";
206-
private static final String TYPE_VIDEO = "video";
207-
208-
float frameRateAsFloat() {
209-
if (frameRateFraction.contains("/")) {
210-
var ratio = frameRateFraction.split("/");
200+
record StreamInfo(@SerializedName("codec_name") String codec, @SerializedName("codec_type") CodecType type, int width, int height, @SerializedName("avg_frame_rate") String frameRateRatio) {
201+
float frameRate() {
202+
if (frameRateRatio.contains("/")) {
203+
var ratio = frameRateRatio.split("/");
211204
return Float.parseFloat(ratio[0]) / Float.parseFloat(ratio[1]);
212205
} else {
213-
return Float.parseFloat(frameRateFraction);
206+
return Float.parseFloat(frameRateRatio);
214207
}
215208
}
216209
}
217-
record FormatInfo(@SerializedName("format_name") String format, @SerializedName("duration") @Nullable String duration, String size) {
218-
@Nullable Long durationAsMillis() {
219-
if (duration == null) {
220-
return null;
221-
}
222-
return (long) (Float.parseFloat(duration) * 1000);
223-
}
224-
225-
long sizeAsLong() {
226-
return Long.parseLong(size);
227-
}
210+
private enum CodecType {
211+
@SerializedName("video") VIDEO,
212+
@SerializedName("audio") AUDIO
228213
}
214+
record FormatInfo(@SerializedName("format_name") String format, @Nullable String duration, String size) {}
229215

230216
/**
231217
* Checks if the file is a {@code gzip} archive, then it reads its content and verifies if it's a valid JSON.
@@ -312,19 +298,17 @@ private static boolean isImageCompliant(File image, String mimeType) throws Corr
312298

313299
var formatInfo = mediaInfo.format();
314300
if (formatInfo == null) {
315-
return false; // not an image, maybe throw
301+
return false;
316302
}
317303

318304
var videoInfo = mediaInfo.video();
319305
if (videoInfo == null) {
320-
return false; // not an image, maybe throw
306+
return false;
321307
}
322308

323-
var size = formatInfo.sizeAsLong();
324-
325309
return ("image/png".equals(mimeType) || "image/webp".equals(mimeType))
326310
&& isSizeCompliant(videoInfo.width(), videoInfo.height())
327-
&& size <= MAX_IMAGE_FILE_SIZE;
311+
&& Long.parseLong(formatInfo.size()) <= MAX_IMAGE_FILE_SIZE;
328312
}
329313

330314
/**
@@ -429,7 +413,7 @@ private static File convertToWebm(File file) throws MediaException, InterruptedE
429413
"-c:v", "libvpx-" + VP9_CODEC,
430414
"-b:v", "650K",
431415
"-pix_fmt", "yuv420p",
432-
"-t", String.valueOf(MAX_VIDEO_DURATION_MILLIS / 1000),
416+
"-t", String.valueOf(MAX_VIDEO_DURATION_SECONDS),
433417
"-an",
434418
"-passlogfile", logPrefix
435419
};

src/main/java/com/github/stickerifier/stickerify/runner/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
public class Main {
66
static final Object LOCK = new Object();
77

8-
public static void main(String[] args) {
8+
static void main() {
99
try (var _ = new Stickerify()) {
1010
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
1111
synchronized (LOCK) {

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

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,16 @@ void resizeRectangularImage() throws Exception {
4646
assertImageConsistency(result, 512, 341);
4747
}
4848

49-
private static void assertImageConsistency(File result, int expectedWidth, int expectedHeight) throws Exception {
50-
var videoInfo = MediaHelper.retrieveMultimediaInfo(result).video();
51-
assertNotNull(videoInfo);
52-
var actualExtension = getExtension(result);
49+
private static void assertImageConsistency(File image, int expectedWidth, int expectedHeight) throws Exception {
50+
var imageInfo = MediaHelper.retrieveMultimediaInfo(image).video();
51+
assertNotNull(imageInfo);
52+
var actualExtension = getExtension(image);
5353

5454
assertAll("Image validation failed",
5555
() -> assertThat("image's extension must be webp", actualExtension, is(equalTo(".webp"))),
56-
() -> assertThat("image's width is not correct", videoInfo.width(), is(equalTo(expectedWidth))),
57-
() -> assertThat("image's height is not correct", videoInfo.height(), is(equalTo(expectedHeight))),
58-
() -> assertThat("image size should not exceed 512 KB", Files.size(result.toPath()), is(lessThanOrEqualTo(MAX_IMAGE_FILE_SIZE)))
56+
() -> assertThat("image's width is not correct", imageInfo.width(), is(equalTo(expectedWidth))),
57+
() -> assertThat("image's height is not correct", imageInfo.height(), is(equalTo(expectedHeight))),
58+
() -> assertThat("image size should not exceed 512 KB", Files.size(image.toPath()), is(lessThanOrEqualTo(MAX_IMAGE_FILE_SIZE)))
5959
);
6060
}
6161

@@ -149,28 +149,29 @@ void convertLongMovVideo() throws Exception {
149149
var movVideo = loadResource("long.mov");
150150
var result = MediaHelper.convert(movVideo);
151151

152-
assertVideoConsistency(result, 512, 288, 29.97003F, 3_003L);
152+
assertVideoConsistency(result, 512, 288, 29.97003F, 2.969F);
153153
}
154154

155-
private static void assertVideoConsistency(File result, int expectedWidth, int expectedHeight, float expectedFrameRate, long expectedDuration) throws Exception {
156-
var mediaInfo = MediaHelper.retrieveMultimediaInfo(result);
155+
private static void assertVideoConsistency(File video, int expectedWidth, int expectedHeight, float expectedFrameRate, float expectedDuration) throws Exception {
156+
var mediaInfo = MediaHelper.retrieveMultimediaInfo(video);
157157
var videoInfo = mediaInfo.video();
158158
assertNotNull(videoInfo);
159159
var formatInfo = mediaInfo.format();
160160
assertNotNull(formatInfo);
161+
assertNotNull(formatInfo.duration());
161162

162-
var actualExtension = getExtension(result);
163+
var actualExtension = getExtension(video);
163164

164165
assertAll("Video validation failed",
165166
() -> assertThat("video's extension must be webm", actualExtension, is(equalTo(".webm"))),
166167
() -> assertThat("video's width is not correct", videoInfo.width(), is(equalTo(expectedWidth))),
167168
() -> assertThat("video's height is not correct", videoInfo.height(), is(equalTo(expectedHeight))),
168-
() -> assertThat("video's frame rate is not correct", videoInfo.frameRateAsFloat(), is(equalTo(expectedFrameRate))),
169+
() -> assertThat("video's frame rate is not correct", videoInfo.frameRate(), is(equalTo(expectedFrameRate))),
169170
() -> assertThat("video must be encoded with the VP9 codec", videoInfo.codec(), is(equalTo(VP9_CODEC))),
170-
() -> assertThat("video's duration is not correct", formatInfo.durationAsMillis(), is(equalTo(expectedDuration))),
171+
() -> assertThat("video's duration is not correct", Float.parseFloat(formatInfo.duration()), is(equalTo(expectedDuration))),
171172
() -> assertThat("video's format must be matroska", formatInfo.format(), startsWith(MATROSKA_FORMAT)),
172173
() -> assertThat("video must have no audio stream", mediaInfo.audio(), is(nullValue())),
173-
() -> assertThat("video size should not exceed 256 KB", Files.size(result.toPath()), is(lessThanOrEqualTo(MAX_VIDEO_FILE_SIZE)))
174+
() -> assertThat("video size should not exceed 256 KB", Files.size(video.toPath()), is(lessThanOrEqualTo(MAX_VIDEO_FILE_SIZE)))
174175
);
175176
}
176177

@@ -180,7 +181,7 @@ void convertMp4WithAudio() throws Exception {
180181
var mp4Video = loadResource("video_with_audio.mp4");
181182
var result = MediaHelper.convert(mp4Video);
182183

183-
assertVideoConsistency(result, 512, 288, 29.97003F, 3_003L);
184+
assertVideoConsistency(result, 512, 288, 29.97003F, 2.969F);
184185
}
185186

186187
@Test
@@ -189,7 +190,7 @@ void convertM4vWithAudio() throws Exception {
189190
var m4vVideo = loadResource("video_with_audio.m4v");
190191
var result = MediaHelper.convert(m4vVideo);
191192

192-
assertVideoConsistency(result, 512, 214, 23.976025F, 3_003L);
193+
assertVideoConsistency(result, 512, 214, 23.976025F, 2.962F);
193194
}
194195

195196
@Test
@@ -198,7 +199,7 @@ void convertShortAndLowFpsVideo() throws Exception {
198199
var webmVideo = loadResource("short_low_fps.webm");
199200
var result = MediaHelper.convert(webmVideo);
200201

201-
assertVideoConsistency(result, 512, 288, 10F, 1_000L);
202+
assertVideoConsistency(result, 512, 288, 10F, 1.0F);
202203
}
203204

204205
@Test
@@ -207,7 +208,7 @@ void resizeSmallWebmVideo() throws Exception {
207208
var webmVideo = loadResource("small_video_sticker.webm");
208209
var result = MediaHelper.convert(webmVideo);
209210

210-
assertVideoConsistency(result, 512, 212, 30F, 2_600L);
211+
assertVideoConsistency(result, 512, 212, 30F, 2.6F);
211212
}
212213

213214
@Test
@@ -216,7 +217,7 @@ void convertVerticalWebmVideo() throws Exception {
216217
var webmVideo = loadResource("vertical_video_sticker.webm");
217218
var result = MediaHelper.convert(webmVideo);
218219

219-
assertVideoConsistency(result, 288, 512, 30F, 2_000L);
220+
assertVideoConsistency(result, 288, 512, 30F, 2.0F);
220221
}
221222

222223
@Test
@@ -225,7 +226,7 @@ void convertGifVideo() throws Exception {
225226
var gifVideo = loadResource("valid.gif");
226227
var result = MediaHelper.convert(gifVideo);
227228

228-
assertVideoConsistency(result, 512, 274, 10F, 1_000L);
229+
assertVideoConsistency(result, 512, 274, 10F, 1.0F);
229230
}
230231

231232
@Test
@@ -234,7 +235,7 @@ void convertAviVideo() throws Exception {
234235
var aviVideo = loadResource("valid.avi");
235236
var result = MediaHelper.convert(aviVideo);
236237

237-
assertVideoConsistency(result, 512, 512, 30F, 3_000L);
238+
assertVideoConsistency(result, 512, 512, 30F, 2.966F);
238239
}
239240

240241
@Test

0 commit comments

Comments
 (0)