Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_FILE_SIZE;
import static com.github.stickerifier.stickerify.media.MediaConstraints.MAX_VIDEO_FRAMES;
import static com.github.stickerifier.stickerify.media.MediaConstraints.VP9_CODEC;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.github.stickerifier.stickerify.exception.CorruptedFileException;
Expand Down Expand Up @@ -46,6 +47,13 @@ public final class MediaHelper {
private static final int IMAGE_KEEP_ASPECT_RATIO = -1;
private static final int VIDEO_KEEP_ASPECT_RATIO = -2;

private static final int WEBP_CHUNK_TYPE_OFFSET = 12;
private static final int WEBP_CHUNK_TYPE_LENGTH = 4;
private static final int WEBP_FLAGS_BYTE_OFFSET = 20;
private static final int WEBP_HEADER_SIZE = 21;
private static final int WEBP_ANIMATION_BIT_MASK = 0x02;
private static final String WEBP_EXTENDED_FILE_FORMAT = "VP8X";

/**
* Based on the type of passed-in file, it converts it into the proper media.
* If no conversion was needed, {@code null} is returned.
Expand Down Expand Up @@ -73,7 +81,7 @@ public final class MediaHelper {
return null;
}

if (mimeType.startsWith("image/")) {
if (isSupportedImage(inputFile, mimeType)) {
if (isImageCompliant(inputFile, mimeType)) {
LOGGER.atInfo().log("The image doesn't need conversion");
return null;
Expand Down Expand Up @@ -288,6 +296,47 @@ private static boolean isAnimationCompliant(@Nullable AnimationDetails animation
&& animation.height() == MAX_SIDE_LENGTH;
}

/**
* Checks if the MIME type corresponds to one of the supported image formats.
* If the image file is an animated WebP, {@code false} is returned as they are not currently supported.
*
* @param image the image file to check
* @param mimeType the MIME type to check
* @return {@code true} if the MIME type is supported
*/
private static boolean isSupportedImage(File image, String mimeType) {
if ("image/webp".equals(mimeType) && isAnimatedWebp(image)) {
LOGGER.atInfo().log("The image is an animated WebP");
return false;
}

return mimeType.startsWith("image/");
}

/**
* Detects if a WebP file is animated by checking its file header.
*
* @param file the WebP file to check
* @return {@code true} if the file is an animated WebP
*/
private static boolean isAnimatedWebp(File file) {
try (var fileInputStream = new FileInputStream(file)) {
var header = fileInputStream.readNBytes(WEBP_HEADER_SIZE);
if (header.length < WEBP_HEADER_SIZE) {
return false;
}

var chunkHeader = new String(header, WEBP_CHUNK_TYPE_OFFSET, WEBP_CHUNK_TYPE_LENGTH, ISO_8859_1);
boolean isExtendedFormat = WEBP_EXTENDED_FILE_FORMAT.equals(chunkHeader);
boolean hasAnimationFlag = (header[WEBP_FLAGS_BYTE_OFFSET] & WEBP_ANIMATION_BIT_MASK) != 0;

return isExtendedFormat && hasAnimationFlag;
} catch (IOException e) {
LOGGER.atWarn().setCause(e).log("An error occurred checking if the file is an animated WebP");
return false;
}
}

/**
* Checks if passed-in image is already compliant with Telegram's requisites.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ void resizeAnimatedWebpVideo() {
var webpVideo = loadResource("animated.webp");

var ex = assertThrows(MediaException.class, () -> MediaHelper.convert(webpVideo));
assertThat(ex.getMessage(), equalTo("FFmpeg image conversion failed"));
assertThat(ex.getMessage(), equalTo("The file with image/webp MIME type is not supported"));
}

@Test
Expand Down
Loading