diff mbox series

[FFmpeg-devel,v15,4/4] avformat/image2: add Jpeg XL as image2 format

Message ID 20220417132236.200239-5-leo.izen@gmail.com
State Accepted
Commit 3ac23440ef4a5a203f53b33325fa38b2e8afa219
Headers show
Series Jpeg XL Patch Set | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished
andriy/make_aarch64_jetson success Make finished
andriy/make_fate_aarch64_jetson success Make fate finished
andriy/make_armv7_RPi4 success Make finished
andriy/make_fate_armv7_RPi4 success Make fate finished

Commit Message

Leo Izen April 17, 2022, 1:22 p.m. UTC
This commit adds support to libavformat for muxing
and demuxing Jpeg XL images as image2 streams.
---
 MAINTAINERS                |   1 +
 libavformat/Makefile       |   1 +
 libavformat/allformats.c   |   1 +
 libavformat/img2.c         |   1 +
 libavformat/img2dec.c      |  20 ++
 libavformat/img2enc.c      |   6 +-
 libavformat/jpegxl_probe.c | 393 +++++++++++++++++++++++++++++++++++++
 libavformat/jpegxl_probe.h |  32 +++
 libavformat/mov.c          |   1 +
 9 files changed, 453 insertions(+), 3 deletions(-)
 create mode 100644 libavformat/jpegxl_probe.c
 create mode 100644 libavformat/jpegxl_probe.h

Comments

Lynne April 23, 2022, 5:53 p.m. UTC | #1
17 Apr 2022, 15:22 by leo.izen@gmail.com:

> This commit adds support to libavformat for muxing
> and demuxing Jpeg XL images as image2 streams.
> ---
>  MAINTAINERS                |   1 +
>  libavformat/Makefile       |   1 +
>  libavformat/allformats.c   |   1 +
>  libavformat/img2.c         |   1 +
>  libavformat/img2dec.c      |  20 ++
>  libavformat/img2enc.c      |   6 +-
>  libavformat/jpegxl_probe.c | 393 +++++++++++++++++++++++++++++++++++++
>  libavformat/jpegxl_probe.h |  32 +++
>  libavformat/mov.c          |   1 +
>  9 files changed, 453 insertions(+), 3 deletions(-)
>  create mode 100644 libavformat/jpegxl_probe.c
>  create mode 100644 libavformat/jpegxl_probe.h
>

Patchset pushed, thanks!
Mark Gaiser May 4, 2022, 9:56 p.m. UTC | #2
On Sat, Apr 23, 2022 at 7:53 PM Lynne <dev@lynne.ee> wrote:

> 17 Apr 2022, 15:22 by leo.izen@gmail.com:
>
> > This commit adds support to libavformat for muxing
> > and demuxing Jpeg XL images as image2 streams.
> > ---
> >  MAINTAINERS                |   1 +
> >  libavformat/Makefile       |   1 +
> >  libavformat/allformats.c   |   1 +
> >  libavformat/img2.c         |   1 +
> >  libavformat/img2dec.c      |  20 ++
> >  libavformat/img2enc.c      |   6 +-
> >  libavformat/jpegxl_probe.c | 393 +++++++++++++++++++++++++++++++++++++
> >  libavformat/jpegxl_probe.h |  32 +++
> >  libavformat/mov.c          |   1 +
> >  9 files changed, 453 insertions(+), 3 deletions(-)
> >  create mode 100644 libavformat/jpegxl_probe.c
> >  create mode 100644 libavformat/jpegxl_probe.h
> >
>
> Patchset pushed, thanks!
>

Hi,

I might have found a bug here. Or I might not be using it correctly, that's
entirely possible too!
What i try is just making an animated jxl from a bunch of input images.
This command for example works and creates a gif:
ffmpeg -f image2 -framerate 1 -i thumb%04d.jpg output.gif

This command (just changing "gif" to "jxl") errors.
ffmpeg -f image2 -framerate 1 -i thumb%04d.jpg output.jxl

The error:
[image2 @ 0x557ea81112c0] Could not get frame filename number 2 from
pattern 'output.jxl'. Use '-frames:v 1' for a single image, or '-update'
option, or use a pattern such as %03d within the filename.
av_interleaved_write_frame(): Invalid argument

