/src/vlc/modules/demux/xiph_metadata.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * xiph_metadata.h: Vorbis Comment parser |
3 | | ***************************************************************************** |
4 | | * Copyright © 2008-2013 VLC authors and VideoLAN |
5 | | * |
6 | | * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org> |
7 | | * Jean-Baptiste Kempf <jb@videolan.org> |
8 | | * |
9 | | * This program is free software; you can redistribute it and/or modify it |
10 | | * under the terms of the GNU Lesser General Public License as published by |
11 | | * the Free Software Foundation; either version 2.1 of the License, or |
12 | | * (at your option) any later version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, |
15 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | | * GNU Lesser General Public License for more details. |
18 | | * |
19 | | * You should have received a copy of the GNU Lesser General Public License |
20 | | * along with this program; if not, write to the Free Software Foundation, |
21 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
22 | | *****************************************************************************/ |
23 | | |
24 | | #ifdef HAVE_CONFIG_H |
25 | | # include "config.h" |
26 | | #endif |
27 | | |
28 | | #include <assert.h> |
29 | | |
30 | | #include <vlc_common.h> |
31 | | #include <vlc_charset.h> |
32 | | #include <vlc_strings.h> |
33 | | #include <vlc_arrays.h> |
34 | | #include <vlc_input.h> |
35 | | #include "xiph_metadata.h" |
36 | | |
37 | | input_attachment_t* ParseFlacPicture( const uint8_t *p_data, size_t size, |
38 | | int i_attachments, int *i_cover_score, int *i_cover_idx ) |
39 | 0 | { |
40 | | /* TODO: Merge with ID3v2 copy in modules/meta_engine/taglib.cpp. */ |
41 | 0 | static const char pi_cover_score[] = { |
42 | 0 | 0, /* Other */ |
43 | 0 | 5, /* 32x32 PNG image that should be used as the file icon */ |
44 | 0 | 4, /* File icon of a different size or format. */ |
45 | 0 | 20, /* Front cover image of the album. */ |
46 | 0 | 19, /* Back cover image of the album. */ |
47 | 0 | 13, /* Inside leaflet page of the album. */ |
48 | 0 | 18, /* Image from the album itself. */ |
49 | 0 | 17, /* Picture of the lead artist or soloist. */ |
50 | 0 | 16, /* Picture of the artist or performer. */ |
51 | 0 | 14, /* Picture of the conductor. */ |
52 | 0 | 15, /* Picture of the band or orchestra. */ |
53 | 0 | 9, /* Picture of the composer. */ |
54 | 0 | 8, /* Picture of the lyricist or text writer. */ |
55 | 0 | 7, /* Picture of the recording location or studio. */ |
56 | 0 | 10, /* Picture of the artists during recording. */ |
57 | 0 | 11, /* Picture of the artists during performance. */ |
58 | 0 | 6, /* Picture from a movie or video related to the track. */ |
59 | 0 | 1, /* Picture of a large, coloured fish. */ |
60 | 0 | 12, /* Illustration related to the track. */ |
61 | 0 | 3, /* Logo of the band or performer. */ |
62 | 0 | 2 /* Logo of the publisher (record company). */ |
63 | 0 | }; |
64 | |
|
65 | 0 | uint32_t type, len; |
66 | |
|
67 | 0 | if( size < 8 ) |
68 | 0 | return NULL; |
69 | 0 | #define RM(x) \ |
70 | 0 | do { \ |
71 | 0 | assert(size >= (x)); \ |
72 | 0 | size -= (x); \ |
73 | 0 | p_data += (x); \ |
74 | 0 | } while (0) |
75 | | |
76 | 0 | type = GetDWBE( p_data ); |
77 | 0 | RM(4); |
78 | 0 | len = GetDWBE( p_data ); |
79 | 0 | RM(4); |
80 | | |
81 | 0 | if( size < len ) |
82 | 0 | return NULL; |
83 | | |
84 | 0 | char *mime = strndup( (const char *)p_data, len ); |
85 | 0 | if( unlikely(mime == NULL) ) |
86 | 0 | return NULL; |
87 | 0 | RM(len); |
88 | | |
89 | 0 | if( size < 4 ) |
90 | 0 | { |
91 | 0 | free( mime ); |
92 | 0 | return NULL; |
93 | 0 | } |
94 | | |
95 | 0 | len = GetDWBE( p_data ); |
96 | 0 | RM(4); |
97 | | |
98 | 0 | if( size < len ) |
99 | 0 | { |
100 | 0 | free( mime ); |
101 | 0 | return NULL; |
102 | 0 | } |
103 | | |
104 | 0 | input_attachment_t *p_attachment = NULL; |
105 | 0 | char *description = strndup( (const char *)p_data, len ); |
106 | 0 | if( unlikely(description == NULL) ) |
107 | 0 | goto error; |
108 | 0 | RM(len); |
109 | | |
110 | 0 | EnsureUTF8( description ); |
111 | |
|
112 | 0 | if( size < 20 ) |
113 | 0 | goto error; |
114 | | |
115 | 0 | RM(4 * 4); /* skip */ |
116 | | |
117 | 0 | len = GetDWBE( p_data ); |
118 | 0 | RM(4); |
119 | | |
120 | 0 | if( size < len ) |
121 | 0 | goto error; |
122 | | |
123 | | /* printf( "Picture type=%"PRIu32" mime=%s description='%s' " |
124 | | "file length=%zu\n", type, mime, description, len ); */ |
125 | | |
126 | 0 | char name[7 + (sizeof (i_attachments) * 3) + 4 + 1]; |
127 | |
|
128 | 0 | snprintf( name, sizeof (name), "picture%u", i_attachments ); |
129 | |
|
130 | 0 | if( !strcasecmp( mime, "image/jpeg" ) ) |
131 | 0 | strcat( name, ".jpg" ); |
132 | 0 | else if( !strcasecmp( mime, "image/png" ) ) |
133 | 0 | strcat( name, ".png" ); |
134 | |
|
135 | 0 | p_attachment = vlc_input_attachment_New( name, mime, description, p_data, |
136 | 0 | size /* XXX: len instead? */ ); |
137 | |
|
138 | 0 | if( type < ARRAY_SIZE(pi_cover_score) && |
139 | 0 | *i_cover_score < pi_cover_score[type] ) |
140 | 0 | { |
141 | 0 | *i_cover_idx = i_attachments; |
142 | 0 | *i_cover_score = pi_cover_score[type]; |
143 | 0 | } |
144 | |
|
145 | 0 | error: |
146 | 0 | free( mime ); |
147 | 0 | free( description ); |
148 | 0 | return p_attachment; |
149 | 0 | } |
150 | | |
151 | | #undef RM |
152 | | #define RM(x) \ |
153 | 1 | do { \ |
154 | 1 | i_data -= (x); \ |
155 | 1 | p_data += (x); \ |
156 | 1 | } while (0) |
157 | | |
158 | | |
159 | | typedef struct chapters_array_t |
160 | | { |
161 | | unsigned int i_size; |
162 | | seekpoint_t ** pp_chapters; |
163 | | } chapters_array_t; |
164 | | |
165 | | static seekpoint_t * getChapterEntry( unsigned int i_index, chapters_array_t *p_array ) |
166 | 0 | { |
167 | 0 | if ( i_index > 4096 ) return NULL; |
168 | 0 | if ( i_index >= p_array->i_size ) |
169 | 0 | { |
170 | 0 | unsigned int i_newsize = p_array->i_size; |
171 | 0 | while( i_index >= i_newsize ) i_newsize += 50; |
172 | |
|
173 | 0 | if ( !p_array->pp_chapters ) |
174 | 0 | { |
175 | 0 | p_array->pp_chapters = calloc( i_newsize, sizeof( seekpoint_t * ) ); |
176 | 0 | if ( !p_array->pp_chapters ) return NULL; |
177 | 0 | p_array->i_size = i_newsize; |
178 | 0 | } else { |
179 | 0 | seekpoint_t **tmp = calloc( i_newsize, sizeof( seekpoint_t * ) ); |
180 | 0 | if ( !tmp ) return NULL; |
181 | 0 | memcpy( tmp, p_array->pp_chapters, p_array->i_size * sizeof( seekpoint_t * ) ); |
182 | 0 | free( p_array->pp_chapters ); |
183 | 0 | p_array->pp_chapters = tmp; |
184 | 0 | p_array->i_size = i_newsize; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | if ( !p_array->pp_chapters[i_index] ) |
188 | 0 | p_array->pp_chapters[i_index] = vlc_seekpoint_New(); |
189 | 0 | return p_array->pp_chapters[i_index]; |
190 | 0 | } |
191 | | |
192 | 0 | #define XIPHMETA_Title (1 << 0) |
193 | 0 | #define XIPHMETA_Artist (1 << 1) |
194 | 0 | #define XIPHMETA_Genre (1 << 2) |
195 | 0 | #define XIPHMETA_Copyright (1 << 3) |
196 | 0 | #define XIPHMETA_Album (1 << 4) |
197 | 0 | #define XIPHMETA_TrackNum (1 << 5) |
198 | 0 | #define XIPHMETA_Description (1 << 6) |
199 | 0 | #define XIPHMETA_Rating (1 << 7) |
200 | 0 | #define XIPHMETA_Date (1 << 8) |
201 | 0 | #define XIPHMETA_Language (1 << 9) |
202 | 0 | #define XIPHMETA_Publisher (1 << 10) |
203 | 0 | #define XIPHMETA_EncodedBy (1 << 11) |
204 | 0 | #define XIPHMETA_TrackTotal (1 << 12) |
205 | | |
206 | | static char * xiph_ExtractCueSheetMeta( const char *psz_line, |
207 | | const char *psz_tag, int i_tag, |
208 | | bool b_quoted ) |
209 | 0 | { |
210 | 0 | if( !strncasecmp( psz_line, psz_tag, i_tag ) ) |
211 | 0 | { |
212 | 0 | if( !b_quoted ) |
213 | 0 | return strdup( &psz_line[i_tag] ); |
214 | | |
215 | | /* Unquote string value */ |
216 | 0 | char *psz_value = malloc( strlen( psz_line ) - i_tag + 1 ); |
217 | 0 | if( psz_value ) |
218 | 0 | { |
219 | 0 | char *psz_out = psz_value; |
220 | 0 | psz_line += i_tag; |
221 | 0 | bool b_escaped = false; |
222 | 0 | while( *psz_line ) |
223 | 0 | { |
224 | 0 | switch( *psz_line ) |
225 | 0 | { |
226 | 0 | case '\\': |
227 | 0 | if( b_escaped ) |
228 | 0 | { |
229 | 0 | b_escaped = false; |
230 | 0 | *(psz_out++) = *psz_line; |
231 | 0 | } |
232 | 0 | else |
233 | 0 | { |
234 | 0 | b_escaped = true; |
235 | 0 | } |
236 | 0 | break; |
237 | 0 | case '"': |
238 | 0 | if( b_escaped ) |
239 | 0 | { |
240 | 0 | b_escaped = false; |
241 | 0 | *(psz_out++) = *psz_line; |
242 | 0 | } |
243 | 0 | break; |
244 | 0 | default: |
245 | 0 | *(psz_out++) = *psz_line; |
246 | 0 | break; |
247 | 0 | } |
248 | 0 | psz_line++; |
249 | 0 | } |
250 | 0 | *psz_out = 0; |
251 | 0 | return psz_value; |
252 | 0 | } |
253 | 0 | } |
254 | 0 | return NULL; |
255 | 0 | } |
256 | | |
257 | | static void xiph_ParseCueSheetMeta( unsigned *pi_flags, vlc_meta_t *p_meta, |
258 | | const char *psz_line, |
259 | | int *pi_seekpoint, seekpoint_t ***ppp_seekpoint, |
260 | | seekpoint_t **pp_tmppoint, bool *pb_valid ) |
261 | 0 | { |
262 | 0 | VLC_UNUSED(pi_seekpoint); |
263 | 0 | VLC_UNUSED(ppp_seekpoint); |
264 | |
|
265 | 0 | seekpoint_t *p_seekpoint = *pp_tmppoint; |
266 | 0 | char *psz_string; |
267 | |
|
268 | 0 | #define TRY_EXTRACT_CUEMETA(var, string, quoted) \ |
269 | 0 | if( !(*pi_flags & XIPHMETA_##var) &&\ |
270 | 0 | ( psz_string = xiph_ExtractCueSheetMeta( psz_line, string, sizeof(string) - 1, quoted ) ) )\ |
271 | 0 | {\ |
272 | 0 | vlc_meta_Set( p_meta, vlc_meta_##var, psz_string );\ |
273 | 0 | free( psz_string );\ |
274 | 0 | *pi_flags |= XIPHMETA_##var;\ |
275 | 0 | } |
276 | |
|
277 | 0 | TRY_EXTRACT_CUEMETA(Title, "TITLE \"", true) |
278 | 0 | else TRY_EXTRACT_CUEMETA(Genre, "REM GENRE ", false) |
279 | 0 | else TRY_EXTRACT_CUEMETA(Date, "REM DATE ", false) |
280 | 0 | else TRY_EXTRACT_CUEMETA(Artist, "PERFORMER \"", true) |
281 | 0 | else if( !strncasecmp( psz_line, " TRACK ", 8 ) ) |
282 | 0 | { |
283 | 0 | if( p_seekpoint ) |
284 | 0 | { |
285 | 0 | if( *pb_valid ) |
286 | 0 | TAB_APPEND( *pi_seekpoint, *ppp_seekpoint, p_seekpoint ); |
287 | 0 | else |
288 | 0 | vlc_seekpoint_Delete( p_seekpoint ); |
289 | 0 | *pb_valid = false; |
290 | 0 | } |
291 | 0 | *pp_tmppoint = p_seekpoint = vlc_seekpoint_New(); |
292 | 0 | } |
293 | 0 | else if( p_seekpoint && !strncasecmp( psz_line, " INDEX 01 ", 13 ) ) |
294 | 0 | { |
295 | 0 | unsigned m, s, f; |
296 | 0 | if( sscanf( &psz_line[13], "%u:%u:%u", &m, &s, &f ) == 3 ) |
297 | 0 | { |
298 | 0 | p_seekpoint->i_time_offset = vlc_tick_from_sec(m * 60 + s) + vlc_tick_from_samples(f, 75); |
299 | 0 | *pb_valid = true; |
300 | 0 | } |
301 | 0 | } |
302 | 0 | else if( p_seekpoint && !p_seekpoint->psz_name ) |
303 | 0 | { |
304 | 0 | p_seekpoint->psz_name = xiph_ExtractCueSheetMeta( psz_line, " TITLE \"", 11, true ); |
305 | 0 | } |
306 | 0 | } |
307 | | |
308 | | static void xiph_ParseCueSheet( unsigned *pi_flags, vlc_meta_t *p_meta, |
309 | | const char *p_data, int i_data, |
310 | | int *pi_seekpoint, seekpoint_t ***ppp_seekpoint ) |
311 | 0 | { |
312 | 0 | seekpoint_t *p_seekpoint = NULL; |
313 | 0 | bool b_valid = false; |
314 | |
|
315 | 0 | const char *p_head = p_data; |
316 | 0 | const char *p_tail = p_head; |
317 | 0 | while( p_tail < p_data + i_data ) |
318 | 0 | { |
319 | 0 | if( *p_tail == 0x0D ) |
320 | 0 | { |
321 | 0 | char *psz = strndup( p_head, p_tail - p_head ); |
322 | 0 | if( psz ) |
323 | 0 | { |
324 | 0 | xiph_ParseCueSheetMeta( pi_flags, p_meta, psz, |
325 | 0 | pi_seekpoint, ppp_seekpoint, |
326 | 0 | &p_seekpoint, &b_valid ); |
327 | 0 | free( psz ); |
328 | 0 | } |
329 | 0 | if( *(++p_tail) == 0x0A ) |
330 | 0 | p_tail++; |
331 | 0 | p_head = p_tail; |
332 | 0 | } |
333 | 0 | else |
334 | 0 | { |
335 | 0 | p_tail++; |
336 | 0 | } |
337 | 0 | } |
338 | | |
339 | |
|
340 | 0 | if( p_seekpoint ) |
341 | 0 | { |
342 | 0 | if( b_valid ) |
343 | 0 | TAB_APPEND( *pi_seekpoint, *ppp_seekpoint, p_seekpoint ); |
344 | 0 | else |
345 | 0 | vlc_seekpoint_Delete( p_seekpoint ); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | | void vorbis_ParseComment( es_format_t *p_fmt, vlc_meta_t **pp_meta, |
350 | | const uint8_t *p_data, size_t i_data, |
351 | | int *i_attachments, input_attachment_t ***attachments, |
352 | | int *i_cover_score, int *i_cover_idx, |
353 | | int *i_seekpoint, seekpoint_t ***ppp_seekpoint, |
354 | | float (* ppf_replay_gain)[AUDIO_REPLAY_GAIN_MAX], |
355 | | float (* ppf_replay_peak)[AUDIO_REPLAY_GAIN_MAX] ) |
356 | 1 | { |
357 | 1 | if( i_data < 8 ) |
358 | 0 | return; |
359 | | |
360 | 1 | uint32_t vendor_length = GetDWLE(p_data); RM(4); |
361 | | |
362 | 1 | if( vendor_length > i_data ) |
363 | 1 | return; /* invalid length */ |
364 | | |
365 | 0 | RM(vendor_length); /* TODO: handle vendor payload */ |
366 | |
|
367 | 0 | if( i_data < 4 ) |
368 | 0 | return; |
369 | | |
370 | 0 | uint32_t i_comment = GetDWLE(p_data); RM(4); |
371 | |
|
372 | 0 | if( i_comment > i_data || i_comment == 0 ) |
373 | 0 | return; /* invalid length */ |
374 | | |
375 | | /* */ |
376 | 0 | vlc_meta_t *p_meta = *pp_meta; |
377 | 0 | if( !p_meta ) |
378 | 0 | *pp_meta = p_meta = vlc_meta_New(); |
379 | |
|
380 | 0 | if( unlikely( !p_meta ) ) |
381 | 0 | return; |
382 | | |
383 | | /* */ |
384 | 0 | unsigned hasMetaFlags = 0; |
385 | |
|
386 | 0 | chapters_array_t chapters_array = { 0, NULL }; |
387 | |
|
388 | 0 | for( ; i_comment > 0 && i_data >= 4; i_comment-- ) |
389 | 0 | { |
390 | 0 | uint32_t comment_size = GetDWLE(p_data); RM(4); |
391 | |
|
392 | 0 | if( comment_size > i_data ) |
393 | 0 | break; |
394 | | |
395 | 0 | if( comment_size == 0 ) |
396 | 0 | continue; |
397 | | |
398 | 0 | char* psz_comment = malloc( comment_size + 1 ); |
399 | |
|
400 | 0 | if( unlikely( !psz_comment ) ) |
401 | 0 | goto next_comment; |
402 | | |
403 | 0 | memcpy( psz_comment, p_data, comment_size ); |
404 | 0 | psz_comment[comment_size] = '\0'; |
405 | |
|
406 | 0 | #define IF_EXTRACT(txt,var) \ |
407 | 0 | if( !strncasecmp(psz_comment, txt, strlen(txt)) ) \ |
408 | 0 | { \ |
409 | 0 | size_t key_length = strlen(txt); \ |
410 | 0 | EnsureUTF8( psz_comment + key_length ); \ |
411 | 0 | const char *oldval = vlc_meta_Get( p_meta, vlc_meta_ ## var ); \ |
412 | 0 | if( oldval && (hasMetaFlags & XIPHMETA_##var)) \ |
413 | 0 | { \ |
414 | 0 | char * newval; \ |
415 | 0 | if( asprintf( &newval, "%s,%s", oldval, &psz_comment[key_length] ) == -1 ) \ |
416 | 0 | newval = NULL; \ |
417 | 0 | vlc_meta_Set( p_meta, vlc_meta_ ## var, newval ); \ |
418 | 0 | free( newval ); \ |
419 | 0 | } \ |
420 | 0 | else \ |
421 | 0 | vlc_meta_Set( p_meta, vlc_meta_ ## var, &psz_comment[key_length] ); \ |
422 | 0 | hasMetaFlags |= XIPHMETA_##var; \ |
423 | 0 | } |
424 | |
|
425 | 0 | #define IF_EXTRACT_ONCE(txt,var) \ |
426 | 0 | if( !strncasecmp(psz_comment, txt, strlen(txt)) && !(hasMetaFlags & XIPHMETA_##var) ) \ |
427 | 0 | { \ |
428 | 0 | vlc_meta_Set( p_meta, vlc_meta_ ## var, &psz_comment[strlen(txt)] ); \ |
429 | 0 | hasMetaFlags |= XIPHMETA_##var; \ |
430 | 0 | } |
431 | |
|
432 | 0 | IF_EXTRACT("TITLE=", Title ) |
433 | 0 | else IF_EXTRACT("ARTIST=", Artist ) |
434 | 0 | else IF_EXTRACT("GENRE=", Genre ) |
435 | 0 | else IF_EXTRACT("COPYRIGHT=", Copyright ) |
436 | 0 | else IF_EXTRACT("ALBUM=", Album ) |
437 | 0 | else if( !(hasMetaFlags & XIPHMETA_TrackNum) && !strncasecmp(psz_comment, "TRACKNUMBER=", strlen("TRACKNUMBER=" ) ) ) |
438 | 0 | { |
439 | | /* Yeah yeah, such a clever idea, let's put xx/xx inside TRACKNUMBER |
440 | | * Oh, and let's not use TRACKTOTAL or TOTALTRACKS... */ |
441 | 0 | short unsigned u_track, u_total; |
442 | 0 | int nb_values = sscanf( &psz_comment[strlen("TRACKNUMBER=")], "%hu/%hu", &u_track, &u_total ); |
443 | 0 | if( nb_values >= 1 ) |
444 | 0 | { |
445 | 0 | char str[6]; |
446 | 0 | snprintf(str, 6, "%u", u_track); |
447 | 0 | vlc_meta_Set( p_meta, vlc_meta_TrackNumber, str ); |
448 | 0 | hasMetaFlags |= XIPHMETA_TrackNum; |
449 | 0 | if( nb_values >= 2 ) |
450 | 0 | { |
451 | 0 | snprintf(str, 6, "%u", u_total); |
452 | 0 | vlc_meta_Set( p_meta, vlc_meta_TrackTotal, str ); |
453 | 0 | hasMetaFlags |= XIPHMETA_TrackTotal; |
454 | 0 | } |
455 | 0 | } |
456 | 0 | } |
457 | 0 | else IF_EXTRACT_ONCE("TRACKTOTAL=", TrackTotal ) |
458 | 0 | else IF_EXTRACT_ONCE("TOTALTRACKS=", TrackTotal ) |
459 | 0 | else IF_EXTRACT("DESCRIPTION=", Description ) |
460 | 0 | else IF_EXTRACT("COMMENT=", Description ) |
461 | 0 | else IF_EXTRACT("COMMENTS=", Description ) |
462 | 0 | else IF_EXTRACT("RATING=", Rating ) |
463 | 0 | else IF_EXTRACT("DATE=", Date ) |
464 | 0 | else if( !strncasecmp(psz_comment, "LANGUAGE=", strlen("LANGUAGE=") ) ) |
465 | 0 | { |
466 | 0 | IF_EXTRACT("LANGUAGE=",Language) |
467 | 0 | if( p_fmt ) |
468 | 0 | { |
469 | 0 | free( p_fmt->psz_language ); |
470 | 0 | p_fmt->psz_language = strdup(&psz_comment[strlen("LANGUAGE=")]); |
471 | 0 | } |
472 | 0 | } |
473 | 0 | else IF_EXTRACT("ORGANIZATION=", Publisher ) |
474 | 0 | else IF_EXTRACT("ENCODER=", EncodedBy ) |
475 | 0 | else if( !strncasecmp( psz_comment, "METADATA_BLOCK_PICTURE=", strlen("METADATA_BLOCK_PICTURE="))) |
476 | 0 | { |
477 | 0 | if( attachments == NULL ) |
478 | 0 | goto next_comment; |
479 | | |
480 | 0 | uint8_t *p_picture; |
481 | 0 | size_t i_size = vlc_b64_decode_binary( &p_picture, &psz_comment[strlen("METADATA_BLOCK_PICTURE=")]); |
482 | 0 | input_attachment_t *p_attachment = ParseFlacPicture( p_picture, |
483 | 0 | i_size, *i_attachments, i_cover_score, i_cover_idx ); |
484 | 0 | free( p_picture ); |
485 | 0 | if( p_attachment ) |
486 | 0 | { |
487 | 0 | TAB_APPEND_CAST( (input_attachment_t**), |
488 | 0 | *i_attachments, *attachments, p_attachment ); |
489 | 0 | } |
490 | 0 | } |
491 | 0 | else if ( ppf_replay_gain && ppf_replay_peak && !strncmp(psz_comment, "REPLAYGAIN_", 11) ) |
492 | 0 | { |
493 | 0 | char *p = strchr( psz_comment, '=' ); |
494 | 0 | if (!p) goto next_comment; |
495 | 0 | if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_GAIN=", 22) ) |
496 | 0 | { |
497 | 0 | (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p ); |
498 | 0 | } |
499 | 0 | else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_GAIN=", 22) ) |
500 | 0 | { |
501 | 0 | (*ppf_replay_gain)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p ); |
502 | 0 | } |
503 | 0 | else if ( !strncasecmp(psz_comment, "REPLAYGAIN_ALBUM_PEAK=", 22) ) |
504 | 0 | { |
505 | 0 | (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_ALBUM] = vlc_atof_c( ++p ); |
506 | 0 | } |
507 | 0 | else if ( !strncasecmp(psz_comment, "REPLAYGAIN_TRACK_PEAK=", 22) ) |
508 | 0 | { |
509 | 0 | (*ppf_replay_peak)[AUDIO_REPLAY_GAIN_TRACK] = vlc_atof_c( ++p ); |
510 | 0 | } |
511 | 0 | } |
512 | 0 | else if( !strncasecmp(psz_comment, "CHAPTER", 7) ) |
513 | 0 | { |
514 | 0 | unsigned int i_chapt; |
515 | 0 | seekpoint_t *p_seekpoint = NULL; |
516 | |
|
517 | 0 | for( int i = 0; psz_comment[i] && psz_comment[i] != '='; i++ ) |
518 | 0 | if( psz_comment[i] >= 'a' && psz_comment[i] <= 'z' ) |
519 | 0 | psz_comment[i] -= 'a' - 'A'; |
520 | |
|
521 | 0 | if( strstr( psz_comment, "NAME=" ) && |
522 | 0 | sscanf( psz_comment, "CHAPTER%uNAME=", &i_chapt ) == 1 ) |
523 | 0 | { |
524 | 0 | char *p = strchr( psz_comment, '=' ); |
525 | 0 | p_seekpoint = getChapterEntry( i_chapt, &chapters_array ); |
526 | 0 | if ( !p || ! p_seekpoint ) goto next_comment; |
527 | 0 | EnsureUTF8( ++p ); |
528 | 0 | if ( ! p_seekpoint->psz_name ) |
529 | 0 | p_seekpoint->psz_name = strdup( p ); |
530 | 0 | } |
531 | 0 | else if( sscanf( psz_comment, "CHAPTER%u=", &i_chapt ) == 1 ) |
532 | 0 | { |
533 | 0 | unsigned int h, m, s, ms; |
534 | 0 | char *p = strchr( psz_comment, '=' ); |
535 | 0 | if( p && sscanf( ++p, "%u:%u:%u.%u", &h, &m, &s, &ms ) == 4 ) |
536 | 0 | { |
537 | 0 | p_seekpoint = getChapterEntry( i_chapt, &chapters_array ); |
538 | 0 | if ( ! p_seekpoint ) goto next_comment; |
539 | 0 | p_seekpoint->i_time_offset = vlc_tick_from_sec(h * 3600 + m * 60 + s) + VLC_TICK_FROM_MS(ms); |
540 | 0 | } |
541 | 0 | } |
542 | 0 | } |
543 | 0 | else if( !strncasecmp(psz_comment, "cuesheet=", 9) ) |
544 | 0 | { |
545 | 0 | EnsureUTF8( &psz_comment[9] ); |
546 | 0 | xiph_ParseCueSheet( &hasMetaFlags, p_meta, &psz_comment[9], comment_size - 9, |
547 | 0 | i_seekpoint, ppp_seekpoint ); |
548 | 0 | } |
549 | 0 | else if( strchr( psz_comment, '=' ) ) |
550 | 0 | { |
551 | | /* generic (PERFORMER/LICENSE/ORGANIZATION/LOCATION/CONTACT/ISRC, |
552 | | * undocumented tags and replay gain ) */ |
553 | 0 | char *p = strchr( psz_comment, '=' ); |
554 | 0 | *p++ = '\0'; |
555 | 0 | EnsureUTF8( p ); |
556 | |
|
557 | 0 | for( int i = 0; psz_comment[i]; i++ ) |
558 | 0 | if( psz_comment[i] >= 'a' && psz_comment[i] <= 'z' ) |
559 | 0 | psz_comment[i] -= 'a' - 'A'; |
560 | |
|
561 | 0 | vlc_meta_AddExtra( p_meta, psz_comment, p ); |
562 | 0 | } |
563 | 0 | #undef IF_EXTRACT |
564 | 0 | next_comment: |
565 | 0 | free( psz_comment ); |
566 | 0 | RM( comment_size ); |
567 | 0 | } |
568 | 0 | #undef RM |
569 | | |
570 | 0 | for ( unsigned int i=0; i<chapters_array.i_size; i++ ) |
571 | 0 | { |
572 | 0 | if ( !chapters_array.pp_chapters[i] ) continue; |
573 | 0 | TAB_APPEND_CAST( (seekpoint_t**), *i_seekpoint, *ppp_seekpoint, |
574 | 0 | chapters_array.pp_chapters[i] ); |
575 | 0 | } |
576 | 0 | free( chapters_array.pp_chapters ); |
577 | 0 | } |
578 | | |
579 | | const char *FindKateCategoryName( const char *psz_tag ) |
580 | 0 | { |
581 | 0 | for( size_t i = 0; i < sizeof(Katei18nCategories)/sizeof(Katei18nCategories[0]); i++ ) |
582 | 0 | { |
583 | 0 | if( !strcmp( psz_tag, Katei18nCategories[i].psz_tag ) ) |
584 | 0 | return Katei18nCategories[i].psz_i18n; |
585 | 0 | } |
586 | 0 | return N_("Unknown category"); |
587 | 0 | } |
588 | | |