1 /*****************************************************************************
2 * meta.c: mp4 meta handling
3 *****************************************************************************
4 * Copyright (C) 2001-2004, 2010, 2014 VLC authors and VideoLAN
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
25 #include "../meta_engine/ID3Genres.h" /* for ATOM_gnre */
26 #include "languages.h"
29 #include <vlc_charset.h>
31 #include "../meta_engine/ID3Tag.h"
32 #include "../meta_engine/ID3Meta.h"
38 const uint32_t xa9_type
;
39 const vlc_meta_type_t meta_type
;
41 { ATOM_0x40PRM
, vlc_meta_EncodedBy
}, /* Adobe Premiere */
42 { ATOM_0x40PRQ
, vlc_meta_EncodedBy
}, /* Adobe Qt */
43 { ATOM_0xa9nam
, vlc_meta_Title
}, /* Full name */
44 { ATOM_0xa9aut
, vlc_meta_Artist
},
45 { ATOM_0xa9ART
, vlc_meta_Artist
},
46 { ATOM_0xa9cpy
, vlc_meta_Copyright
},
47 { ATOM_0xa9day
, vlc_meta_Date
}, /* Creation Date */
48 { ATOM_0xa9des
, vlc_meta_Description
}, /* Description */
49 { ATOM_0xa9gen
, vlc_meta_Genre
}, /* Genre */
50 { ATOM_0xa9alb
, vlc_meta_Album
}, /* Album */
51 { ATOM_0xa9trk
, vlc_meta_TrackNumber
}, /* Track */
52 { ATOM_0xa9cmt
, vlc_meta_Description
}, /* Comment */
53 { ATOM_0xa9url
, vlc_meta_URL
}, /* URL */
54 { ATOM_0xa9too
, vlc_meta_EncodedBy
}, /* Encoder Tool */
55 { ATOM_0xa9enc
, vlc_meta_EncodedBy
}, /* Encoded By */
56 { ATOM_0xa9pub
, vlc_meta_Publisher
},
57 { ATOM_0xa9dir
, vlc_meta_Director
},
58 { ATOM_desc
, vlc_meta_Description
},
59 { ATOM_MCPS
, vlc_meta_EncodedBy
}, /* Cleaner Pro */
60 { ATOM_aART
, vlc_meta_AlbumArtist
},
65 const uint32_t xa9_type
;
66 const char metadata
[25];
67 } xa9typetoextrameta
[] = {
68 { ATOM_0xa9wrt
, N_("Writer") },
69 { ATOM_0xa9com
, N_("Composer") },
70 { ATOM_0xa9prd
, N_("Producer") },
71 { ATOM_0xa9inf
, N_("Information") },
72 { ATOM_0xa9dis
, N_("Disclaimer") },
73 { ATOM_0xa9req
, N_("Requirements") },
74 { ATOM_0xa9fmt
, N_("Original Format") },
75 { ATOM_0xa9dsa
, N_("Display Source As") },
76 { ATOM_0xa9hst
, N_("Host Computer") },
77 { ATOM_0xa9prf
, N_("Performers") },
78 { ATOM_0xa9ope
, N_("Original Performer") },
79 { ATOM_0xa9src
, N_("Providers Source Content") },
80 { ATOM_0xa9wrn
, N_("Warning") },
81 { ATOM_0xa9swr
, N_("Software") },
82 { ATOM_0xa9lyr
, N_("Lyrics") },
83 { ATOM_0xa9mak
, N_("Record Company") },
84 { ATOM_0xa9mod
, N_("Model") },
85 { ATOM_0xa9PRD
, N_("Product") },
86 { ATOM_0xa9grp
, N_("Grouping") },
87 { ATOM_0xa9gen
, N_("Genre") },
88 { ATOM_0xa9st3
, N_("Sub-Title") },
89 { ATOM_0xa9arg
, N_("Arranger") },
90 { ATOM_0xa9ard
, N_("Art Director") },
91 { ATOM_0xa9cak
, N_("Copyright Acknowledgement") },
92 { ATOM_0xa9con
, N_("Conductor") },
93 { ATOM_0xa9des
, N_("Song Description") },
94 { ATOM_0xa9lnt
, N_("Liner Notes") },
95 { ATOM_0xa9phg
, N_("Phonogram Rights") },
96 { ATOM_0xa9pub
, N_("Publisher") },
97 { ATOM_0xa9sne
, N_("Sound Engineer") },
98 { ATOM_0xa9sol
, N_("Soloist") },
99 { ATOM_0xa9thx
, N_("Thanks") },
100 { ATOM_0xa9xpd
, N_("Executive Producer") },
101 { ATOM_aART
, N_("Album Artist") },
102 { ATOM_flvr
, N_("Encoding Params") },
103 { ATOM_vndr
, N_("Vendor") },
104 { ATOM_xid_
, N_("Catalog Number") },
105 { ATOM_gshh
, "YouTube Host" },
106 { ATOM_gspm
, "YouTube Ping Message" },
107 { ATOM_gspu
, "YouTube Ping Url" },
108 { ATOM_gssd
, "YouTube Source Data" },
109 { ATOM_gsst
, "YouTube Start Time" },
110 { ATOM_gstd
, "YouTube Track Duration" },
115 const char *psz_naming
;
116 const vlc_meta_type_t meta_type
;
117 } com_apple_quicktime_tometa
[] = {
118 { "displayname", vlc_meta_NowPlaying
},
119 { "software", vlc_meta_EncodedBy
},
120 { "Encoded_With", vlc_meta_EncodedBy
},
121 { "album", vlc_meta_Album
},
122 { "artist", vlc_meta_Artist
},
123 { "comment", vlc_meta_Description
},
124 { "description", vlc_meta_Description
},
125 { "copyright", vlc_meta_Copyright
},
126 { "creationdate", vlc_meta_Date
},
127 { "director", vlc_meta_Director
},
128 { "genre", vlc_meta_Genre
},
129 { "publisher", vlc_meta_Publisher
},
134 const char *psz_naming
;
135 const char *psz_metadata
;
136 } com_apple_quicktime_toextrameta
[] = {
137 { "information", N_("Information") },
138 { "keywords", N_("Keywords") },
139 { "make", N_("Vendor") },
142 inline static char * StringConvert( const MP4_Box_data_data_t
*p_data
)
144 if ( !p_data
|| !p_data
->i_blob
)
147 switch( p_data
->e_wellknowntype
)
150 case DATA_WKT_UTF8_SORT
:
151 return FromCharset( "UTF-8", p_data
->p_blob
, p_data
->i_blob
);
153 case DATA_WKT_UTF16_SORT
:
154 return FromCharset( "UTF-16BE", p_data
->p_blob
, p_data
->i_blob
);
156 return FromCharset( "SHIFT-JIS", p_data
->p_blob
, p_data
->i_blob
);
162 static char * ExtractString( MP4_Box_t
*p_box
)
164 if ( p_box
->i_type
== ATOM_data
)
165 return StringConvert( p_box
->data
.p_data
);
167 MP4_Box_t
*p_data
= MP4_BoxGet( p_box
, "data" );
169 return StringConvert( BOXDATA(p_data
) );
170 else if ( p_box
->data
.p_binary
&& p_box
->data
.p_binary
->p_blob
)
172 char *psz_utf
= strndup( p_box
->data
.p_binary
->p_blob
,
173 p_box
->data
.p_binary
->i_blob
);
174 if (likely( psz_utf
))
175 EnsureUTF8( psz_utf
);
182 static bool AppleNameToMeta( char const* name
,
183 vlc_meta_type_t
const** meta_type
, char const** meta_key
)
188 for( size_t i
= 0; *meta_type
== NULL
&&
189 i
< ARRAY_SIZE( com_apple_quicktime_tometa
); ++i
)
191 if( !strcmp( name
, com_apple_quicktime_tometa
[i
].psz_naming
) )
192 *meta_type
= &com_apple_quicktime_tometa
[i
].meta_type
;
195 for( size_t i
= 0; *meta_key
== NULL
&&
196 i
< ARRAY_SIZE( com_apple_quicktime_toextrameta
); ++i
)
198 if( !strcmp( name
, com_apple_quicktime_toextrameta
[i
].psz_naming
) )
199 *meta_key
= com_apple_quicktime_toextrameta
[i
].psz_metadata
;
202 return *meta_type
|| *meta_key
;
205 static bool AtomXA9ToMeta( uint32_t i_type
,
206 vlc_meta_type_t
const** meta_type
, char const** meta_key
)
211 for( size_t i
= 0; !*meta_type
&& i
< ARRAY_SIZE( xa9typetometa
); ++i
)
212 if( xa9typetometa
[i
].xa9_type
== i_type
)
213 *meta_type
= &xa9typetometa
[i
].meta_type
;
215 for( size_t i
= 0; !*meta_key
&& i
< ARRAY_SIZE( xa9typetoextrameta
); ++i
)
216 if( xa9typetoextrameta
[i
].xa9_type
== i_type
)
217 *meta_key
= xa9typetoextrameta
[i
].metadata
;
219 return *meta_type
|| *meta_key
;
222 static bool SetMeta( vlc_meta_t
* p_meta
, int i_type
, char const* name
, MP4_Box_t
* p_box
)
224 vlc_meta_type_t
const* type
;
227 if( ( name
!= NULL
&& !AppleNameToMeta( name
, &type
, &key
) ) ||
228 ( name
== NULL
&& !AtomXA9ToMeta( i_type
, &type
, &key
) ) )
233 char* psz_utf
= ExtractString( p_box
);
237 if( type
) vlc_meta_Set( p_meta
, *type
, psz_utf
);
238 else vlc_meta_AddExtra( p_meta
, key
, psz_utf
);
246 static int ExtractIntlStrings( vlc_meta_t
*p_meta
, MP4_Box_t
*p_box
)
248 if( MP4_BoxGet( p_box
, "data" ) )
251 vlc_meta_type_t
const* meta_type
;
252 char const* meta_key
;
254 if( AtomXA9ToMeta( p_box
->i_type
, &meta_type
, &meta_key
) == false )
257 if( p_box
->p_father
== NULL
||
258 p_box
->p_father
->i_type
!= ATOM_udta
||
259 p_box
->data
.p_binary
== NULL
||
260 p_box
->data
.p_binary
->p_blob
== NULL
)
265 vlc_meta_t
* p_meta_intl
= vlc_meta_New();
267 if( unlikely( !p_meta_intl
) )
270 char const* p_peek
= p_box
->data
.p_binary
->p_blob
;
271 uint64_t i_read
= p_box
->data
.p_binary
->i_blob
;
275 uint16_t i_len
, i_lang
;
277 MP4_GET2BYTES( i_len
);
278 MP4_GET2BYTES( i_lang
);
283 char charset
[15] = "MACINTOSH//";
285 decodeQtLanguageCode( i_lang
, charset
+11, &(bool){0} );
287 if( i_lang
>= 0x400 && i_lang
!= 0x7fff )
289 strcpy( charset
, i_len
< 2 || memcmp( p_peek
, "\xFE\xFF", 2 )
290 ? "UTF-8" : "UTF-16BE" );
293 char* data
= FromCharset( charset
, p_peek
, i_len
);
298 vlc_meta_Set( p_meta_intl
, *meta_type
, data
);
300 meta_key
= vlc_meta_TypeToLocalizedString( *meta_type
);
306 if( asprintf( &key
, "%s (%s)", meta_key
, charset
+11 ) != -1 )
308 vlc_meta_AddExtra( p_meta_intl
, key
, data
);
320 vlc_meta_Merge( p_meta
, p_meta_intl
);
322 vlc_meta_Delete( p_meta_intl
);
326 static void ExtractItunesInfoTriplets( vlc_meta_t
*p_meta
, MP4_Box_t
*p_box
)
328 if( p_box
->i_type
!= ATOM_ITUN
)
330 MP4_Box_t
*p_mean
= MP4_BoxGet( p_box
, "mean" );
331 MP4_Box_t
*p_name
= MP4_BoxGet( p_box
, "name" );
332 MP4_Box_t
*p_data
= MP4_BoxGet( p_box
, "data" );
333 if( !p_mean
|| p_mean
->data
.p_binary
->i_blob
< 4 + 16 ||
334 !p_name
|| p_name
->data
.p_binary
->i_blob
< 5 ||
335 !p_data
|| !BOXDATA(p_data
) )
338 if( !strncmp( &((char*)p_mean
->data
.p_binary
->p_blob
)[4], "com.apple.iTunes",
339 p_mean
->data
.p_binary
->i_blob
- 4 ) )
341 char *psz_name
= strndup( &((char*)p_name
->data
.p_binary
->p_blob
)[4],
342 p_name
->data
.p_binary
->i_blob
- 4 );
343 char *psz_value
= ExtractString( p_data
);
344 if( psz_name
&& psz_value
)
345 vlc_meta_AddExtra( p_meta
, psz_name
, psz_value
);
351 static void SetupmdirMeta( vlc_meta_t
*p_meta
, MP4_Box_t
*p_box
)
353 const MP4_Box_t
*p_data
= MP4_BoxGet( p_box
, "data" );
355 if( p_data
== NULL
|| !BOXDATA(p_data
) )
357 if( ExtractIntlStrings( p_meta
, p_box
) )
360 SetMeta( p_meta
, p_box
->i_type
, NULL
, p_box
);
364 /* XXX Becarefull p_udta can have box that are not 0xa9xx */
365 switch( p_box
->i_type
)
369 if ( BOXDATA(p_data
)->i_blob
>= 4 &&
370 BOXDATA(p_data
)->e_wellknowntype
== DATA_WKT_BE_SIGNED
)
373 snprintf( psz_utf
, sizeof( psz_utf
), "%"PRId32
,
374 GetDWBE(BOXDATA(p_data
)->p_blob
) );
375 vlc_meta_AddExtra( p_meta
, "iTunes Account ID", psz_utf
);
381 if ( BOXDATA(p_data
)->i_blob
>= 4 &&
382 BOXDATA(p_data
)->e_wellknowntype
== DATA_WKT_BE_SIGNED
)
385 snprintf( psz_utf
, sizeof( psz_utf
), "%"PRId32
,
386 GetDWBE(BOXDATA(p_data
)->p_blob
) );
387 vlc_meta_AddExtra( p_meta
, "iTunes Catalog ID", psz_utf
);
393 if ( BOXDATA(p_data
)->i_blob
>= 6 &&
394 BOXDATA(p_data
)->e_wellknowntype
== DATA_WKT_RESERVED
)
397 snprintf( psz_number
, sizeof( psz_number
), "%"PRIu16
, GetWBE(&BOXDATA(p_data
)->p_blob
[2]) );
398 vlc_meta_Set( p_meta
, vlc_meta_DiscNumber
, psz_number
);
399 snprintf( psz_number
, sizeof( psz_number
), "%"PRIu16
, GetWBE(&BOXDATA(p_data
)->p_blob
[4]) );
400 vlc_meta_Set( p_meta
, vlc_meta_DiscTotal
, psz_number
);
406 if ( BOXDATA(p_data
)->i_blob
>= 2 &&
407 BOXDATA(p_data
)->e_wellknowntype
== DATA_WKT_RESERVED
)
409 const uint16_t i_genre
= GetWBE(BOXDATA(p_data
)->p_blob
);
410 if( i_genre
&& i_genre
<= ID3_GENRES_COUNT
)
411 vlc_meta_SetGenre( p_meta
, ID3_ppsz_genres
[i_genre
- 1] );
417 if ( BOXDATA(p_data
)->i_blob
>= 1 )
419 const char *psz_rating
;
420 switch( *BOXDATA(p_data
)->p_blob
)
423 psz_rating
= N_("Explicit");
426 psz_rating
= N_("Clean");
430 psz_rating
= N_("None");
433 vlc_meta_AddExtra( p_meta
, N_("Rating"), psz_rating
);
439 if ( BOXDATA(p_data
)->i_blob
>= 4 &&
440 BOXDATA(p_data
)->e_wellknowntype
== DATA_WKT_RESERVED
)
443 snprintf( psz_trck
, sizeof( psz_trck
), "%"PRIu16
, GetWBE(&BOXDATA(p_data
)->p_blob
[2]) );
444 vlc_meta_SetTrackNum( p_meta
, psz_trck
);
445 if( BOXDATA(p_data
)->i_blob
>= 8 && GetWBE(&BOXDATA(p_data
)->p_blob
[4]) )
447 snprintf( psz_trck
, sizeof( psz_trck
), "%"PRIu16
, GetWBE(&BOXDATA(p_data
)->p_blob
[4]) );
448 vlc_meta_Set( p_meta
, vlc_meta_TrackTotal
, psz_trck
);
454 ExtractItunesInfoTriplets( p_meta
, p_box
);
457 SetMeta( p_meta
, p_box
->i_type
, NULL
, p_box
);
462 static void SetupmdtaMeta( vlc_meta_t
*p_meta
, MP4_Box_t
*p_box
, MP4_Box_t
*p_keys
)
464 if ( !p_keys
|| !BOXDATA(p_keys
) || BOXDATA(p_keys
)->i_entry_count
== 0 )
466 if ( !p_box
->i_index
|| p_box
->i_index
> BOXDATA(p_keys
)->i_entry_count
)
469 const char *psz_naming
= BOXDATA(p_keys
)->p_entries
[p_box
->i_index
- 1].psz_value
;
470 const uint32_t i_namespace
= BOXDATA(p_keys
)->p_entries
[p_box
->i_index
- 1].i_namespace
;
472 if( i_namespace
== HANDLER_mdta
)
474 if ( !strncmp( "com.apple.quicktime.", psz_naming
, 20 ) )
475 SetMeta( p_meta
, 0, psz_naming
+ 20, p_box
);
477 else if ( i_namespace
== ATOM_udta
)
479 /* Regular atom inside... could that be even more complex ??? */
480 char *psz_utf
= ExtractString( p_box
);
483 if ( strlen(psz_utf
) == 4 )
486 VLC_FOURCC(psz_utf
[0],psz_utf
[1],psz_utf
[2],psz_utf
[3]),
494 static int ID3TAG_Parse_Handler( uint32_t i_tag
, const uint8_t *p_payload
,
495 size_t i_payload
, void *p_priv
)
497 vlc_meta_t
*p_meta
= (vlc_meta_t
*) p_priv
;
499 (void) ID3HandleTag( p_payload
, i_payload
, i_tag
, p_meta
, NULL
);
504 static void SetupID3v2Meta( vlc_meta_t
*p_meta
, MP4_Box_t
*p_box
)
506 const MP4_Box_t
*p_binary
= MP4_BoxGet( p_box
, "ID32" );
507 if( p_binary
== NULL
|| !BOXDATA(p_binary
) || BOXDATA(p_binary
)->i_blob
< 6 + 20 + 1 )
510 /* ID3v2 in 3GPP / ETSI TS 126 244 8.3, Header size 4 + 2 */
511 ID3TAG_Parse( &((uint8_t *)BOXDATA(p_binary
)->p_blob
)[6], BOXDATA(p_binary
)->i_blob
- 6,
512 ID3TAG_Parse_Handler
, p_meta
);
515 void SetupMeta( vlc_meta_t
*p_meta
, MP4_Box_t
*p_udta
)
517 uint32_t i_handler
= 0;
518 if ( p_udta
->p_father
)
519 i_handler
= p_udta
->i_handler
;
521 for( MP4_Box_t
*p_box
= p_udta
->p_first
; p_box
; p_box
= p_box
->p_next
)
527 MP4_Box_t
*p_keys
= MP4_BoxGet( p_udta
->p_father
, "keys" );
528 SetupmdtaMeta( p_meta
, p_box
, p_keys
);
533 SetupID3v2Meta( p_meta
, p_box
);
538 SetupmdirMeta( p_meta
, p_box
);