Coverage Report

Created: 2026-05-30 08:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/image.c
Line
Count
Source
1
/*****************************************************************************
2
 * image.c: Image demuxer
3
 *****************************************************************************
4
 * Copyright (C) 2010 Laurent Aimar
5
 *
6
 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
7
 *
8
 * This program is free software; you can redistribute it and/or modify it
9
 * under the terms of the GNU Lesser General Public License as published by
10
 * the Free Software Foundation; either version 2.1 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public License
19
 * along with this program; if not, write to the Free Software Foundation,
20
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21
 *****************************************************************************/
22
23
/*****************************************************************************
24
 * Preamble
25
 *****************************************************************************/
26
27
#ifdef HAVE_CONFIG_H
28
# include "config.h"
29
#endif
30
31
#include <assert.h>
32
33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
35
#include <vlc_demux.h>
36
#include <vlc_image.h>
37
#include <vlc_ancillary.h>
38
#include "mxpeg_helper.h"
39
40
/*****************************************************************************
41
 * Module descriptor
42
 *****************************************************************************/
43
static int  Open (vlc_object_t *);
44
static void Close(vlc_object_t *);
45
46
#define ID_TEXT N_("ES ID")
47
#define ID_LONGTEXT N_( \
48
    "Set the ID of the elementary stream")
49
50
#define GROUP_TEXT N_("Group")
51
#define GROUP_LONGTEXT N_(\
52
    "Set the group of the elementary stream")
53
54
#define DECODE_TEXT N_("Decode")
55
#define DECODE_LONGTEXT N_( \
56
    "Decode at the demuxer stage")
57
58
#define CHROMA_TEXT N_("Forced chroma")
59
#define CHROMA_LONGTEXT N_( \
60
    "If non empty and image-decode is true, the image will be " \
61
    "converted to the specified chroma.")
62
63
#define DURATION_TEXT N_("Duration in seconds")
64
#define DURATION_LONGTEXT N_( \
65
    "Duration in seconds before simulating an end of file. " \
66
    "A negative value means an unlimited play time.")
67
68
#define FPS_TEXT N_("Frame rate")
69
#define FPS_LONGTEXT N_( \
70
    "Frame rate of the elementary stream produced.")
71
72
#define RT_TEXT N_("Real-time")
73
#define RT_LONGTEXT N_( \
74
    "Use real-time mode suitable for being used as a master input and " \
75
    "real-time input slaves.")
76
77
150
vlc_module_begin()
78
75
    set_description(N_("Image demuxer"))
79
75
    set_shortname(N_("Image"))
80
75
    set_subcategory(SUBCAT_INPUT_DEMUX)
81
75
    add_integer("image-id", -1, ID_TEXT, ID_LONGTEXT)
82
75
        change_safe()
83
75
    add_integer("image-group", 0, GROUP_TEXT, GROUP_LONGTEXT)
84
75
        change_safe()
85
75
    add_bool("image-decode", true, DECODE_TEXT, DECODE_LONGTEXT)
86
75
        change_safe()
87
75
    add_string("image-chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT)
88
75
        change_safe()
89
75
    add_float("image-duration", 10, DURATION_TEXT, DURATION_LONGTEXT)
90
75
        change_safe()
91
75
    add_string("image-fps", "10/1", FPS_TEXT, FPS_LONGTEXT)
92
75
        change_safe()
93
75
    add_bool("image-realtime", false, RT_TEXT, RT_LONGTEXT)
94
75
        change_safe()
95
75
    set_capability("demux", 10)
96
150
    set_callbacks(Open, Close)
97
75
vlc_module_end()
98
99
/*****************************************************************************
100
 * Local prototypes
101
 *****************************************************************************/
102
typedef struct
103
{
104
    block_t     *data;
105
    es_out_id_t *es;
106
    vlc_tick_t  duration;
107
    bool        is_realtime;
108
    vlc_tick_t  pts_offset;
109
    vlc_tick_t  pts_next;
110
    date_t        pts;
111
} demux_sys_t;
112
113
static block_t *Load(demux_t *demux)
114
5.21k
{
115
5.21k
    const unsigned max_size = 4096 * 4096 * 8;
116
5.21k
    uint64_t size;
117
118
5.21k
    if (vlc_stream_GetSize(demux->s, &size) == VLC_SUCCESS) {
119
5.21k
        if (size > max_size) {
120
0
            msg_Err(demux, "image too large (%"PRIu64" > %u), rejected",
121
0
                    size, max_size);
122
0
            return NULL;
123
0
        }
124
5.21k
    } else
125
0
        size = max_size;
126
127
5.21k
    block_t *block = block_Alloc(size);
128
5.21k
    if (block == NULL)
129
0
        return NULL;
130
131
5.21k
    ssize_t val = vlc_stream_Read(demux->s, block->p_buffer, size);
132
5.21k
    if (val < 0) {
133
0
        block_Release(block);
134
0
        return NULL;
135
0
    }
136
137
5.21k
    block->i_buffer = val;
138
5.21k
    return block;
139
5.21k
}
140
141
static block_t *Decode(demux_t *demux,
142
                       es_format_t *fmt, vlc_fourcc_t chroma, block_t *data)
143
5.21k
{
144
5.21k
    image_handler_t *handler = image_HandlerCreate(demux);
145
5.21k
    if (!handler) {
146
0
        block_Release(data);
147
0
        return NULL;
148
0
    }
149
150
5.21k
    video_format_t decoded;
151
5.21k
    video_format_Init(&decoded, chroma);
152
153
5.21k
    picture_t *image = image_Read(handler, data, fmt, &decoded);
154
5.21k
    image_HandlerDelete(handler);
155
156
5.21k
    if (!image)
157
4.92k
        return NULL;
158
159
284
    es_format_Clean(fmt);
160
284
    es_format_InitFromVideo(fmt, &decoded);
161
284
    video_format_Clean(&decoded);
162
163
284
    size_t size = 0;
164
820
    for (int i = 0; i < image->i_planes; i++)
165
536
        size += image->p[i].i_pitch * image->p[i].i_lines;
166
167
284
    data = block_Alloc(size);
168
284
    if (!data) {
169
0
        picture_Release(image);
170
0
        return NULL;
171
0
    }
172
173
284
    size_t offset = 0;
174
820
    for (int i = 0; i < image->i_planes; i++) {
175
536
        const plane_t *src = &image->p[i];
176
387k
        for (int y = 0; y < src->i_visible_lines; y++) {
177
386k
            memcpy(&data->p_buffer[offset],
178
386k
                   &src->p_pixels[y * src->i_pitch],
179
386k
                   src->i_visible_pitch);
180
386k
            offset += src->i_visible_pitch;
181
386k
        }
182
536
    }
183
184
    // Preserve important metadata
185
284
    struct vlc_ancillary *ancillary;
186
284
    ancillary = picture_GetAncillary(image, VLC_ANCILLARY_ID_ICC);
187
284
    if (ancillary)
188
3
        vlc_frame_AttachAncillary(data, ancillary);
189
190
284
    picture_Release(image);
191
284
    return data;
192
284
}
193
194
static int Demux(demux_t *demux)
195
33.3k
{
196
33.3k
    demux_sys_t *sys = demux->p_sys;
197
198
33.3k
    if (!sys->data)
199
4.92k
        return VLC_DEMUXER_EOF;
200
201
28.4k
    vlc_tick_t deadline;
202
28.4k
    const vlc_tick_t pts_first = sys->pts_offset + date_Get(&sys->pts);
203
28.4k
    if (sys->pts_next != VLC_TICK_INVALID) {
204
0
        deadline = sys->pts_next;
205
28.4k
    } else if (sys->is_realtime) {
206
0
        deadline = vlc_tick_now();
207
0
        const vlc_tick_t max_wait = VLC_TICK_FROM_MS(20);
208
0
        if (deadline + max_wait < pts_first) {
209
0
            es_out_SetPCR(demux->out, deadline);
210
            /* That's ugly, but not yet easily fixable */
211
0
            vlc_tick_wait(deadline + max_wait);
212
0
            return VLC_DEMUXER_SUCCESS;
213
0
        }
214
28.4k
    } else {
215
28.4k
        deadline = 1 + pts_first;
216
28.4k
    }
217
218
56.8k
    for (;;) {
219
56.8k
        const vlc_tick_t pts = sys->pts_offset + date_Get(&sys->pts);
220
56.8k
        if (sys->duration >= 0 && pts >= VLC_TICK_0 + sys->pts_offset + sys->duration)
221
284
            return VLC_DEMUXER_EOF;
222
223
56.5k
        if (pts >= deadline)
224
28.1k
            return VLC_DEMUXER_SUCCESS;
225
226
28.4k
        block_t *data = block_Duplicate(sys->data);
227
28.4k
        if (!data)
228
0
            return VLC_DEMUXER_EGENERIC;
229
230
28.4k
        data->i_dts =
231
28.4k
        data->i_pts = VLC_TICK_0 + pts;
232
28.4k
        es_out_SetPCR(demux->out, data->i_pts);
233
28.4k
        if(sys->es)
234
28.4k
            es_out_Send(demux->out, sys->es, data);
235
0
        else
236
0
            block_Release(data);
237
238
28.4k
        date_Increment(&sys->pts, 1);
239
28.4k
    }
240
28.4k
}
241
242
static int Control(demux_t *demux, int query, va_list args)
243
0
{
244
0
    demux_sys_t *sys = demux->p_sys;
245
246
0
    switch (query) {
247
0
    case DEMUX_CAN_SEEK:
248
0
        *va_arg(args, bool *) = sys->duration >= 0 && !sys->is_realtime;
249
0
        return VLC_SUCCESS;
250
0
    case DEMUX_GET_POSITION: {
251
0
        double *position = va_arg(args, double *);
252
0
        if (sys->duration > 0)
253
0
            *position = date_Get(&sys->pts) / (double)sys->duration;
254
0
        else
255
0
            *position = 0;
256
0
        return VLC_SUCCESS;
257
0
    }
258
0
    case DEMUX_SET_POSITION: {
259
0
        if (sys->duration < 0 || sys->is_realtime)
260
0
            return VLC_EGENERIC;
261
0
        double position = va_arg(args, double);
262
0
        date_Set(&sys->pts, position * sys->duration);
263
0
        return VLC_SUCCESS;
264
0
    }
265
0
    case DEMUX_GET_TIME: {
266
0
        *va_arg(args, vlc_tick_t *) = sys->pts_offset + date_Get(&sys->pts);
267
0
        return VLC_SUCCESS;
268
0
    }
269
0
    case DEMUX_SET_TIME: {
270
0
        if (sys->duration < 0 || sys->is_realtime)
271
0
            return VLC_EGENERIC;
272
0
        vlc_tick_t time = va_arg(args, vlc_tick_t);
273
0
        date_Set(&sys->pts, VLC_CLIP(time - sys->pts_offset, VLC_TICK_0, sys->duration));
274
0
        return VLC_SUCCESS;
275
0
    }
276
0
    case DEMUX_SET_NEXT_DEMUX_TIME: {
277
0
        vlc_tick_t pts_next = VLC_TICK_0 + va_arg(args, vlc_tick_t);
278
0
        if (sys->pts_next == VLC_TICK_INVALID)
279
0
            sys->pts_offset = pts_next - VLC_TICK_0;
280
0
        sys->pts_next = pts_next;
281
0
        return VLC_SUCCESS;
282
0
    }
283
0
    case DEMUX_GET_LENGTH: {
284
0
        *va_arg(args, vlc_tick_t *) = __MAX(sys->duration, 0);
285
0
        return VLC_SUCCESS;
286
0
    }
287
0
    case DEMUX_GET_FPS: {
288
0
        double *fps = va_arg(args, double *);
289
0
        *fps = (double)sys->pts.i_divider_num / sys->pts.i_divider_den;
290
0
        return VLC_SUCCESS;
291
0
    }
292
0
    case DEMUX_GET_META:
293
0
    case DEMUX_HAS_UNSUPPORTED_META:
294
0
    case DEMUX_GET_ATTACHMENTS:
295
0
        return VLC_EGENERIC;
296
297
0
    case DEMUX_CAN_PAUSE:
298
0
    case DEMUX_SET_PAUSE_STATE:
299
0
    case DEMUX_CAN_CONTROL_PACE:
300
0
    case DEMUX_GET_PTS_DELAY:
301
0
        return demux_vaControlHelper( demux->s, 0, -1, 0, 1, query, args );
302
303
0
    default:
304
0
        return VLC_EGENERIC;
305
306
0
    }
307
0
}
308
309
static bool IsBmp(stream_t *s)
310
6.67k
{
311
6.67k
    const uint8_t *header;
312
6.67k
    if (vlc_stream_Peek(s, &header, 18) < 18)
313
619
        return false;
314
6.05k
    if (memcmp(header, "BM", 2) &&
315
6.04k
        memcmp(header, "BA", 2) &&
316
6.03k
        memcmp(header, "CI", 2) &&
317
6.01k
        memcmp(header, "CP", 2) &&
318
5.98k
        memcmp(header, "IC", 2) &&
319
5.95k
        memcmp(header, "PT", 2))
320
5.94k
        return false;
321
113
    uint32_t file_size   = GetDWLE(&header[2]);
322
113
    uint32_t data_offset = GetDWLE(&header[10]);
323
113
    uint32_t header_size = GetDWLE(&header[14]);
324
113
    if (file_size != 14 && file_size != 14 + header_size &&
325
108
        file_size <= data_offset)
326
26
        return false;
327
87
    if (data_offset < header_size + 14)
328
44
        return false;
329
43
    static const uint8_t header_sizes[] = { 12, 40, 56, 64, 108, 124 };
330
43
    return memchr(header_sizes, header_size, ARRAY_SIZE(header_sizes)) != NULL;
331
87
}
332
333
static bool IsPcx(stream_t *s)
334
6.66k
{
335
6.66k
    const uint8_t *header;
336
6.66k
    if (vlc_stream_Peek(s, &header, 66) < 66)
337
3.69k
        return false;
338
2.97k
    if (header[0] != 0x0A ||                        /* marker */
339
173
        (header[1] != 0x00 && header[1] != 0x02 &&
340
30
         header[1] != 0x03 && header[1] != 0x05) || /* version */
341
165
        (header[2] != 0 && header[2] != 1) ||       /* encoding */
342
138
        (header[3] != 1 && header[3] != 2 &&
343
52
         header[3] != 4 && header[3] != 8) ||       /* bits per pixel per plane */
344
130
        header[64] != 0 ||                          /* reserved */
345
127
        header[65] == 0 || header[65] > 4)          /* plane count */
346
2.85k
        return false;
347
123
    if (GetWLE(&header[4]) > GetWLE(&header[8]) ||  /* xmin vs xmax */
348
108
        GetWLE(&header[6]) > GetWLE(&header[10]))   /* ymin vs ymax */
349
32
        return false;
350
91
    return true;
351
123
}
352
353
static bool IsLbm(stream_t *s)
354
6.57k
{
355
6.57k
    const uint8_t *header;
356
6.57k
    if (vlc_stream_Peek(s, &header, 12) < 12)
357
290
        return false;
358
6.28k
    if (memcmp(&header[0], "FORM", 4) ||
359
57
        GetDWBE(&header[4]) <= 4 ||
360
53
        (memcmp(&header[8], "ILBM", 4) && memcmp(&header[8], "PBM ", 4)))
361
6.28k
        return false;
362
5
    return true;
363
6.28k
}
364
static bool IsPnmBlank(uint8_t v)
365
1.21k
{
366
1.21k
    return v == ' ' || v == '\t' || v == '\r' || v == '\n';
367
1.21k
}
368
static bool IsPnm(stream_t *s)
369
6.57k
{
370
6.57k
    const uint8_t *header;
371
6.57k
    int size = vlc_stream_Peek(s, &header, 256);
372
6.57k
    if (size < 3)
373
33
        return false;
374
6.53k
    if (header[0] != 'P' ||
375
169
        header[1] < '1' || header[1] > '6' ||
376
138
        !IsPnmBlank(header[2]))
377
6.42k
        return false;
378
379
119
    int number_count = 0;
380
1.17k
    for (int i = 3, parsing_number = 0; i < size && number_count < 2; i++) {
381
1.07k
        if (IsPnmBlank(header[i])) {
382
722
            if (parsing_number) {
383
112
                parsing_number = 0;
384
112
                number_count++;
385
112
            }
386
722
        } else {
387
356
            if (header[i] < '0' || header[i] > '9')
388
21
                break;
389
335
            parsing_number = 1;
390
335
        }
391
1.07k
    }
392
119
    if (number_count < 2)
393
65
        return false;
394
54
    return true;
395
119
}
396
397
static uint8_t FindJpegMarker(size_t *position, const uint8_t *data, size_t size)
398
4.09k
{
399
27.9k
    for (size_t i = *position; i + 1 < size; i++) {
400
27.8k
        if (data[i + 0] != 0xff || data[i + 1] == 0x00)
401
3.08k
            return 0xff;
402
24.8k
        if (data[i + 1] != 0xff) {
403
975
            *position = i + 2;
404
975
            return data[i + 1];
405
975
        }
406
24.8k
    }
407
31
    return 0xff;
408
4.09k
}
409
static bool IsJfif(stream_t *s)
410
6.51k
{
411
6.51k
    const uint8_t *header;
412
6.51k
    ssize_t peek = vlc_stream_Peek(s, &header, 4096);
413
6.51k
    if(peek < 256)
414
4.84k
        return false;
415
1.66k
    size_t size = (size_t) peek;
416
1.66k
    size_t position = 0;
417
418
1.66k
    if (FindJpegMarker(&position, header, size) != 0xd8) // SOI
419
1.54k
        return false;
420
421
645
    while (1) {
422
645
        uint8_t marker = FindJpegMarker(&position, header, size);
423
645
        switch (marker) {
424
537
            case 0xe2: { // ICC Profile
425
537
                size_t icc_size = GetWBE(&header[position]);
426
537
                position += 2;
427
537
                if (position + 12 > size)
428
8
                    return false;
429
529
                if (memcmp(&header[position], "ICC_PROFILE\0", 12))
430
7
                    return false;
431
522
                position += icc_size - 2;
432
522
                break;
433
529
            }
434
26
            case 0xe0: { // APP0
435
26
                position += 2;  /* Skip size */
436
26
                if (position + 5 > size)
437
5
                    return false;
438
21
                return (memcmp(&header[position], "JFIF\0", 5) == 0);
439
26
            }
440
82
            default:
441
82
                return false;
442
645
        }
443
645
    }
444
123
}
445
446
static bool IsWebP(stream_t *s)
447
6.50k
{
448
6.50k
    const uint8_t *header;
449
6.50k
    if (vlc_stream_Peek(s, &header, 20) < 20) /* WebP header size */
450
797
        return false;
451
5.71k
    if (memcmp(&header[0], "RIFF", 4))
452
960
        return false;
453
    /* TODO: support other chunk types */
454
4.75k
    if (memcmp(&header[8], "WEBPVP8 ", 8))
455
376
        return false;
456
    /* skip headers */
457
4.37k
    return vlc_stream_Seek(s, 20) == 0;
458
4.75k
}
459
460
static bool IsSpiff(stream_t *s)
461
6.51k
{
462
6.51k
    const uint8_t *header;
463
6.51k
    if (vlc_stream_Peek(s, &header, 36) < 36) /* SPIFF header size */
464
1.30k
        return false;
465
5.21k
    if (header[0] != 0xff || header[1] != 0xd8 ||
466
105
        header[2] != 0xff || header[3] != 0xe8)
467
5.20k
        return false;
468
10
    if (memcmp(&header[6], "SPIFF\0", 6))
469
9
        return false;
470
1
    return true;
471
10
}
472
473
67
#define EXIF_STRING "Exif" /* includes \0 */
474
52
#define EXIF_XMP_STRING "http://ns.adobe.com/xap/1.0/" /* includes \0 */
475
static bool IsExifXMP(stream_t *s)
476
6.51k
{
477
6.51k
    const uint8_t *header;
478
6.51k
    ssize_t peek = vlc_stream_Peek(s, &header, 256);
479
6.51k
    if (peek < 256)
480
4.84k
        return false;
481
1.66k
    size_t size = (size_t) peek;
482
1.66k
    size_t position = 0;
483
484
1.66k
    if (FindJpegMarker(&position, header, size) != 0xd8)
485
1.54k
        return false;
486
118
    if (FindJpegMarker(&position, header, size) != 0xe1)
487
95
        return false;
488
23
    position += 2;  /* Skip size */
489
23
    if (position + sizeof(EXIF_STRING) <= size &&
490
22
        !memcmp(&header[position], EXIF_STRING, sizeof(EXIF_STRING)))
491
3
        return true;
492
20
    if (position + sizeof(EXIF_XMP_STRING) <= size &&
493
16
        !memcmp(&header[position], EXIF_XMP_STRING, sizeof(EXIF_XMP_STRING)))
494
1
        return true;
495
19
    return false;
496
20
}
497
498
static bool FindSVGmarker(int *position, const uint8_t *data, const int size, const char *marker)
499
0
{
500
0
    for( int i = *position; i < size; i++)
501
0
    {
502
0
        if (memcmp(&data[i], marker, strlen(marker)) == 0)
503
0
        {
504
0
            *position = i;
505
0
            return true;
506
0
        }
507
0
    }
508
0
    return false;
509
0
}
510
511
static bool IsSVG(stream_t *s)
512
1.89k
{
513
1.89k
    if (s->psz_url == NULL)
514
1.89k
        return false;
515
516
0
    char *ext = strstr(s->psz_url, ".svg");
517
0
    if (!ext) return false;
518
519
0
    const uint8_t *header;
520
0
    ssize_t size = vlc_stream_Peek(s, &header, 4096);
521
0
    if (size == -1)
522
0
        return false;
523
0
    int position = 0;
524
525
0
    const char xml[] = "<?xml version=\"";
526
0
    if (!FindSVGmarker(&position, header, size, xml))
527
0
        return false;
528
0
    if (position != 0)
529
0
        return false;
530
531
0
    const char endxml[] = ">\0";
532
0
    if (!FindSVGmarker(&position, header, size, endxml))
533
0
        return false;
534
0
    if (position <= 15)
535
0
        return false;
536
537
0
    const char svg[] = "<svg";
538
0
    if (!FindSVGmarker(&position, header, size, svg))
539
0
        return false;
540
0
    if (position < 19)
541
0
        return false;
542
543
    /* SVG Scalable Vector Graphics image */
544
545
    /* NOTE: some SVG images have the mimetype set in a meta data section
546
     * and some do not */
547
0
    return true;
548
0
}
549
550
static bool IsTarga(stream_t *s)
551
1.89k
{
552
    /* The header is not enough to ensure proper detection, we need
553
     * to have a look at the footer. But doing so can be slow. So
554
     * try to avoid it when possible */
555
1.89k
    const uint8_t *header;
556
1.89k
    if (vlc_stream_Peek(s, &header, 18) < 18)   /* Targa fixed header */
557
404
        return false;
558
1.49k
    if (header[1] > 1)                      /* Color Map Type */
559
1.20k
        return false;
560
290
    if ((header[1] != 0 || header[3 + 4] != 0) &&
561
161
        header[3 + 4] != 8  &&
562
152
        header[3 + 4] != 15 && header[3 + 4] != 16 &&
563
146
        header[3 + 4] != 24 && header[3 + 4] != 32)
564
121
        return false;
565
169
    if ((header[2] > 3 && header[2] < 9) || header[2] > 11) /* Image Type */
566
15
        return false;
567
154
    if (GetWLE(&header[8 + 4]) <= 0 ||      /* Width */
568
149
        GetWLE(&header[8 + 6]) <= 0)        /* Height */
569
7
        return false;
570
147
    if (header[8 + 8] != 8  &&
571
116
        header[8 + 8] != 15 && header[8 + 8] != 16 &&
572
81
        header[8 + 8] != 24 && header[8 + 8] != 32)
573
36
        return false;
574
111
    if (header[8 + 9] & 0xc0)               /* Reserved bits */
575
11
        return false;
576
577
100
    const int64_t size = stream_Size(s);
578
100
    if (size <= 18 + 26)
579
6
        return false;
580
100
    bool can_seek;
581
94
    if (vlc_stream_Control(s, STREAM_CAN_SEEK, &can_seek) || !can_seek)
582
0
        return false;
583
584
94
    const int64_t position = vlc_stream_Tell(s);
585
94
    if (vlc_stream_Seek(s, size - 26))
586
0
        return false;
587
588
94
    const uint8_t *footer;
589
94
    if (vlc_stream_Peek(s, &footer, 26) < 26
590
94
     || memcmp(&footer[8], "TRUEVISION-XFILE.\x00", 18))
591
74
        return false;
592
593
20
    return vlc_stream_Seek(s, position) == 0;
594
94
}
595
596
typedef struct {
597
    vlc_fourcc_t  codec;
598
    size_t        marker_size;
599
    const uint8_t marker[14];
600
    bool          (*detect)(stream_t *s);
601
} image_format_t;
602
603
#define VLC_CODEC_XCF VLC_FOURCC('X', 'C', 'F', ' ')
604
#define VLC_CODEC_LBM VLC_FOURCC('L', 'B', 'M', ' ')
605
static const image_format_t formats[] = {
606
    { .codec = VLC_CODEC_XCF,
607
      .marker_size = 9 + 4 + 1,
608
      .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
609
                  'f', 'i', 'l', 'e', '\0' }
610
    },
611
    { .codec = VLC_CODEC_XCF,
612
      .marker_size = 9 + 4 + 1,
613
      .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
614
                  'v', '0', '0', '1', '\0' }
615
    },
616
    { .codec = VLC_CODEC_XCF,
617
      .marker_size = 9 + 4 + 1,
618
      .marker = { 'g', 'i', 'm', 'p', ' ', 'x', 'c', 'f', ' ',
619
                  'v', '0', '0', '2', '\0' }
620
    },
621
    { .codec = VLC_CODEC_PNG,
622
      .marker_size = 8,
623
      .marker = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }
624
    },
625
    { .codec = VLC_CODEC_GIF,
626
      .marker_size = 6,
627
      .marker = { 'G', 'I', 'F', '8', '7', 'a' }
628
    },
629
    { .codec = VLC_CODEC_GIF,
630
      .marker_size = 6,
631
      .marker = { 'G', 'I', 'F', '8', '9', 'a' }
632
    },
633
    /* XXX TIFF detection may be a bit weak */
634
    { .codec = VLC_CODEC_TIFF,
635
      .marker_size = 4,
636
      .marker = { 'I', 'I', 0x2a, 0x00 },
637
    },
638
    { .codec = VLC_CODEC_TIFF,
639
      .marker_size = 4,
640
      .marker = { 'M', 'M', 0x00, 0x2a },
641
    },
642
    { .codec = VLC_CODEC_BMP,
643
      .detect = IsBmp,
644
    },
645
    { .codec = VLC_CODEC_PCX,
646
      .detect = IsPcx,
647
    },
648
    { .codec = VLC_CODEC_LBM,
649
      .detect = IsLbm,
650
    },
651
    { .codec = VLC_CODEC_PNM,
652
      .detect = IsPnm,
653
    },
654
    { .codec = VLC_CODEC_MXPEG,
655
      .detect = IsMxpeg,
656
    },
657
    { .codec = VLC_CODEC_JPEG,
658
      .detect = IsJfif,
659
    },
660
    { .codec = VLC_CODEC_JPEG,
661
      .detect = IsSpiff,
662
    },
663
    { .codec = VLC_CODEC_JPEG,
664
      .detect = IsExifXMP,
665
    },
666
    { .codec = VLC_CODEC_WEBP,
667
      .detect = IsWebP,
668
    },
669
    { .codec = VLC_CODEC_FARBFELD,
670
      .marker_size = 8,
671
      .marker = { 'f', 'a', 'r', 'b', 'f', 'e', 'l', 'd' },
672
    },
673
    { .codec = VLC_CODEC_BPG,
674
      .marker_size = 4,
675
      .marker = { 'B', 'P', 'G', 0xFB },
676
    },
677
    { .codec = VLC_CODEC_SVG,
678
      .detect = IsSVG,
679
    },
680
    { .codec = VLC_CODEC_TARGA,
681
      .detect = IsTarga,
682
    },
683
};
684
685
static vlc_fourcc_t Detect(stream_t *s)
686
7.10k
{
687
7.10k
    const uint8_t *peek;
688
7.10k
    size_t peek_size = 0;
689
690
124k
    for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
691
122k
        const image_format_t *img = &formats[i];
692
693
122k
        if (img->detect != NULL) {
694
62.8k
            if (img->detect(s))
695
4.56k
                return img->codec;
696
697
58.2k
            if (vlc_stream_Seek(s, 0))
698
0
               return 0;
699
700
            /* Seeking invalidates the current peek buffer */
701
58.2k
            peek_size = 0;
702
58.2k
            continue;
703
58.2k
        }
704
705
59.8k
        if (peek_size < img->marker_size) {
706
10.9k
            ssize_t val = vlc_stream_Peek(s, &peek, img->marker_size);
707
10.9k
            if (val < 0)
708
0
                continue;
709
10.9k
            peek_size = val;
710
10.9k
        }
711
712
59.8k
        assert(img->marker_size > 0); /* ensure peek is a valid pointer */
713
714
59.8k
        if (peek_size >= img->marker_size
715
57.3k
         && memcmp(peek, img->marker, img->marker_size) == 0)
716
661
            return img->codec;
717
59.8k
    }
718
1.87k
    return 0;
719
7.10k
}
720
721
static int parse_farbfeld_header(vlc_object_t *object, es_format_t *fmt)
722
227
{
723
227
    demux_t *demux = (demux_t*)object;
724
725
227
    const uint8_t *p_peek;
726
227
    if (vlc_stream_Peek(demux->s, &p_peek, 16) < 16)
727
6
        return VLC_EGENERIC;
728
729
221
    uint32_t width = GetDWBE(p_peek + 8);
730
221
    uint32_t height = GetDWBE(p_peek + 12);
731
732
221
    if (width == 0 || height == 0)
733
2
        return VLC_EGENERIC;
734
735
    /* Farbfeld is essentially just a header followed by pixels. */
736
219
    fmt->video.i_width = width;
737
219
    fmt->video.i_height = height;
738
219
    fmt->video.i_chroma = fmt->i_codec = VLC_CODEC_RGBA64;
739
740
219
    vlc_stream_Read(demux->s, NULL, 16);
741
742
219
    return VLC_SUCCESS;
743
221
}
744
745
static int Open(vlc_object_t *object)
746
7.10k
{
747
7.10k
    demux_t *demux = (demux_t*)object;
748
749
    /* Detect the image type */
750
7.10k
    vlc_fourcc_t codec = Detect(demux->s);
751
7.10k
    if (codec == 0)
752
1.87k
        return VLC_EGENERIC;
753
754
5.22k
    msg_Dbg(demux, "Detected image: %s",
755
5.22k
            vlc_fourcc_GetDescription(VIDEO_ES, codec));
756
757
5.22k
    if (codec == VLC_CODEC_MXPEG)
758
2
        return VLC_EGENERIC; //let avformat demux this file
759
760
    /* Load and if selected decode */
761
5.21k
    es_format_t fmt;
762
5.21k
    es_format_Init(&fmt, VIDEO_ES, codec);
763
5.21k
    fmt.video.i_chroma = fmt.i_codec;
764
765
5.21k
    if (codec == VLC_CODEC_FARBFELD)
766
227
    {
767
227
        if (parse_farbfeld_header(object, &fmt))
768
8
            return VLC_EGENERIC;
769
227
    }
770
771
5.21k
    block_t *data = Load(demux);
772
5.21k
    if (data && var_InheritBool(demux, "image-decode")) {
773
5.21k
        char *string = var_InheritString(demux, "image-chroma");
774
5.21k
        vlc_fourcc_t chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, string);
775
5.21k
        free(string);
776
777
5.21k
        data = Decode(demux, &fmt, chroma, data);
778
5.21k
    }
779
5.21k
    fmt.i_id    = var_InheritInteger(demux, "image-id");
780
5.21k
    fmt.i_group = var_InheritInteger(demux, "image-group");
781
5.21k
    if (var_InheritURational(demux,
782
5.21k
                             &fmt.video.i_frame_rate,
783
5.21k
                             &fmt.video.i_frame_rate_base,
784
5.21k
                             "image-fps") ||
785
5.21k
        fmt.video.i_frame_rate <= 0 || fmt.video.i_frame_rate_base <= 0) {
786
0
        msg_Err(demux, "Invalid frame rate, using 10/1 instead");
787
0
        fmt.video.i_frame_rate      = 10;
788
0
        fmt.video.i_frame_rate_base = 1;
789
0
    }
790
791
    /* If loadind failed, we still continue to avoid mis-detection
792
     * by other demuxers. */
793
5.21k
    if (!data)
794
5.21k
        msg_Err(demux, "Failed to load the image");
795
796
    /* */
797
5.21k
    demux_sys_t *sys = malloc(sizeof(*sys));
798
5.21k
    if (!sys) {
799
0
        if (data)
800
0
            block_Release(data);
801
0
        es_format_Clean(&fmt);
802
0
        return VLC_ENOMEM;
803
0
    }
804
805
5.21k
    date_Init(&sys->pts, fmt.video.i_frame_rate, fmt.video.i_frame_rate_base);
806
5.21k
    date_Set(&sys->pts, VLC_TICK_0);
807
5.21k
    sys->es = es_out_Add(demux->out, &fmt);
808
5.21k
    es_format_Clean(&fmt);
809
5.21k
    if(unlikely(!sys->es))
810
0
    {
811
0
        if (data)
812
0
            block_Release(data);
813
0
        free(sys);
814
0
        return VLC_EGENERIC;
815
0
    }
816
5.21k
    sys->data        = data;
817
5.21k
    sys->duration    = vlc_tick_from_sec( var_InheritFloat(demux, "image-duration") );
818
5.21k
    sys->is_realtime = var_InheritBool(demux, "image-realtime");
819
5.21k
    sys->pts_offset  = sys->is_realtime ? vlc_tick_now() : 0;
820
5.21k
    sys->pts_next    = VLC_TICK_INVALID;
821
822
5.21k
    demux->pf_demux   = Demux;
823
5.21k
    demux->pf_control = Control;
824
5.21k
    demux->p_sys      = sys;
825
5.21k
    return VLC_SUCCESS;
826
5.21k
}
827
828
static void Close(vlc_object_t *object)
829
5.21k
{
830
5.21k
    demux_t     *demux = (demux_t*)object;
831
5.21k
    demux_sys_t *sys   = demux->p_sys;
832
833
5.21k
    if (sys->data)
834
284
        block_Release(sys->data);
835
5.21k
    free(sys);
836
5.21k
}