diff --git a/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java b/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java index 1f1d518a..af26601f 100644 --- a/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java +++ b/src/main/java/com/github/stickerifier/stickerify/media/MediaHelper.java @@ -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; @@ -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. @@ -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; @@ -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. * diff --git a/src/test/java/com/github/stickerifier/stickerify/media/MediaHelperTest.java b/src/test/java/com/github/stickerifier/stickerify/media/MediaHelperTest.java index 5aa342aa..174ad277 100644 --- a/src/test/java/com/github/stickerifier/stickerify/media/MediaHelperTest.java +++ b/src/test/java/com/github/stickerifier/stickerify/media/MediaHelperTest.java @@ -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