Coverage Report

Created: 2026-04-01 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ffmpeg/libavformat/jpegxl_anim_dec.c
Line
Count
Source
1
/*
2
 * Animated JPEG XL Demuxer
3
 * Copyright (c) 2023 Leo Izen (thebombzen)
4
 *
5
 * This file is part of FFmpeg.
6
 *
7
 * FFmpeg is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * FFmpeg is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with FFmpeg; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20
 */
21
22
/**
23
 * @file
24
 * Animated JPEG XL Demuxer
25
 * @see ISO/IEC 18181-1 and 18181-2
26
 */
27
28
#include <stdint.h>
29
#include <string.h>
30
31
#include "libavcodec/jpegxl.h"
32
#include "libavcodec/jpegxl_parse.h"
33
#include "libavutil/intreadwrite.h"
34
#include "libavutil/opt.h"
35
36
#include "avformat.h"
37
#include "demux.h"
38
#include "internal.h"
39
40
typedef struct JXLAnimDemuxContext {
41
    AVBufferRef *initial;
42
} JXLAnimDemuxContext;
43
44
static int jpegxl_anim_probe(const AVProbeData *p)
45
959k
{
46
959k
    uint8_t buffer[4096 + AV_INPUT_BUFFER_PADDING_SIZE] = {0};
47
959k
    int copied = 0, ret;
48
959k
    FFJXLMetadata meta = { 0 };
49
50
    /* this is a raw codestream */
51
959k
    if (AV_RL16(p->buf) == FF_JPEGXL_CODESTREAM_SIGNATURE_LE) {
52
48.1k
        ret = ff_jpegxl_parse_codestream_header(p->buf, p->buf_size, &meta, 5);
53
48.1k
        if (ret >= 0 && meta.animation_offset > 0)
54
1.79k
            return AVPROBE_SCORE_MAX;
55
56
46.3k
        return 0;
57
48.1k
    }
58
59
    /* not a JPEG XL file at all */
60
911k
    if (AV_RL64(p->buf) != FF_JPEGXL_CONTAINER_SIGNATURE_LE)
61
905k
        return 0;
62
63
5.64k
    if (ff_jpegxl_collect_codestream_header(p->buf, p->buf_size, buffer,
64
5.64k
            sizeof(buffer) - AV_INPUT_BUFFER_PADDING_SIZE, &copied) <= 0
65
4.18k
            || copied <= 0)
66
4.22k
        return 0;
67
68
1.42k
    ret = ff_jpegxl_parse_codestream_header(buffer, copied, &meta, 10);
69
1.42k
    if (ret >= 0 && meta.animation_offset > 0)
70
39
        return AVPROBE_SCORE_MAX;
71
72
1.38k
    return 0;
73
1.42k
}
74
75
static int jpegxl_anim_read_header(AVFormatContext *s)
76
3.24k
{
77
3.24k
    JXLAnimDemuxContext *ctx = s->priv_data;
78
3.24k
    AVIOContext *pb = s->pb;
79
3.24k
    AVStream *st;
80
3.24k
    uint8_t head[256 + AV_INPUT_BUFFER_PADDING_SIZE];
81
3.24k
    const int sizeofhead = sizeof(head) - AV_INPUT_BUFFER_PADDING_SIZE;
82
3.24k
    int headsize = 0, ret;
83
3.24k
    FFJXLMetadata meta = { 0 };
84
85
3.24k
    uint64_t sig16 = avio_rl16(pb);
86
3.24k
    if (sig16 == FF_JPEGXL_CODESTREAM_SIGNATURE_LE) {
87
2.32k
        AV_WL16(head, sig16);
88
2.32k
        headsize = avio_read(s->pb, head + 2, sizeofhead - 2);
89
2.32k
        if (headsize < 0)
90
10
            return headsize;
91
2.31k
        headsize += 2;
92
2.31k
        ctx->initial = av_buffer_alloc(headsize);
93
2.31k
        if (!ctx->initial)
94
0
            return AVERROR(ENOMEM);
95
2.31k
        memcpy(ctx->initial->data, head, headsize);
96
2.31k
    } else {
97
924
        uint64_t sig64 = avio_rl64(pb);
98
924
        sig64 = (sig64 << 16) | sig16;
99
924
        if (sig64 != FF_JPEGXL_CONTAINER_SIGNATURE_LE)
100
236
            return AVERROR_INVALIDDATA;
101
688
        avio_skip(pb, 2); // first box always 12 bytes
102
5.81k
        while (1) {
103
5.81k
            int copied = 0;
104
5.81k
            uint8_t buf[4096];
105
5.81k
            int read = avio_read(pb, buf, sizeof(buf));
106
5.81k
            if (read < 0)
107
16
                return read;
108
5.79k
            if (!ctx->initial) {
109
681
                ctx->initial = av_buffer_alloc(read + 12);
110
681
                if (!ctx->initial)
111
0
                    return AVERROR(ENOMEM);
112
681
                AV_WL64(ctx->initial->data, FF_JPEGXL_CONTAINER_SIGNATURE_LE);
113
681
                AV_WL32(ctx->initial->data + 8, 0x0a870a0d);
114
5.11k
            } else {
115
                /* this only should be happening zero or one times in practice */
116
5.11k
                if (av_buffer_realloc(&ctx->initial, ctx->initial->size + read) < 0)
117
0
                    return AVERROR(ENOMEM);
118
5.11k
            }
119
5.79k
            ff_jpegxl_collect_codestream_header(buf, read, head + headsize, sizeofhead - headsize, &copied);
120
5.79k
            memcpy(ctx->initial->data + (ctx->initial->size - read), buf, read);
121
5.79k
            headsize += copied;
122
5.79k
            if (headsize >= sizeofhead || read < sizeof(buf))
123
672
                break;
124
5.79k
        }
125
688
    }
126
127
2.98k
    memset(head + headsize, 0, AV_INPUT_BUFFER_PADDING_SIZE);
128
129
    /* offset in bits of the animation header */
130
2.98k
    ret = ff_jpegxl_parse_codestream_header(head, headsize, &meta, 0);
131
2.98k
    if (ret < 0 || meta.animation_offset <= 0)
132
2.13k
        return AVERROR_INVALIDDATA;
133
134
852
    st = avformat_new_stream(s, NULL);
135
852
    if (!st)
136
0
        return AVERROR(ENOMEM);
137
138
852
    st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
139
852
    st->codecpar->codec_id   = AV_CODEC_ID_JPEGXL_ANIM;
140
852
    avpriv_set_pts_info(st, 1, meta.timebase.num, meta.timebase.den);
141
852
    ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL;
142
143
852
    return 0;
144
852
}
145
146
/* the decoder requires the full input file as a single packet */
147
static int jpegxl_anim_read_packet(AVFormatContext *s, AVPacket *pkt)
148
3.31k
{
149
3.31k
    JXLAnimDemuxContext *ctx = s->priv_data;
150
3.31k
    AVIOContext *pb = s->pb;
151
3.31k
    int ret;
152
3.31k
    int64_t size;
153
3.31k
    size_t offset = 0;
154
155
3.31k
    size = avio_size(pb);
156
3.31k
    if (size < 0)
157
357
        return size;
158
2.95k
    if (size > INT_MAX)
159
107
        return AVERROR(EDOM);
160
2.84k
    if (size == 0)
161
0
        size = 4096;
162
163
2.84k
    if (ctx->initial && size < ctx->initial->size)
164
0
        size = ctx->initial->size;
165
166
2.84k
    ret = av_new_packet(pkt, size);
167
2.84k
    if (ret < 0)
168
11
        return ret;
169
170
2.83k
    if (ctx->initial) {
171
603
        offset = ctx->initial->size;
172
603
        memcpy(pkt->data, ctx->initial->data, offset);
173
603
        av_buffer_unref(&ctx->initial);
174
603
    }
175
176
2.83k
    pkt->pos = avio_tell(pb) - offset;
177
178
2.83k
    ret = avio_read(pb, pkt->data + offset, size - offset);
179
2.83k
    if (ret < 0)
180
962
        return ret;
181
1.87k
    if (ret < size - offset)
182
419
        pkt->size = ret + offset;
183
184
1.87k
    return 0;
185
2.83k
}
186
187
static int jpegxl_anim_close(AVFormatContext *s)
188
3.24k
{
189
3.24k
    JXLAnimDemuxContext *ctx = s->priv_data;
190
3.24k
    if (ctx->initial)
191
2.39k
        av_buffer_unref(&ctx->initial);
192
193
3.24k
    return 0;
194
3.24k
}
195
196
const FFInputFormat ff_jpegxl_anim_demuxer = {
197
    .p.name         = "jpegxl_anim",
198
    .p.long_name    = NULL_IF_CONFIG_SMALL("Animated JPEG XL"),
199
    .p.flags        = AVFMT_GENERIC_INDEX | AVFMT_NOTIMESTAMPS,
200
    .p.mime_type    = "image/jxl",
201
    .p.extensions   = "jxl",
202
    .priv_data_size = sizeof(JXLAnimDemuxContext),
203
    .read_probe     = jpegxl_anim_probe,
204
    .read_header    = jpegxl_anim_read_header,
205
    .read_packet    = jpegxl_anim_read_packet,
206
    .read_close     = jpegxl_anim_close,
207
    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
208
};