It does look like it tries to use "output.jxl" as a pattern file as opposed
to "thumb%04d.jpg".

Am I doing something wrong here?


> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
Leo Izen May 5, 2022, 3:49 a.m. UTC | #3
On 5/4/22 17:56, Mark Gaiser wrote:
> Hi,
>
> I might have found a bug here. Or I might not be using it correctly, that's
> entirely possible too!
> What i try is just making an animated jxl from a bunch of input images.

Currently animated JXL is not supported.

- Leo Izen (thebombzen)
Mark Gaiser May 5, 2022, 11:13 a.m. UTC | #4
On Thu, May 5, 2022 at 5:49 AM Leo Izen <leo.izen@gmail.com> wrote:

> On 5/4/22 17:56, Mark Gaiser wrote:
> > Hi,
> >
> > I might have found a bug here. Or I might not be using it correctly,
> that's
> > entirely possible too!
> > What i try is just making an animated jxl from a bunch of input images.
>
> Currently animated JXL is not supported.
>

Yeah, I noticed ;)

But is that a limitation on the implementation side within ffmpeg or the
libjxl side?
In either case the error could perhaps be more descriptive as it currently
makes you believe you've done something wrong in the command arguments.

Also, thank you so much for your efforts in getting this in ffmpeg!


> - Leo Izen (thebombzen)
>
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
>
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index faea84ebf1..46723972dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -439,6 +439,7 @@  Muxers/Demuxers:
   ipmovie.c                             Mike Melanson
   ircam*                                Paul B Mahol
   iss.c                                 Stefan Gehrer
+  jpegxl_probe.*                        Leo Izen
   jvdec.c                               Peter Ross
   kvag.c                                Zane van Iperen
   libmodplug.c                          Clément Bœsch
diff --git a/libavformat/Makefile b/libavformat/Makefile
index e3233fd7ac..f16634a418 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -272,6 +272,7 @@  OBJS-$(CONFIG_IMAGE_GIF_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_J2K_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_JPEG_PIPE_DEMUXER)    += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_JPEGLS_PIPE_DEMUXER)  += img2dec.o img2.o
+OBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER)  += img2dec.o img2.o jpegxl_probe.o
 OBJS-$(CONFIG_IMAGE_PAM_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PBM_PIPE_DEMUXER)     += img2dec.o img2.o
 OBJS-$(CONFIG_IMAGE_PCX_PIPE_DEMUXER)     += img2dec.o img2.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 7c1d0ac38f..63876c468f 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -510,6 +510,7 @@  extern const AVInputFormat  ff_image_gif_pipe_demuxer;
 extern const AVInputFormat  ff_image_j2k_pipe_demuxer;
 extern const AVInputFormat  ff_image_jpeg_pipe_demuxer;
 extern const AVInputFormat  ff_image_jpegls_pipe_demuxer;
+extern const AVInputFormat  ff_image_jpegxl_pipe_demuxer;
 extern const AVInputFormat  ff_image_pam_pipe_demuxer;
 extern const AVInputFormat  ff_image_pbm_pipe_demuxer;
 extern const AVInputFormat  ff_image_pcx_pipe_demuxer;
diff --git a/libavformat/img2.c b/libavformat/img2.c
index fe2ca7bfff..566ef873ca 100644
--- a/libavformat/img2.c
+++ b/libavformat/img2.c
@@ -88,6 +88,7 @@  const IdStrMap ff_img_tags[] = {
     { AV_CODEC_ID_GEM,        "ximg"     },
     { AV_CODEC_ID_GEM,        "timg"     },
     { AV_CODEC_ID_VBN,        "vbn"      },
+    { AV_CODEC_ID_JPEGXL,     "jxl"      },
     { AV_CODEC_ID_NONE,       NULL       }
 };
 
diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c
index 551b9d508e..5f9d1f094f 100644
--- a/libavformat/img2dec.c
+++ b/libavformat/img2dec.c
@@ -36,6 +36,7 @@ 
 #include "avio_internal.h"
 #include "internal.h"
 #include "img2.h"
+#include "jpegxl_probe.h"
 #include "libavcodec/mjpeg.h"
 #include "libavcodec/vbn.h"
 #include "libavcodec/xwd.h"
