Commit b0ad53b6 authored by François Cartegnie's avatar François Cartegnie 🤞

demux: mp4: rewrite meta handling

Follow the spec and correctly handle text encoding
parent 84726b40
......@@ -193,7 +193,7 @@ libmp4_plugin_la_SOURCES = demux/mp4/mp4.c demux/mp4/mp4.h \
demux/mp4/libmp4.c demux/mp4/libmp4.h \
demux/mp4/id3genres.h demux/mp4/languages.h \
demux/asf/asfpacket.c demux/asf/asfpacket.h \
demux/mp4/essetup.c
demux/mp4/essetup.c demux/mp4/meta.c
libmp4_plugin_la_LIBADD = $(LIBM)
libmp4_plugin_la_LDFLAGS = $(AM_LDFLAGS)
if HAVE_ZLIB
......
......@@ -2967,89 +2967,62 @@ static void MP4_FreeBox_data( MP4_Box_t *p_box )
free( p_box->data.p_data->p_blob );
}
static int MP4_ReadBox_Metadata( stream_t *p_stream, MP4_Box_t *p_box )
{
const uint8_t *p_peek;
if ( stream_Peek( p_stream, &p_peek, 16 ) < 16 )
return 0;
if ( stream_Read( p_stream, NULL, 8 ) < 8 )
return 0;
return MP4_ReadBoxContainerChildren( p_stream, p_box, ATOM_data );
}
static int MP4_ReadBox_0xa9xxx( stream_t *p_stream, MP4_Box_t *p_box )
{
uint16_t i16;
return MP4_ReadBox_Metadata( p_stream, p_box );
MP4_READBOX_ENTER( MP4_Box_data_string_t );
/* FIXME: find out what was that 2 bytes sized atom and its own handler */
// if ( GetWBE( &p_peek[8] ) > 0 )
// uint16_t i16;
p_box->data.p_string->psz_text = NULL;
// MP4_READBOX_ENTER( MP4_Box_data_string_t );
MP4_GET2BYTES( i16 );
// p_box->data.p_string->psz_text = NULL;
if( i16 > 0 )
{
int i_length = i16;
// MP4_GET2BYTES( i16 );
MP4_GET2BYTES( i16 );
if( i_length >= i_read ) i_length = i_read + 1;
// if( i16 > 0 )
// {
// int i_length = i16;
p_box->data.p_string->psz_text = malloc( i_length );
if( p_box->data.p_string->psz_text == NULL )
MP4_READBOX_EXIT( 0 );
// MP4_GET2BYTES( i16 );
// if( i_length >= i_read ) i_length = i_read + 1;
i_length--;
memcpy( p_box->data.p_string->psz_text,
p_peek, i_length );
p_box->data.p_string->psz_text[i_length] = '\0';
// p_box->data.p_string->psz_text = malloc( i_length );
// if( p_box->data.p_string->psz_text == NULL )
// MP4_READBOX_EXIT( 0 );
#ifdef MP4_VERBOSE
msg_Dbg( p_stream,
"read box: \"c%3.3s\" text=`%s'",
((char*)&p_box->i_type + 1),
p_box->data.p_string->psz_text );
#endif
}
else
{
/* try iTune/Quicktime format, rewind to start */
p_peek -= 2; i_read += 2;
// we are expecting a 'data' box
uint32_t i_data_len;
uint32_t i_data_tag;
// i_length--;
// memcpy( p_box->data.p_string->psz_text,
// p_peek, i_length );
// p_box->data.p_string->psz_text[i_length] = '\0';
MP4_GET4BYTES( i_data_len );
if( i_data_len > i_read ) i_data_len = i_read;
MP4_GETFOURCC( i_data_tag );
if( (i_data_len > 0) && (i_data_tag == ATOM_data) )
{
/* data box contains a version/flags field */
uint32_t i_version;
uint32_t i_reserved;
VLC_UNUSED(i_reserved);
MP4_GET4BYTES( i_version );
MP4_GET4BYTES( i_reserved );
// version should be 0, flags should be 1 for text, 0 for data
if( ( i_version == 0x00000001 ) && (i_data_len >= 12 ) )
{
// the rest is the text
i_data_len -= 12;
p_box->data.p_string->psz_text = malloc( i_data_len + 1 );
if( p_box->data.p_string->psz_text == NULL )
MP4_READBOX_EXIT( 0 );
memcpy( p_box->data.p_string->psz_text,
p_peek, i_data_len );
p_box->data.p_string->psz_text[i_data_len] = '\0';
#ifdef MP4_VERBOSE
msg_Dbg( p_stream,
"read box: \"c%3.3s\" text=`%s'",
((char*)&p_box->i_type+1),
p_box->data.p_string->psz_text );
#endif
}
else
{
// TODO: handle data values for ID3 tag values, track num or cover art,etc...
}
}
}
//#ifdef MP4_VERBOSE
// msg_Dbg( p_stream,
// "read box: \"c%3.3s\" text=`%s'",
// ((char*)&p_box->i_type + 1),
// p_box->data.p_string->psz_text );
//#endif
// }
// else
MP4_READBOX_EXIT( 1 );
// MP4_READBOX_EXIT( 1 );
}
static void MP4_FreeBox_0xa9xxx( MP4_Box_t *p_box )
{
FREENULL( p_box->data.p_string->psz_text );
/* If Meta, that box should be empty /common */
if( p_box->data.p_string )
FREENULL( p_box->data.p_string->psz_text );
}
/* Chapter support */
......
/*****************************************************************************
* meta.c: mp4 meta handling
*****************************************************************************
* Copyright (C) 2001-2004, 2010, 2014 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include "mp4.h"
#include "id3genres.h" /* for ATOM_gnre */
#include <vlc_meta.h>
#include <vlc_charset.h>
#include <assert.h>
static const struct
{
const uint32_t xa9_type;
const vlc_meta_type_t meta_type;
} xa9typetometa[] = {
{ ATOM_0xa9nam, vlc_meta_Title }, /* Full name */
{ ATOM_0xa9aut, vlc_meta_Artist },
{ ATOM_0xa9ART, vlc_meta_Artist },
{ ATOM_0xa9cpy, vlc_meta_Copyright },
{ ATOM_0xa9day, vlc_meta_Date }, /* Creation Date */
{ ATOM_0xa9des, vlc_meta_Description }, /* Description */
{ ATOM_0xa9gen, vlc_meta_Genre }, /* Genre */
{ ATOM_0xa9alb, vlc_meta_Album }, /* Album */
{ ATOM_0xa9trk, vlc_meta_TrackNumber }, /* Track */
{ ATOM_0xa9cmt, vlc_meta_Description }, /* Comment */
{ ATOM_0xa9url, vlc_meta_URL }, /* URL */
{ ATOM_0xa9too, vlc_meta_EncodedBy }, /* Encoder Tool */
{ ATOM_0xa9enc, vlc_meta_EncodedBy }, /* Encoded By */
{ ATOM_0xa9pub, vlc_meta_Publisher },
{ ATOM_0xa9dir, vlc_meta_Director },
{ 0, 0 },
};
static const struct
{
const uint32_t xa9_type;
const char metadata[25];
} xa9typetoextrameta[] = {
{ ATOM_0xa9wrt, N_("Writer") },
{ ATOM_0xa9com, N_("Composer") },
{ ATOM_0xa9prd, N_("Producer") },
{ ATOM_0xa9inf, N_("Information") },
{ ATOM_0xa9dis, N_("Disclaimer") },
{ ATOM_0xa9req, N_("Requirements") },
{ ATOM_0xa9fmt, N_("Original Format") },
{ ATOM_0xa9dsa, N_("Display Source As") },
{ ATOM_0xa9hst, N_("Host Computer") },
{ ATOM_0xa9prf, N_("Performers") },
{ ATOM_0xa9ope, N_("Original Performer") },
{ ATOM_0xa9src, N_("Providers Source Content") },
{ ATOM_0xa9wrn, N_("Warning") },
{ ATOM_0xa9swr, N_("Software") },
{ ATOM_0xa9lyr, N_("Lyrics") },
{ ATOM_0xa9mak, N_("Record Company") },
{ ATOM_0xa9mod, N_("Model") },
{ ATOM_0xa9PRD, N_("Product") },
{ ATOM_0xa9grp, N_("Grouping") },
{ ATOM_0xa9gen, N_("Genre") },
{ ATOM_0xa9st3, N_("Sub-Title") },
{ ATOM_0xa9arg, N_("Arranger") },
{ ATOM_0xa9ard, N_("Art Director") },
{ ATOM_0xa9cak, N_("Copyright Acknowledgement") },
{ ATOM_0xa9con, N_("Conductor") },
{ ATOM_0xa9des, N_("Song Description") },
{ ATOM_0xa9lnt, N_("Liner Notes") },
{ ATOM_0xa9phg, N_("Phonogram Rights") },
{ ATOM_0xa9pub, N_("Publisher") },
{ ATOM_0xa9sne, N_("Sound Engineer") },
{ ATOM_0xa9sol, N_("Soloist") },
{ ATOM_0xa9thx, N_("Thanks") },
{ ATOM_0xa9xpd, N_("Executive Producer") },
{ ATOM_vndr, N_("Vendor") },
{ 0, "" },
};
static const struct
{
const char *psz_naming;
const vlc_meta_type_t meta_type;
} com_apple_quicktime_tometa[] = {
{ "displayname", vlc_meta_NowPlaying },
{ "software", vlc_meta_EncodedBy },
{ "Encoded_With", vlc_meta_EncodedBy },
{ "album", vlc_meta_Album },
{ "artist", vlc_meta_Artist },
{ "comment", vlc_meta_Description },
{ "description", vlc_meta_Description },
{ "copyright", vlc_meta_Copyright },
{ "creationdate", vlc_meta_Date },
{ "director", vlc_meta_Director },
{ "genre", vlc_meta_Genre },
{ "publisher", vlc_meta_Publisher },
{ NULL, 0 },
};
static const struct
{
const char *psz_naming;
const char *psz_metadata;
} com_apple_quicktime_toextrameta[] = {
{ "information", N_("Information") },
{ "keywords", N_("Keywords") },
{ "make", N_("Vendor") },
{ NULL, NULL },
};
inline static char * StringConvert( const MP4_Box_data_data_t *p_data )
{
if ( !p_data || !p_data->i_blob )
return NULL;
switch( p_data->e_wellknowntype )
{
case DATA_WKT_UTF8:
case DATA_WKT_UTF8_SORT:
return FromCharset( "UTF-8", p_data->p_blob, p_data->i_blob );
case DATA_WKT_UTF16:
case DATA_WKT_UTF16_SORT:
return FromCharset( "UTF-16BE", p_data->p_blob, p_data->i_blob );
case DATA_WKT_SJIS:
return FromCharset( "SHIFT-JIS", p_data->p_blob, p_data->i_blob );
default:
return NULL;
}
}
static char * ExtractString( MP4_Box_t *p_box )
{
if ( p_box->i_type == ATOM_data )
return StringConvert( p_box->data.p_data );
MP4_Box_t *p_data = MP4_BoxGet( p_box, "data" );
if ( p_data )
return StringConvert( BOXDATA(p_data) );
else if ( p_box->data.p_string && p_box->data.p_string->psz_text )
{
char *psz_utf = strdup( p_box->data.p_string->psz_text );
if (likely( psz_utf ))
EnsureUTF8( psz_utf );
return psz_utf;
}
else
return NULL;
}
static bool MatchXA9Type( vlc_meta_t *p_meta, uint32_t i_type, MP4_Box_t *p_box )
{
bool b_matched = false;
for( unsigned i = 0; !b_matched && xa9typetometa[i].xa9_type; i++ )
{
if( i_type == xa9typetometa[i].xa9_type )
{
b_matched = true;
char *psz_utf = ExtractString( p_box );
if( psz_utf )
{
vlc_meta_Set( p_meta, xa9typetometa[i].meta_type, psz_utf );
free( psz_utf );
}
break;
}
}
for( unsigned i = 0; !b_matched && xa9typetoextrameta[i].xa9_type; i++ )
{
if( i_type == xa9typetoextrameta[i].xa9_type )
{
b_matched = true;
char *psz_utf = ExtractString( p_box );
if( psz_utf )
{
vlc_meta_AddExtra( p_meta, _(xa9typetoextrameta[i].metadata), psz_utf );
free( psz_utf );
}
break;
}
}
return b_matched;
}
static bool Matchcom_apple_quicktime( vlc_meta_t *p_meta, const char *psz_naming, MP4_Box_t *p_box )
{
bool b_matched = false;
for( unsigned i = 0; !b_matched && com_apple_quicktime_tometa[i].psz_naming; i++ )
{
if( !strcmp( psz_naming, com_apple_quicktime_tometa[i].psz_naming ) )
{
b_matched = true;
char *psz_utf = ExtractString( p_box );
if( psz_utf )
{
vlc_meta_Set( p_meta, com_apple_quicktime_tometa[i].meta_type, psz_utf );
free( psz_utf );
}
break;
}
}
for( unsigned i = 0; !b_matched && com_apple_quicktime_toextrameta[i].psz_naming; i++ )
{
if( !strcmp( psz_naming, com_apple_quicktime_toextrameta[i].psz_naming ) )
{
b_matched = true;
char *psz_utf = ExtractString( p_box );
if( psz_utf )
{
vlc_meta_AddExtra( p_meta, _(com_apple_quicktime_toextrameta[i].psz_metadata), psz_utf );
free( psz_utf );
}
break;
}
}
return b_matched;
}
static void SetupmdirMeta( vlc_meta_t *p_meta, MP4_Box_t *p_box )
{
bool b_matched = true;
/* XXX Becarefull p_udta can have box that are not 0xa9xx */
switch( p_box->i_type )
{
case ATOM_gnre:
if( p_box->data.p_gnre && p_box->data.p_gnre->i_genre <= NUM_GENRES )
vlc_meta_SetGenre( p_meta, ppsz_genres[p_box->data.p_gnre->i_genre - 1] );
break;
case ATOM_trkn:
{
if ( p_box->data.p_trkn )
{
char psz_trck[11];
snprintf( psz_trck, sizeof( psz_trck ), "%i",
p_box->data.p_trkn->i_track_number );
vlc_meta_SetTrackNum( p_meta, psz_trck );
if( p_box->data.p_trkn->i_track_total > 0 )
{
snprintf( psz_trck, sizeof( psz_trck ), "%i",
p_box->data.p_trkn->i_track_total );
vlc_meta_Set( p_meta, vlc_meta_TrackTotal, psz_trck );
}
}
break;
}
default:
b_matched = false;
break;
}
if ( !b_matched )
MatchXA9Type( p_meta, p_box->i_type, p_box );
}
static void SetupmdtaMeta( vlc_meta_t *p_meta, MP4_Box_t *p_box, MP4_Box_t *p_keys )
{
if ( !p_keys || !BOXDATA(p_keys) || BOXDATA(p_keys)->i_entry_count == 0 )
return;
if ( !p_box->i_index || p_box->i_index > BOXDATA(p_keys)->i_entry_count )
return;
const char *psz_naming = BOXDATA(p_keys)->p_entries[p_box->i_index - 1].psz_value;
const uint32_t i_namespace = BOXDATA(p_keys)->p_entries[p_box->i_index - 1].i_namespace;
if( i_namespace == HANDLER_mdta )
{
if ( !strncmp( "com.apple.quicktime.", psz_naming, 20 ) )
{
Matchcom_apple_quicktime( p_meta, psz_naming + 20, p_box );
}
}
else if ( i_namespace == ATOM_udta )
{
/* Regular atom inside... could that be even more complex ??? */
char *psz_utf = ExtractString( p_box );
if ( psz_utf )
{
if ( strlen(psz_utf) == 4 )
{
MatchXA9Type( p_meta,
VLC_FOURCC(psz_utf[0],psz_utf[1],psz_utf[2],psz_utf[3]),
p_box );
}
free( psz_utf );
}
}
}
void SetupMeta( vlc_meta_t *p_meta, MP4_Box_t *p_udta )
{
uint32_t i_handler = 0;
if ( p_udta->p_father )
i_handler = p_udta->i_handler;
for( MP4_Box_t *p_box = p_udta->p_first; p_box; p_box = p_box->p_next )
{
switch( i_handler )
{
case HANDLER_mdta:
{
MP4_Box_t *p_keys = MP4_BoxGet( p_udta->p_father, "keys" );
SetupmdtaMeta( p_meta, p_box, p_keys );
break;
}
case HANDLER_mdir:
default:
SetupmdirMeta( p_meta, p_box );
break;
}
}
}
......@@ -34,13 +34,10 @@
#include <vlc_demux.h>
#include <vlc_charset.h> /* EnsureUTF8 */
#include <vlc_meta.h> /* vlc_meta_t, vlc_meta_ */
#include <vlc_input.h>
#include <vlc_aout.h>
#include <assert.h>
#include "id3genres.h" /* for ATOM_gnre */
/*****************************************************************************
* Module descriptor
*****************************************************************************/
......@@ -1528,149 +1525,8 @@ static int Control( demux_t *p_demux, int i_query, va_list args )
if( p_udta == NULL && p_data == NULL )
return VLC_EGENERIC;
for( const MP4_Box_t * p_string = p_udta->p_first; p_string != NULL;
p_string = p_string->p_next )
{
if( !p_string || !BOXDATA(p_string) ) /* !WARN could be data atoms ! */
continue;
/* FIXME FIXME: should convert from whatever the character
* encoding of MP4 meta data is to UTF-8. */
#define SET(fct) do { char *psz_utf = strdup( BOXDATA(p_string)->psz_text ? BOXDATA(p_string)->psz_text : "" ); \
if( psz_utf ) { EnsureUTF8( psz_utf ); \
fct( p_meta, psz_utf ); free( psz_utf ); } } while(0)
/* XXX Becarefull p_udta can have box that are not 0xa9xx */
switch( p_string->i_type )
{
case ATOM_0xa9nam: /* Full name */
SET( vlc_meta_SetTitle );
break;
case ATOM_0xa9aut:
SET( vlc_meta_SetArtist );
break;
case ATOM_0xa9ART:
SET( vlc_meta_SetArtist );
break;
case ATOM_0xa9cpy:
SET( vlc_meta_SetCopyright );
break;
case ATOM_0xa9day: /* Creation Date */
SET( vlc_meta_SetDate );
break;
case ATOM_0xa9des: /* Description */
SET( vlc_meta_SetDescription );
break;
case ATOM_0xa9gen: /* Genre */
SET( vlc_meta_SetGenre );
break;
case ATOM_gnre:
if( p_string->data.p_gnre->i_genre <= NUM_GENRES )
vlc_meta_SetGenre( p_meta, ppsz_genres[p_string->data.p_gnre->i_genre - 1] );
break;
case ATOM_0xa9alb: /* Album */
SET( vlc_meta_SetAlbum );
break;
case ATOM_0xa9trk: /* Track */
SET( vlc_meta_SetTrackNum );
break;
case ATOM_trkn:
{
char psz_trck[11];
snprintf( psz_trck, sizeof( psz_trck ), "%i",
p_string->data.p_trkn->i_track_number );
vlc_meta_SetTrackNum( p_meta, psz_trck );
if( p_string->data.p_trkn->i_track_total > 0 )
{
snprintf( psz_trck, sizeof( psz_trck ), "%i",
p_string->data.p_trkn->i_track_total );
vlc_meta_Set( p_meta, vlc_meta_TrackTotal, psz_trck );
}
break;
}
case ATOM_0xa9cmt: /* Commment */
SET( vlc_meta_SetDescription );
break;
case ATOM_0xa9url: /* URL */
SET( vlc_meta_SetURL );
break;
case ATOM_0xa9too: /* Encoder Tool */
case ATOM_0xa9enc: /* Encoded By */
SET( vlc_meta_SetEncodedBy );
break;
case ATOM_0xa9pub:
SET( vlc_meta_SetPublisher );
break;
SetupMeta( p_meta, p_udta );
case ATOM_0xa9dir:
SET( vlc_meta_SetDirector );
break;
default:
break;
}
#undef SET
static const struct { uint32_t xa9_type; char metadata[25]; } xa9typetoextrameta[] =
{
{ ATOM_0xa9wrt, N_("Writer") },
{ ATOM_0xa9com, N_("Composer") },
{ ATOM_0xa9prd, N_("Producer") },
{ ATOM_0xa9inf, N_("Information") },
{ ATOM_0xa9dis, N_("Disclaimer") },
{ ATOM_0xa9req, N_("Requirements") },
{ ATOM_0xa9fmt, N_("Original Format") },
{ ATOM_0xa9dsa, N_("Display Source As") },
{ ATOM_0xa9hst, N_("Host Computer") },
{ ATOM_0xa9prf, N_("Performers") },
{ ATOM_0xa9ope, N_("Original Performer") },
{ ATOM_0xa9src, N_("Providers Source Content") },
{ ATOM_0xa9wrn, N_("Warning") },
{ ATOM_0xa9swr, N_("Software") },
{ ATOM_0xa9lyr, N_("Lyrics") },
{ ATOM_0xa9mak, N_("Record Company") },
{ ATOM_0xa9mod, N_("Model") },
{ ATOM_0xa9PRD, N_("Product") },
{ ATOM_0xa9grp, N_("Grouping") },
{ ATOM_0xa9gen, N_("Genre") },
{ ATOM_0xa9st3, N_("Sub-Title") },
{ ATOM_0xa9arg, N_("Arranger") },
{ ATOM_0xa9ard, N_("Art Director") },
{ ATOM_0xa9cak, N_("Copyright Acknowledgement") },
{ ATOM_0xa9con, N_("Conductor") },
{ ATOM_0xa9des, N_("Song Description") },
{ ATOM_0xa9lnt, N_("Liner Notes") },
{ ATOM_0xa9phg, N_("Phonogram Rights") },
{ ATOM_0xa9pub, N_("Publisher") },
{ ATOM_0xa9sne, N_("Sound Engineer") },
{ ATOM_0xa9sol, N_("Soloist") },
{ ATOM_0xa9thx, N_("Thanks") },
{ ATOM_0xa9xpd, N_("Executive Producer") },
{ ATOM_vndr, N_("Vendor") },
{ 0, "" },
};
for( unsigned i = 0; xa9typetoextrameta[i].xa9_type; i++ )
{
if( p_string->i_type == xa9typetoextrameta[i].xa9_type )
{
assert( BOXDATA(p_string) );
char *psz_utf = strdup( BOXDATA(p_string)->psz_text ? BOXDATA(p_string)->psz_text : "" );
if( psz_utf )
{
EnsureUTF8( psz_utf );
vlc_meta_AddExtra( p_meta, _(xa9typetoextrameta[i].metadata), psz_utf );
free( psz_utf );
}
break;
}
}
}
return VLC_SUCCESS;
}
......
......@@ -155,4 +155,5 @@ struct mp4_fragment_t
int SetupVideoES( demux_t *p_demux, mp4_track_t *p_track, MP4_Box_t *p_sample );
int SetupAudioES( demux_t *p_demux, mp4_track_t *p_track, MP4_Box_t *p_sample );
int SetupSpuES( demux_t *p_demux, mp4_track_t *p_track, MP4_Box_t *p_sample );
void SetupMeta( vlc_meta_t *p_meta, MP4_Box_t *p_udta );
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment