/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 | } |