@@ -837,6 +838,24 @@  static int jpegls_probe(const AVProbeData *p)
     return 0;
 }
 
+static int jpegxl_probe(const AVProbeData *p)
+{
+    const uint8_t *b = p->buf;
+
+    /* ISOBMFF-based container */
+    /* 0x4a584c20 == "JXL " */
+    if (AV_RL64(b) == FF_JPEGXL_CONTAINER_SIGNATURE_LE)
+        return AVPROBE_SCORE_EXTENSION + 1;
+    /* Raw codestreams all start with 0xff0a */
+    if (AV_RL16(b) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
+        return 0;
+#if CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER
+    if (ff_jpegxl_verify_codestream_header(p->buf, p->buf_size) >= 0)
+        return AVPROBE_SCORE_MAX - 2;
+#endif
+    return 0;
+}
+
 static int pcx_probe(const AVProbeData *p)
 {
     const uint8_t *b = p->buf;
@@ -1176,6 +1195,7 @@  IMAGEAUTO_DEMUXER(gif,       GIF)
 IMAGEAUTO_DEMUXER_EXT(j2k,   JPEG2000, J2K)
 IMAGEAUTO_DEMUXER_EXT(jpeg,  MJPEG, JPEG)
 IMAGEAUTO_DEMUXER(jpegls,    JPEGLS)
+IMAGEAUTO_DEMUXER(jpegxl,    JPEGXL)
 IMAGEAUTO_DEMUXER(pam,       PAM)
 IMAGEAUTO_DEMUXER(pbm,       PBM)
 IMAGEAUTO_DEMUXER(pcx,       PCX)
diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c
index ae351963d9..5ed97bb833 100644
--- a/libavformat/img2enc.c
+++ b/libavformat/img2enc.c
@@ -263,9 +263,9 @@  static const AVClass img2mux_class = {
 const AVOutputFormat ff_image2_muxer = {
     .name           = "image2",
     .long_name      = NULL_IF_CONFIG_SMALL("image2 sequence"),
-    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,png,"
-                      "ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,im24,"
-                      "sunras,vbn,xbm,xface,pix,y",
+    .extensions     = "bmp,dpx,exr,jls,jpeg,jpg,jxl,ljpg,pam,pbm,pcx,pfm,pgm,pgmyuv,"
+                      "png,ppm,sgi,tga,tif,tiff,jp2,j2c,j2k,xwd,sun,ras,rs,im1,im8,"
+                      "im24,sunras,vbn,xbm,xface,pix,y",
     .priv_data_size = sizeof(VideoMuxData),
     .video_codec    = AV_CODEC_ID_MJPEG,
     .write_header   = write_header,
diff --git a/libavformat/jpegxl_probe.c b/libavformat/jpegxl_probe.c
new file mode 100644
index 0000000000..924b529ad5
--- /dev/null
+++ b/libavformat/jpegxl_probe.c
@@ -0,0 +1,393 @@ 
+/*
+ * Jpeg XL header verification
+ * Copyright (c) 2022 Leo Izen <leo.izen@gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "jpegxl_probe.h"
+
+#define BITSTREAM_READER_LE
+#include "libavcodec/get_bits.h"
+
+enum JpegXLExtraChannelType {
+    FF_JPEGXL_CT_ALPHA = 0,
+    FF_JPEGXL_CT_DEPTH,
+    FF_JPEGXL_CT_SPOT_COLOR,
+    FF_JPEGXL_CT_SELECTION_MASK,
+    FF_JPEGXL_CT_BLACK,
+    FF_JPEGXL_CT_CFA,
+    FF_JPEGXL_CT_THERMAL,
+    FF_JPEGXL_CT_NON_OPTIONAL = 15,
+    FF_JPEGXL_CT_OPTIONAL
+};
+
+enum JpegXLColorSpace {
+    FF_JPEGXL_CS_RGB = 0,
+    FF_JPEGXL_CS_GRAY,
+    FF_JPEGXL_CS_XYB,
+    FF_JPEGXL_CS_UNKNOWN
+};
+
+enum JpegXLWhitePoint {
+    FF_JPEGXL_WP_D65 = 1,
+    FF_JPEGXL_WP_CUSTOM,
+    FF_JPEGXL_WP_E = 10,
+    FF_JPEGXL_WP_DCI = 11
+};
+
+enum JpegXLPrimaries {
+    FF_JPEGXL_PR_SRGB = 1,
+    FF_JPEGXL_PR_CUSTOM,
+    FF_JPEGXL_PR_2100 = 9,
+    FF_JPEGXL_PR_P3 = 11,
+};
+
+#define jxl_bits(n) get_bits_long(gb, (n))
+#define jxl_bits_skip(n) skip_bits_long(gb, (n))
+#define jxl_u32(c0, c1, c2, c3, u0, u1, u2, u3) jpegxl_u32(gb, \
+    (const uint32_t[]){c0, c1, c2, c3}, (const uint32_t[]){u0, u1, u2, u3})
+#define jxl_u64() jpegxl_u64(gb)
+#define jxl_enum() jxl_u32(0, 1, 2, 18, 0, 0, 4, 6)
+
+/* read a U32(c_i + u(u_i)) */
+static uint32_t jpegxl_u32(GetBitContext *gb,
+                           const uint32_t constants[4], const uint32_t ubits[4])
+{
+    uint32_t ret, choice = jxl_bits(2);
+
+    ret = constants[choice];
+    if (ubits[choice])
+        ret += jxl_bits(ubits[choice]);
+
+    return ret;
+}
+
+/* read a U64() */
+static uint64_t jpegxl_u64(GetBitContext *gb)
+{
+    uint64_t shift = 12, ret;
+
+    switch (jxl_bits(2)) {
+    case 0:
+        ret = 0;
+        break;
+    case 1:
+        ret = 1 + jxl_bits(4);
+        break;
+    case 2:
+        ret = 17 + jxl_bits(8);
+        break;
+    case 3:
+        ret = jxl_bits(12);
+        while (jxl_bits(1)) {
+            if (shift < 60) {
+                ret |= jxl_bits(8) << shift;
+                shift += 8;
+            } else {
+                ret |= jxl_bits(4) << shift;
+                break;
+            }
+        }
+        break;
+    }
+
+    return ret;
+}
+
+static uint32_t jpegxl_width_from_ratio(uint32_t height, int ratio)
+{
+    uint64_t height64 = height; /* avoid integer overflow */
+    switch (ratio) {
+    case 1:
+        return height;
+    case 2:
+        return (uint32_t)((height64 * 12) / 10);
+    case 3:
+        return (uint32_t)((height64 * 4) / 3);
+    case 4:
+        return (uint32_t)((height64 * 3) / 2);
+    case 5:
+        return (uint32_t)((height64 * 16) / 9);
+    case 6:
+        return (uint32_t)((height64 * 5) / 4);
+    case 7:
+        return (uint32_t)(height64 * 2);
+    default:
+        break;
+    }
+
+    return 0; /* manual width */
+}
+
+/**
+ * validate a Jpeg XL Size Header
+ * @return >= 0 upon valid size, < 0 upon invalid size found
+ */
+static int jpegxl_read_size_header(GetBitContext *gb)
+{
+    uint32_t width, height;
+
+    if (jxl_bits(1)) {
+        /* small size header */
+        height = (jxl_bits(5) + 1) << 3;
+        width = jpegxl_width_from_ratio(height, jxl_bits(3));
+        if (!width)
+            width = (jxl_bits(5) + 1) << 3;
+    } else {
+        /* large size header */
+        height = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30);
+        width = jpegxl_width_from_ratio(height, jxl_bits(3));
+        if (!width)
+            width = 1 + jxl_u32(0, 0, 0, 0, 9, 13, 18, 30);
+    }
+    if (width > (1 << 18) || height > (1 << 18)
+        || (width >> 4) * (height >> 4) > (1 << 20))
+        return -1;
+
+    return 0;
+}
+
+/**
+ * validate a Jpeg XL Preview Header
+ * @return >= 0 upon valid size, < 0 upon invalid size found
+ */
+static int jpegxl_read_preview_header(GetBitContext *gb)
+{
+    uint32_t width, height;
+
+    if (jxl_bits(1)) {
+        /* coded height and width divided by eight */
+        height = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3;
+        width = jpegxl_width_from_ratio(height, jxl_bits(3));
+        if (!width)
+            width = jxl_u32(16, 32, 1, 33, 0, 0, 5, 9) << 3;
+    } else {
+        /* full height and width coded */
+        height = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12);
+        width = jpegxl_width_from_ratio(height, jxl_bits(3));
+        if (!width)
+            width = jxl_u32(1, 65, 321, 1345, 6, 8, 10, 12);
+    }
+    if (width > 4096 || height > 4096)
+        return -1;
+
+    return 0;
+}
+
+/**
+ * skip a Jpeg XL BitDepth Header. These cannot be invalid.
+ */
+static void jpegxl_skip_bit_depth(GetBitContext *gb)
+{
+    if (jxl_bits(1)) {
+        /* float samples */
+        jxl_u32(32, 16, 24, 1, 0, 0, 0, 6); /* mantissa */
+        jxl_bits_skip(4); /* exponent */
+    } else {
+        /* integer samples */
+        jxl_u32(8, 10, 12, 1, 0, 0, 0, 6);
+    }
+}
+
+/**
+ * validate a Jpeg XL Extra Channel Info bundle
+ * @return >= 0 upon valid, < 0 upon invalid
+ */
+static int jpegxl_read_extra_channel_info(GetBitContext *gb)
+{
+    int all_default = jxl_bits(1);
+    uint32_t type, name_len = 0;
+
+    if (!all_default) {
+        type = jxl_enum();
+        if (type > 63)
+            return -1; /* enum types cannot be 64+ */
+        if (type == FF_JPEGXL_CT_BLACK)
+            return -1;
+        jpegxl_skip_bit_depth(gb);
+        jxl_u32(0, 3, 4, 1, 0, 0, 0, 3); /* dim-shift */
+        /* max of name_len is 1071 = 48 + 2^10 - 1 */
+        name_len = jxl_u32(0, 0, 16, 48, 0, 4, 5, 10);
+    } else {
+        type = FF_JPEGXL_CT_ALPHA;
+    }
+
+    /* skip over the name */
+    jxl_bits_skip(8 * name_len);
+
+    if (!all_default && type == FF_JPEGXL_CT_ALPHA)
+        jxl_bits_skip(1);
+
+    if (type == FF_JPEGXL_CT_SPOT_COLOR)
+        jxl_bits_skip(16 * 4);
+
+    if (type == FF_JPEGXL_CT_CFA)
+        jxl_u32(1, 0, 3, 19, 0, 2, 4, 8);
+
+    return 0;
+}
+
+/* verify that a codestream header is valid */
+int ff_jpegxl_verify_codestream_header(const uint8_t *buf, int buflen)
+{
+    GetBitContext gbi, *gb = &gbi;
+    int all_default, extra_fields = 0;
+    int xyb_encoded = 1, have_icc_profile = 0;
+    uint32_t num_extra_channels;
+    uint64_t extensions;
+
+    init_get_bits8(gb, buf, buflen);
+
+    if (jxl_bits(16) != FF_JPEGXL_CODESTREAM_SIGNATURE_LE)
+        return -1;
+
+    if (jpegxl_read_size_header(gb) < 0)
+        return -1;
+
+    all_default = jxl_bits(1);
+    if (!all_default)
+        extra_fields = jxl_bits(1);
+
+    if (extra_fields) {
+        jxl_bits_skip(3); /* orientation */
+
+        /*
+         * intrinstic size
+         * any size header here is valid, but as it
+         * is variable length we have to read it
+         */
+        if (jxl_bits(1))
+            jpegxl_read_size_header(gb);
+
+        /* preview header */
+        if (jxl_bits(1)) {
+            if (jpegxl_read_preview_header(gb) < 0)
+                return -1;
+        }
+
+        /* animation header */
+        if (jxl_bits(1)) {
+            jxl_u32(100, 1000, 1, 1, 0, 0, 10, 30);
+            jxl_u32(1, 1001, 1, 1, 0, 0, 8, 10);
+            jxl_u32(0, 0, 0, 0, 0, 3, 16, 32);
+            jxl_bits_skip(1);
+        }
+    }
+
+    if (!all_default) {
+        jpegxl_skip_bit_depth(gb);
+
+        /* modular_16bit_buffers must equal 1 */
+        if (!jxl_bits(1))
+            return -1;
+
+        num_extra_channels = jxl_u32(0, 1, 2, 1, 0, 0, 4, 12);
+        if (num_extra_channels > 4)
+            return -1;
+        for (uint32_t i = 0; i < num_extra_channels; i++) {
+            if (jpegxl_read_extra_channel_info(gb) < 0)
+                return -1;
+        }
+
+        xyb_encoded = jxl_bits(1);
+
+        /* color encoding bundle */
+        if (!jxl_bits(1)) {
+            uint32_t color_space;
+            have_icc_profile = jxl_bits(1);
+            color_space = jxl_enum();
+            if (color_space > 63)
+                return -1;
+
+            if (!have_icc_profile) {
+                if (color_space != FF_JPEGXL_CS_XYB) {
+                    uint32_t white_point = jxl_enum();
+                    if (white_point > 63)
+                        return -1;
+                    if (white_point == FF_JPEGXL_WP_CUSTOM) {
+                        /* ux and uy values */
+                        jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
+                        jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
+                    }
+                    if (color_space != FF_JPEGXL_CS_GRAY) {
+                        /* primaries */
+                        uint32_t primaries = jxl_enum();
+                        if (primaries > 63)
+                            return -1;
+                        if (primaries == FF_JPEGXL_PR_CUSTOM) {
+                            /* ux/uy values for r,g,b */
+                            for (int i = 0; i < 6; i++)
+                                jxl_u32(0, 524288, 1048576, 2097152, 19, 19, 20, 21);
+                        }
+                    }
+                }
+
+                /* transfer characteristics */
+                if (jxl_bits(1)) {
+                    /* gamma */
+                    jxl_bits_skip(24);
+                } else {
+                    /* transfer function */
+                    if (jxl_enum() > 63)
+                        return -1;
+                }
+
+                /* rendering intent */
+                if (jxl_enum() > 63)
+                    return -1;
+            }
+        }
+
+        /* tone mapping bundle */
+        if (extra_fields && !jxl_bits(1))
+            jxl_bits_skip(16 + 16 + 1 + 16);
+
+        extensions = jxl_u64();
+        if (extensions) {
+            for (int i = 0; i < 64; i++) {
+                if (extensions & (UINT64_C(1) << i))
+                    jxl_u64();
+            }
+        }
+    }
+
+    /* default transform */
+    if (!jxl_bits(1)) {
+        /* opsin inverse matrix */
+        if (xyb_encoded && !jxl_bits(1))
+            jxl_bits_skip(16 * 16);
+        /* cw_mask and default weights */
+        if (jxl_bits(1))
+            jxl_bits_skip(16 * 15);
+        if (jxl_bits(1))
+            jxl_bits_skip(16 * 55);
+        if (jxl_bits(1))
+            jxl_bits_skip(16 * 210);
+    }
+
+    if (!have_icc_profile) {
+        int bits_remaining = 7 - (get_bits_count(gb) - 1) % 8;
+        if (bits_remaining && jxl_bits(bits_remaining))
+            return -1;
+    }
+
+    if (get_bits_left(gb) < 0)
+        return -1;
+
+    return 0;
+}
diff --git a/libavformat/jpegxl_probe.h b/libavformat/jpegxl_probe.h
new file mode 100644
index 0000000000..2960e81e11
--- /dev/null
+++ b/libavformat/jpegxl_probe.h
@@ -0,0 +1,32 @@ 
+/*
+ * Jpeg XL header verification
+ * Copyright (c) 2022 Leo Izen <leo.izen@gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVFORMAT_JPEGXL_PROBE_H
+#define AVFORMAT_JPEGXL_PROBE_H
+
+#include <stdint.h>
+
+#define FF_JPEGXL_CODESTREAM_SIGNATURE_LE 0x0aff
+#define FF_JPEGXL_CONTAINER_SIGNATURE_LE 0x204c584a0c000000
+
+int ff_jpegxl_verify_codestream_header(const uint8_t *buf, int buflen);
+
+#endif /* AVFORMAT_JPEGXL_PROBE_H */
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 6c847de164..c4b8873b0a 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -7697,6 +7697,7 @@  static int mov_probe(const AVProbeData *p)
             if (tag == MKTAG('f','t','y','p') &&
                        (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
                         || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
+                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','x','l',' ')
                     )) {
                 score = FFMAX(score, 5);
             } else {