taglib.cpp 39.1 KB
Newer Older
1 2 3
/*****************************************************************************
 * taglib.cpp: Taglib tag parser/writer
 *****************************************************************************
4
 * Copyright (C) 2003-2011 VLC authors and VideoLAN
Antoine Cellerier's avatar
Antoine Cellerier committed
5
 * $Id$
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Rafaël Carré <funman@videolanorg>
9
 *          Rémi Duraffort <ivoire@videolan.org>
10
 *
11 12 13
 * 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
14 15 16 17
 * (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
18 19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
20
 *
21 22 23
 * 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.
24
 *****************************************************************************/
25

26 27 28 29
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

30
#include <vlc_common.h>
31
#include <vlc_plugin.h>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
32 33 34
#include <vlc_demux.h>              /* demux_meta_t */
#include <vlc_strings.h>            /* vlc_b64_decode_binary */
#include <vlc_input.h>              /* for attachment_new */
Angelo Haller's avatar
Angelo Haller committed
35 36 37 38 39
#include <vlc_url.h>                /* make_path */
#include <vlc_mime.h>               /* mime type */
#include <vlc_fs.h>

#include <sys/stat.h>
40

41
#ifdef _WIN32
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
42
# include <vlc_charset.h>
43 44 45 46
# include <io.h>
#else
# include <unistd.h>
#endif
47

48

49
// Taglib headers
50
#ifdef _WIN32
51 52
# define TAGLIB_STATIC
#endif
53 54 55 56 57 58
#include <taglib.h>
#define VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
#define TAGLIB_VERSION VERSION_INT(TAGLIB_MAJOR_VERSION, \
                                   TAGLIB_MINOR_VERSION, \
                                   TAGLIB_PATCH_VERSION)

59
#include <fileref.h>
60
#include <tag.h>
61 62
#include <tbytevector.h>

63 64 65
#if TAGLIB_VERSION >= VERSION_INT(1,7,0)
# define TAGLIB_HAVE_APEFILE_H
# include <apefile.h>
66 67 68 69
# ifdef TAGLIB_WITH_ASF                     // ASF pictures comes with v1.7.0
#  define TAGLIB_HAVE_ASFPICTURE_H
#  include <asffile.h>
# endif
70 71
#endif

.'s avatar
. committed
72 73 74 75
#if TAGLIB_VERSION >= VERSION_INT(1,9,0)
# include <opusfile.h>
#endif

76
#include <apetag.h>
77 78 79 80 81
#include <flacfile.h>
#include <mpcfile.h>
#include <mpegfile.h>
#include <oggfile.h>
#include <oggflacfile.h>
82
#include "../demux/xiph_metadata.h"
83

84 85
#include <aifffile.h>
#include <wavfile.h>
86

87
#if defined(TAGLIB_WITH_MP4)
88 89 90
# include <mp4file.h>
#endif

91 92
#include <speexfile.h>
#include <trueaudiofile.h>
93
#include <vorbisfile.h>
94 95
#include <wavpackfile.h>

96
#include <attachedpictureframe.h>
97
#include <textidentificationframe.h>
98 99
#include <uniquefileidentifierframe.h>

Rafaël Carré's avatar
Rafaël Carré committed
100 101
// taglib is not thread safe
static vlc_mutex_t taglib_lock = VLC_STATIC_MUTEX;
102

103 104 105
// Local functions
static int ReadMeta    ( vlc_object_t * );
static int WriteMeta   ( vlc_object_t * );
106

107 108 109 110 111 112 113
vlc_module_begin ()
    set_capability( "meta reader", 1000 )
    set_callbacks( ReadMeta, NULL )
    add_submodule ()
        set_capability( "meta writer", 50 )
        set_callbacks( WriteMeta, NULL )
vlc_module_end ()
Clément Stenac's avatar
Clément Stenac committed
114

115 116
using namespace TagLib;

117 118 119 120 121 122 123 124 125 126 127 128
static void ExtractTrackNumberValues( vlc_meta_t* p_meta, const char *psz_value )
{
    unsigned int i_trknum, i_trktot;
    if( sscanf( psz_value, "%u/%u", &i_trknum, &i_trktot ) == 2 )
    {
        char psz_trck[11];
        snprintf( psz_trck, sizeof( psz_trck ), "%u", i_trknum );
        vlc_meta_SetTrackNum( p_meta, psz_trck );
        snprintf( psz_trck, sizeof( psz_trck ), "%u", i_trktot );
        vlc_meta_Set( p_meta, vlc_meta_TrackTotal, psz_trck );
    }
}
129 130

/**
Rémi Duraffort's avatar
Rémi Duraffort committed
131
 * Read meta information from APE tags
132
 * @param tag: the APE tag
133
 * @param p_demux_meta: the demuxer meta
134 135
 * @param p_meta: the meta
 */
136
static void ReadMetaFromAPE( APE::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
137
{
138 139
    APE::ItemListMap fields ( tag->itemListMap() );
    APE::ItemListMap::Iterator iter;
140

141 142 143 144
    iter = fields.find("COVER ART (FRONT)");
    if( iter != fields.end()
        && !iter->second.isEmpty()
        && !iter->second.type() == APE::Item::Binary)
145 146 147
    {
        input_attachment_t *p_attachment;

148
        const ByteVector picture = iter->second.binaryData();
149 150 151
        const char *p_data = picture.data();
        unsigned i_data = picture.size();

152
        /* Null terminated filename followed by the image data */
153
        size_t desc_len = strnlen(p_data, i_data);
154 155
        if( desc_len < i_data && IsUTF8( p_data ) )
        {
156
            const char *psz_name = p_data;
157
            const char *psz_mime = vlc_mime_Ext2Mime( psz_name );
158 159
            p_data += desc_len + 1; /* '\0' */
            i_data -= desc_len + 1;
160

161
            msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
162
                     psz_name, psz_mime, i_data );
163

164
            p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
165 166
                                    psz_name, p_data, i_data );
            if( p_attachment )
167
            {
168 169 170 171
                TAB_APPEND_CAST( (input_attachment_t**),
                                 p_demux_meta->i_attachments, p_demux_meta->attachments,
                                 p_attachment );

172 173 174 175 176 177 178
                char *psz_url;
                if( asprintf( &psz_url, "attachment://%s", p_attachment->psz_name ) != -1 )
                {
                    vlc_meta_SetArtURL( p_meta, psz_url );
                    free( psz_url );
                }
            }
179
        }
180 181

        fields.erase(iter);
182 183
    }

184
#define SET( keyName, metaName ) \
185 186 187 188 189
    iter = fields.find(keyName); \
    if( iter != fields.end() && !iter->second.isEmpty() ) { \
        vlc_meta_Set##metaName( p_meta, iter->second.toString().toCString( true ) ); \
        fields.erase(iter); \
    }
190 191

#define SET_EXTRA( keyName, metaName ) \
192 193 194 195 196
    iter = fields.find( keyName ); \
    if( iter != fields.end() && !iter->second.isEmpty() ) { \
        vlc_meta_AddExtra( p_meta, metaName, iter->second.toString().toCString( true ) ); \
        fields.erase(iter); \
    }
197

198 199 200 201 202
    SET( "ALBUM", Album );
    SET( "ARTIST", Artist );
    SET( "COMMENT", Description );
    SET( "GENRE", Genre );
    SET( "TITLE", Title );
203 204 205
    SET( "COPYRIGHT", Copyright );
    SET( "LANGUAGE", Language );
    SET( "PUBLISHER", Publisher );
206
    SET( "MUSICBRAINZ_TRACKID", TrackID );
207

208 209
    SET_EXTRA( "MUSICBRAINZ_ALBUMID", VLC_META_EXTRA_MB_ALBUMID );

210
#undef SET
211
#undef SET_EXTRA
212 213

    /* */
214 215 216 217 218 219 220 221 222
    iter = fields.find( "TRACK" );
    if( iter != fields.end() && !iter->second.isEmpty() )
    {
        ExtractTrackNumberValues( p_meta, iter->second.toString().toCString( true ) );
        fields.erase( iter );
    }

    /* Remainings */
    for( iter = fields.begin(); iter != fields.end(); ++iter )
223
    {
224 225 226 227 228 229 230 231 232
        if( iter->second.isEmpty() )
            continue;

        if( iter->second.type() != APE::Item::Text )
            continue;

        vlc_meta_AddExtra( p_meta,
                           iter->first.toCString( true ),
                           iter->second.toString().toCString( true ) );
233
    }
234 235
}

236

237
/**
238 239
 * Read meta information from ASF tags
 * @param tag: the ASF tag
240 241 242 243 244
 * @param p_demux_meta: the demuxer meta
 * @param p_meta: the meta
 */
static void ReadMetaFromASF( ASF::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
{
245 246 247 248 249 250 251 252 253

    ASF::AttributeList list;
#define SET( keyName, metaName )                                                     \
    if( tag->attributeListMap().contains(keyName) )                                  \
    {                                                                                \
        list = tag->attributeListMap()[keyName];                                     \
        vlc_meta_Set##metaName( p_meta, list.front().toString().toCString( true ) ); \
    }

254 255 256 257 258 259 260
#define SET_EXTRA( keyName, metaName )                                                     \
    if( tag->attributeListMap().contains(keyName) )                                  \
    {                                                                                \
        list = tag->attributeListMap()[keyName];                                     \
        vlc_meta_AddExtra( p_meta, metaName, list.front().toString().toCString( true ) ); \
    }

261
    SET("MusicBrainz/Track Id", TrackID );
262
    SET_EXTRA("MusicBrainz/Album Id", VLC_META_EXTRA_MB_ALBUMID );
263 264

#undef SET
265
#undef SET_EXTRA
266 267

#ifdef TAGLIB_HAVE_ASFPICTURE_H
268
    // List the pictures
269
    list = tag->attributeListMap()["WM/Picture"];
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    ASF::AttributeList::Iterator iter;
    for( iter = list.begin(); iter != list.end(); iter++ )
    {
        const ASF::Picture asfPicture = (*iter).toPicture();
        const ByteVector picture = asfPicture.picture();
        const char *psz_mime = asfPicture.mimeType().toCString();
        const char *p_data = picture.data();
        const unsigned i_data = picture.size();
        char *psz_name;
        input_attachment_t *p_attachment;

        if( asfPicture.description().size() > 0 )
            psz_name = strdup( asfPicture.description().toCString( true ) );
        else
        {
            if( asprintf( &psz_name, "%i", asfPicture.type() ) == -1 )
                continue;
        }

289 290 291
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
                 psz_name, psz_mime, i_data );

292 293 294 295 296 297 298
        p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
                                psz_name, p_data, i_data );
        if( p_attachment )
            TAB_APPEND_CAST( (input_attachment_t**),
                             p_demux_meta->i_attachments, p_demux_meta->attachments,
                             p_attachment );
        char *psz_url;
299 300 301 302 303 304
        if( asprintf( &psz_url, "attachment://%s", psz_name ) != -1 )
        {
            vlc_meta_SetArtURL( p_meta, psz_url );
            free( psz_url );
        }
        free( psz_name );
305 306
    }
#endif
307
}
308 309


310
/**
311
 * Read meta information from id3v2 tags
312
 * @param tag: the id3v2 tag
313
 * @param p_demux_meta: the demuxer meta
314 315
 * @param p_meta: the meta
 */
316
static void ReadMetaFromId3v2( ID3v2::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
317 318 319 320 321
{
    // Get the unique file identifier
    ID3v2::FrameList list = tag->frameListMap()["UFID"];
    ID3v2::FrameList::Iterator iter;
    for( iter = list.begin(); iter != list.end(); iter++ )
322
    {
323 324
        ID3v2::UniqueFileIdentifierFrame* p_ufid =
                dynamic_cast<ID3v2::UniqueFileIdentifierFrame*>(*iter);
325 326
        if( !p_ufid )
            continue;
327 328 329 330 331 332 333
        const char *owner = p_ufid->owner().toCString();
        if (!strcmp( owner, "http://musicbrainz.org" ))
        {
            /* ID3v2 UFID contains up to 64 bytes binary data
             * but in our case it will be a '\0'
             * terminated string */
            char psz_ufid[64];
334
            int max_size = __MIN( p_ufid->identifier().size(), 63);
335
            strncpy( psz_ufid, p_ufid->identifier().data(), max_size );
336
            psz_ufid[max_size] = '\0';
337 338 339
            vlc_meta_SetTrackID( p_meta, psz_ufid );
        }
    }
340

341 342 343 344 345 346
    // Get the use text
    list = tag->frameListMap()["TXXX"];
    for( iter = list.begin(); iter != list.end(); iter++ )
    {
        ID3v2::UserTextIdentificationFrame* p_txxx =
                dynamic_cast<ID3v2::UserTextIdentificationFrame*>(*iter);
347 348
        if( !p_txxx )
            continue;
349 350 351 352 353
        if( !strcmp( p_txxx->description().toCString( true ), "TRACKTOTAL" ) )
        {
            vlc_meta_Set( p_meta, vlc_meta_TrackTotal, p_txxx->fieldList().back().toCString( true ) );
            continue;
        }
354 355 356 357 358
        if( !strcmp( p_txxx->description().toCString( true ), "MusicBrainz Album Id" ) )
        {
            vlc_meta_AddExtra( p_meta, VLC_META_EXTRA_MB_ALBUMID, p_txxx->fieldList().back().toCString( true ) );
            continue;
        }
359
        vlc_meta_AddExtra( p_meta, p_txxx->description().toCString( true ),
360
                           p_txxx->fieldList().back().toCString( true ) );
361 362
    }

Rémi Duraffort's avatar
Rémi Duraffort committed
363
    // Get some more information
364 365 366 367 368 369 370 371 372 373 374 375
#define SET( tagName, metaName )                                               \
    list = tag->frameListMap()[tagName];                                       \
    if( !list.isEmpty() )                                                      \
        vlc_meta_Set##metaName( p_meta,                                        \
                                (*list.begin())->toString().toCString( true ) );

    SET( "TCOP", Copyright );
    SET( "TENC", EncodedBy );
    SET( "TLAN", Language );
    SET( "TPUB", Publisher );

#undef SET
376

377 378 379 380 381 382 383
    /* */
    list = tag->frameListMap()["TRCK"];
    if( !list.isEmpty() )
    {
        ExtractTrackNumberValues( p_meta, (*list.begin())->toString().toCString( true ) );
    }

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
    /* Preferred type of image
     * The 21 types are defined in id3v2 standard:
     * http://www.id3.org/id3v2.4.0-frames */
    static const int pi_cover_score[] = {
        0,  /* Other */
        5,  /* 32x32 PNG image that should be used as the file icon */
        4,  /* File icon of a different size or format. */
        20, /* Front cover image of the album. */
        19, /* Back cover image of the album. */
        13, /* Inside leaflet page of the album. */
        18, /* Image from the album itself. */
        17, /* Picture of the lead artist or soloist. */
        16, /* Picture of the artist or performer. */
        14, /* Picture of the conductor. */
        15, /* Picture of the band or orchestra. */
        9,  /* Picture of the composer. */
        8,  /* Picture of the lyricist or text writer. */
        7,  /* Picture of the recording location or studio. */
        10, /* Picture of the artists during recording. */
        11, /* Picture of the artists during performance. */
        6,  /* Picture from a movie or video related to the track. */
        1,  /* Picture of a large, coloured fish. */
        12, /* Illustration related to the track. */
        3,  /* Logo of the band or performer. */
        2   /* Logo of the publisher (record company). */
    };
410
    #define PI_COVER_SCORE_SIZE (sizeof (pi_cover_score) / sizeof (pi_cover_score[0]))
411 412 413 414 415 416
    int i_score = -1;

    // Try now to get embedded art
    list = tag->frameListMap()[ "APIC" ];
    if( list.isEmpty() )
        return;
417

418
    for( iter = list.begin(); iter != list.end(); iter++ )
419 420 421
    {
        ID3v2::AttachedPictureFrame* p_apic =
            dynamic_cast<ID3v2::AttachedPictureFrame*>(*iter);
422 423
        if( !p_apic )
            continue;
424 425
        input_attachment_t *p_attachment;

426 427
        const char *psz_mime;
        char *psz_name, *psz_description;
428

429 430
        // Get the mime and description of the image.
        // If the description is empty, take the type as a description
431
        psz_mime = p_apic->mimeType().toCString( true );
432 433 434 435 436 437 438 439 440 441 442
        if( p_apic->description().size() > 0 )
            psz_description = strdup( p_apic->description().toCString( true ) );
        else
        {
            if( asprintf( &psz_description, "%i", p_apic->type() ) == -1 )
                psz_description = NULL;
        }

        if( !psz_description )
            continue;
        psz_name = psz_description;
443 444 445 446 447 448 449 450

        /* some old iTunes version not only sets incorrectly the mime type
         * or the description of the image,
         * but also embeds incorrectly the image.
         * Recent versions seem to behave correctly */
        if( !strncmp( psz_mime, "PNG", 3 ) ||
            !strncmp( psz_name, "\xC2\x89PNG", 5 ) )
        {
451
            msg_Warn( p_demux_meta, "Invalid picture embedded by broken iTunes version" );
452 453
            free( psz_description );
            continue;
454 455
        }

456 457 458
        const ByteVector picture = p_apic->picture();
        const char *p_data = picture.data();
        const unsigned i_data = picture.size();
459

460
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
461 462 463 464
                 psz_name, psz_mime, i_data );

        p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
                                psz_description, p_data, i_data );
465 466 467 468 469 470 471 472
        if( !p_attachment )
        {
            free( psz_description );
            continue;
        }
        TAB_APPEND_CAST( (input_attachment_t**),
                         p_demux_meta->i_attachments, p_demux_meta->attachments,
                         p_attachment );
473
        free( psz_description );
474

475 476 477 478 479
        unsigned i_pic_type = p_apic->type();
        if( i_pic_type >= PI_COVER_SCORE_SIZE )
            i_pic_type = 0; // Defaults to "Other"

        if( pi_cover_score[i_pic_type] > i_score )
480
        {
481
            i_score = pi_cover_score[i_pic_type];
482 483 484
            char *psz_url;
            if( asprintf( &psz_url, "attachment://%s",
                          p_attachment->psz_name ) == -1 )
485
                continue;
486 487 488 489
            vlc_meta_SetArtURL( p_meta, psz_url );
            free( psz_url );
        }
    }
490 491
}

492

493
/**
Rémi Duraffort's avatar
Rémi Duraffort committed
494
 * Read the meta information from XiphComments
495
 * @param tag: the Xiph Comment
496
 * @param p_demux_meta: the demuxer meta
497 498
 * @param p_meta: the meta
 */
499
static void ReadMetaFromXiph( Ogg::XiphComment* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
500
{
501
    StringList list;
502
    bool hasTrackTotal = false;
Rémi Duraffort's avatar
Rémi Duraffort committed
503
#define SET( keyName, metaName )                                               \
504
    list = tag->fieldListMap()[keyName];                                       \
Rémi Duraffort's avatar
Rémi Duraffort committed
505 506 507
    if( !list.isEmpty() )                                                      \
        vlc_meta_Set##metaName( p_meta, (*list.begin()).toCString( true ) );

508 509 510 511 512
#define SET_EXTRA( keyName, metaName ) \
    list = tag->fieldListMap()[keyName]; \
    if( !list.isEmpty() ) \
        vlc_meta_AddExtra( p_meta, keyName, (*list.begin()).toCString( true ) );

Rémi Duraffort's avatar
Rémi Duraffort committed
513
    SET( "COPYRIGHT", Copyright );
.'s avatar
. committed
514 515 516 517 518
    SET( "ORGANIZATION", Publisher );
    SET( "DATE", Date );
    SET( "ENCODER", EncodedBy );
    SET( "RATING", Rating );
    SET( "LANGUAGE", Language );
519
    SET( "MUSICBRAINZ_TRACKID", TrackID );
520 521

    SET_EXTRA( "MUSICBRAINZ_ALBUMID", VLC_META_EXTRA_MB_ALBUMID );
Rémi Duraffort's avatar
Rémi Duraffort committed
522
#undef SET
523
#undef SET_EXTRA
524

525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    list = tag->fieldListMap()["TRACKNUMBER"];
    if( !list.isEmpty() )
    {
        const char *psz_value;
        unsigned short u_track;
        unsigned short u_total;
        psz_value = (*list.begin()).toCString( true );
        if( sscanf( psz_value, "%hu/%hu", &u_track, &u_total ) == 2)
        {
            char str[6];
            snprintf(str, 6, "%u", u_track);
            vlc_meta_SetTrackNum( p_meta, str);
            snprintf(str, 6, "%u", u_total);
            vlc_meta_SetTrackTotal( p_meta, str);
            hasTrackTotal = true;
        }
        else
            vlc_meta_SetTrackNum( p_meta, psz_value);
    }
    if( !hasTrackTotal )
    {
        list = tag->fieldListMap()["TRACKTOTAL"];
        if( list.isEmpty() )
            list = tag->fieldListMap()["TOTALTRACKS"];
        if( !list.isEmpty() )
            vlc_meta_SetTrackTotal( p_meta, (*list.begin()).toCString( true ) );
    }

553 554 555 556 557 558
    // Try now to get embedded art
    StringList mime_list = tag->fieldListMap()[ "COVERARTMIME" ];
    StringList art_list = tag->fieldListMap()[ "COVERART" ];

    input_attachment_t *p_attachment;

559 560 561 562 563 564
    if( mime_list.size() != 0 && art_list.size() != 0 )
    {
        // We get only the first covert art
        if( mime_list.size() > 1 || art_list.size() > 1 )
            msg_Warn( p_demux_meta, "Found %i embedded arts, so using only the first one",
                    art_list.size() );
565

566 567 568
        const char* psz_name = "cover";
        const char* psz_mime = mime_list[0].toCString(true);
        const char* psz_description = "cover";
569

570 571
        uint8_t *p_data;
        int i_data = vlc_b64_decode_binary( &p_data, art_list[0].toCString(true) );
572

573 574
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %i bytes",
                psz_name, psz_mime, i_data );
575

576 577 578 579 580 581 582 583 584 585 586
        p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
                psz_description, p_data, i_data );
        free( p_data );
    }
    else
    {
        art_list = tag->fieldListMap()[ "METADATA_BLOCK_PICTURE" ];
        if( art_list.size() == 0 )
            return;

        uint8_t *p_data;
587 588
        int i_cover_score;
        int i_cover_idx;
589
        int i_data = vlc_b64_decode_binary( &p_data, art_list[0].toCString(true) );
590 591 592 593
        i_cover_score = i_cover_idx = 0;
        /* TODO: Use i_cover_score / i_cover_idx to select the picture. */
        p_attachment = ParseFlacPicture( p_data, i_data, 0,
            &i_cover_score, &i_cover_idx );
594 595
    }

596 597 598 599
    if (p_attachment) {
        TAB_APPEND_CAST( (input_attachment_t**),
                p_demux_meta->i_attachments, p_demux_meta->attachments,
                p_attachment );
600

601 602 603 604 605
        char *psz_url;
        if( asprintf( &psz_url, "attachment://%s", p_attachment->psz_name ) != -1 ) {
            vlc_meta_SetArtURL( p_meta, psz_url );
            free( psz_url );
        }
606
    }
607 608
}

Rémi Duraffort's avatar
Rémi Duraffort committed
609

610
#if defined(TAGLIB_WITH_MP4)
Rémi Duraffort's avatar
Rémi Duraffort committed
611 612 613 614 615 616
/**
 * Read the meta information from mp4 specific tags
 * @param tag: the mp4 tag
 * @param p_demux_meta: the demuxer meta
 * @param p_meta: the meta
 */
617
static void ReadMetaFromMP4( MP4::Tag* tag, demux_meta_t *p_demux_meta, vlc_meta_t* p_meta )
618
{
619 620 621 622 623 624 625
    MP4::Item list;
#define SET( keyName, metaName )                                                             \
    if( tag->itemListMap().contains(keyName) )                                               \
    {                                                                                        \
        list = tag->itemListMap()[keyName];                                                  \
        vlc_meta_Set##metaName( p_meta, list.toStringList().front().toCString( true ) );     \
    }
626 627 628 629 630 631
#define SET_EXTRA( keyName, metaName )                                                   \
    if( tag->itemListMap().contains(keyName) )                                  \
    {                                                                                \
        list = tag->itemListMap()[keyName];                                     \
        vlc_meta_AddExtra( p_meta, metaName, list.toStringList().front().toCString( true ) ); \
    }
632 633

    SET("----:com.apple.iTunes:MusicBrainz Track Id", TrackID );
634
    SET_EXTRA("----:com.apple.iTunes:MusicBrainz Album Id", VLC_META_EXTRA_MB_ALBUMID );
635 636

#undef SET
637
#undef SET_EXTRA
638

639 640 641 642 643
    if( tag->itemListMap().contains("covr") )
    {
        MP4::CoverArtList list = tag->itemListMap()["covr"].toCoverArtList();
        const char *psz_format = list[0].format() == MP4::CoverArt::PNG ? "image/png" : "image/jpeg";

644 645 646
        msg_Dbg( p_demux_meta, "Found embedded art (%s) is %i bytes",
                 psz_format, list[0].data().size() );

647 648 649 650 651 652 653 654 655 656
        input_attachment_t *p_attachment =
                vlc_input_attachment_New( "cover", psz_format, "cover",
                                          list[0].data().data(), list[0].data().size() );
        TAB_APPEND_CAST( (input_attachment_t**),
                         p_demux_meta->i_attachments, p_demux_meta->attachments,
                         p_attachment );
        vlc_meta_SetArtURL( p_meta, "attachment://cover" );
    }
}
#endif
657

Rémi Duraffort's avatar
Rémi Duraffort committed
658

659 660 661 662 663 664 665
/**
 * Get the tags from the file using TagLib
 * @param p_this: the demux object
 * @return VLC_SUCCESS if the operation success
 */
static int ReadMeta( vlc_object_t* p_this)
{
Rafaël Carré's avatar
Rafaël Carré committed
666
    vlc_mutex_locker locker (&taglib_lock);
667 668
    demux_meta_t*   p_demux_meta = (demux_meta_t *)p_this;
    demux_t*        p_demux = p_demux_meta->p_demux;
669
    vlc_meta_t*     p_meta;
670
    FileRef f;
671 672

    p_demux_meta->p_meta = NULL;
673
    if( strcmp( p_demux->psz_access, "file" ) )
674
        return VLC_EGENERIC;
675

676
    char *psz_path = strdup( p_demux->psz_file );
677 678
    if( !psz_path )
        return VLC_ENOMEM;
679

680
#if defined(_WIN32)
681 682
    wchar_t *wpath = ToWide( psz_path );
    if( wpath == NULL )
683 684
    {
        free( psz_path );
685
        return VLC_EGENERIC;
686
    }
687
    f = FileRef( wpath );
688
    free( wpath );
689
#else
690
    f = FileRef( psz_path );
691
#endif
Ilkka Ollakka's avatar
Ilkka Ollakka committed
692
    free( psz_path );
693

Rafaël Carré's avatar
Rafaël Carré committed
694 695
    if( f.isNull() )
        return VLC_EGENERIC;
696
    if( !f.tag() || f.tag()->isEmpty() )
Rafaël Carré's avatar
Rafaël Carré committed
697
        return VLC_EGENERIC;
698

699
    p_demux_meta->p_meta = p_meta = vlc_meta_New();
700 701 702 703 704 705 706
    if( !p_meta )
        return VLC_ENOMEM;


    // Read the tags from the file
    Tag* p_tag = f.tag();

Rémi Duraffort's avatar
Rémi Duraffort committed
707
#define SET( tag, meta )                                                       \
708 709
    if( !p_tag->tag().isNull() && !p_tag->tag().isEmpty() )                    \
        vlc_meta_Set##meta( p_meta, p_tag->tag().toCString(true) )
Rémi Duraffort's avatar
Rémi Duraffort committed
710
#define SETINT( tag, meta )                                                    \
711 712 713 714 715 716
    if( p_tag->tag() )                                                         \
    {                                                                          \
        char psz_tmp[10];                                                      \
        snprintf( psz_tmp, 10, "%d", p_tag->tag() );                           \
        vlc_meta_Set##meta( p_meta, psz_tmp );                                 \
    }
Rafaël Carré's avatar
Rafaël Carré committed
717

Rémi Duraffort's avatar
Rémi Duraffort committed
718 719 720 721 722 723
    SET( title, Title );
    SET( artist, Artist );
    SET( album, Album );
    SET( comment, Description );
    SET( genre, Genre );
    SETINT( year, Date );
724
    SETINT( track, TrackNum );
Rafaël Carré's avatar
Rafaël Carré committed
725

726
#undef SETINT
Rafaël Carré's avatar
Rafaël Carré committed
727
#undef SET
728

729
    TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
730

731
    // Try now to read special tags
732
#ifdef TAGLIB_HAVE_APEFILE_H
733 734 735 736 737
    if( APE::File* ape = dynamic_cast<APE::File*>(f.file()) )
    {
        if( ape->APETag() )
            ReadMetaFromAPE( ape->APETag(), p_demux_meta, p_meta );
    }
738
    else
739
#endif
740
#ifdef TAGLIB_WITH_ASF
741 742 743 744 745 746
    if( ASF::File* asf = dynamic_cast<ASF::File*>(f.file()) )
    {
        if( asf->tag() )
            ReadMetaFromASF( asf->tag(), p_demux_meta, p_meta );
    }
    else
747 748
#endif
    if( FLAC::File* flac = dynamic_cast<FLAC::File*>(f.file()) )
749 750
    {
        if( flac->ID3v2Tag() )
751
            ReadMetaFromId3v2( flac->ID3v2Tag(), p_demux_meta, p_meta );
752
        else if( flac->xiphComment() )
753
            ReadMetaFromXiph( flac->xiphComment(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
754
    }
755
#if defined(TAGLIB_WITH_MP4)
756 757 758
    else if( MP4::File *mp4 = dynamic_cast<MP4::File*>(f.file()) )
    {
        if( mp4->tag() )
759
            ReadMetaFromMP4( mp4->tag(), p_demux_meta, p_meta );
760 761
    }
#endif
762
    else if( MPC::File* mpc = dynamic_cast<MPC::File*>(f.file()) )
Rafaël Carré's avatar
Rafaël Carré committed
763
    {
764
        if( mpc->APETag() )
765
            ReadMetaFromAPE( mpc->APETag(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
766
    }
767
    else if( MPEG::File* mpeg = dynamic_cast<MPEG::File*>(f.file()) )
Rafaël Carré's avatar
Rafaël Carré committed
768
    {
769
        if( mpeg->ID3v2Tag() )
770
            ReadMetaFromId3v2( mpeg->ID3v2Tag(), p_demux_meta, p_meta );
771
        else if( mpeg->APETag() )
772
            ReadMetaFromAPE( mpeg->APETag(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
773
    }
774
    else if( dynamic_cast<Ogg::File*>(f.file()) )
775 776
    {
        if( Ogg::FLAC::File* ogg_flac = dynamic_cast<Ogg::FLAC::File*>(f.file()))
777
            ReadMetaFromXiph( ogg_flac->tag(), p_demux_meta, p_meta );
778
        else if( Ogg::Speex::File* ogg_speex = dynamic_cast<Ogg::Speex::File*>(f.file()) )
779
            ReadMetaFromXiph( ogg_speex->tag(), p_demux_meta, p_meta );
780
        else if( Ogg::Vorbis::File* ogg_vorbis = dynamic_cast<Ogg::Vorbis::File*>(f.file()) )
781
            ReadMetaFromXiph( ogg_vorbis->tag(), p_demux_meta, p_meta );
.'s avatar
. committed
782 783 784 785
#if defined(TAGLIB_OPUSFILE_H)
        else if( Ogg::Opus::File* ogg_opus = dynamic_cast<Ogg::Opus::File*>(f.file()) )
            ReadMetaFromXiph( ogg_opus->tag(), p_demux_meta, p_meta );
#endif
786
    }
787
    else if( dynamic_cast<RIFF::File*>(f.file()) )
788 789
    {
        if( RIFF::AIFF::File* riff_aiff = dynamic_cast<RIFF::AIFF::File*>(f.file()) )
790
            ReadMetaFromId3v2( riff_aiff->tag(), p_demux_meta, p_meta );
791
        else if( RIFF::WAV::File* riff_wav = dynamic_cast<RIFF::WAV::File*>(f.file()) )
792
            ReadMetaFromId3v2( riff_wav->tag(), p_demux_meta, p_meta );
793
    }
794 795 796
    else if( TrueAudio::File* trueaudio = dynamic_cast<TrueAudio::File*>(f.file()) )
    {
        if( trueaudio->ID3v2Tag() )
797
            ReadMetaFromId3v2( trueaudio->ID3v2Tag(), p_demux_meta, p_meta );
798 799 800 801
    }
    else if( WavPack::File* wavpack = dynamic_cast<WavPack::File*>(f.file()) )
    {
        if( wavpack->APETag() )
802
            ReadMetaFromAPE( wavpack->APETag(), p_demux_meta, p_meta );
803
    }
804

Rafaël Carré's avatar
Rafaël Carré committed
805
    return VLC_SUCCESS;
806
}
807

808

809
/**
Rémi Duraffort's avatar
Rémi Duraffort committed
810
 * Write meta information to APE tags
811
 * @param tag: the APE tag
812
 * @param p_item: the input item
813
 */
814
static void WriteMetaToAPE( APE::Tag* tag, input_item_t* p_item )
815
{
816 817 818 819 820 821 822 823 824 825 826 827 828 829
    char* psz_meta;
#define WRITE( metaName, keyName )                      \
    psz_meta = input_item_Get##metaName( p_item );      \
    if( psz_meta )                                      \
    {                                                   \
        String key( keyName, String::UTF8 );            \
        String value( psz_meta, String::UTF8 );         \
        tag->addValue( key, value, true );              \
    }                                                   \
    free( psz_meta );

    WRITE( Copyright, "COPYRIGHT" );
    WRITE( Language, "LANGUAGE" );
    WRITE( Publisher, "PUBLISHER" );
830
    WRITE( TrackID, "MUSICBRAINZ_TRACKID" );
831
#undef WRITE
832 833 834 835 836 837 838 839
}


/**
 * Write meta information to id3v2 tags
 * @param tag: the id3v2 tag
 * @param p_input: the input item
 */
840
static void WriteMetaToId3v2( ID3v2::Tag* tag, input_item_t* p_item )
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
{
    char* psz_meta;
#define WRITE( metaName, tagName )                                            \
    psz_meta = input_item_Get##metaName( p_item );                            \
    if( psz_meta )                                                            \
    {                                                                         \
        ByteVector p_byte( tagName, 4 );                                      \
        tag->removeFrames( p_byte );                                         \
        ID3v2::TextIdentificationFrame* p_frame =                             \
            new ID3v2::TextIdentificationFrame( p_byte, String::UTF8 );       \
        p_frame->setText( psz_meta );                                         \
        tag->addFrame( p_frame );                                             \
    }                                                                         \
    free( psz_meta );

    WRITE( Copyright, "TCOP" );
    WRITE( EncodedBy, "TENC" );
    WRITE( Language,  "TLAN" );
    WRITE( Publisher, "TPUB" );

#undef WRITE
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
    /* Known TXXX frames */
    ID3v2::FrameList list = tag->frameListMap()["TXXX"];

#define WRITETXXX( metaName, txxName )\
    psz_meta = input_item_Get##metaName( p_item );                                       \
    if ( psz_meta )                                                                      \
    {                                                                                    \
        ID3v2::UserTextIdentificationFrame *p_txxx;                                      \
        for( ID3v2::FrameList::Iterator iter = list.begin(); iter != list.end(); iter++ )\
        {                                                                                \
            p_txxx = dynamic_cast<ID3v2::UserTextIdentificationFrame*>(*iter);           \
            if( !p_txxx )                                                                \
                continue;                                                                \
            if( !strcmp( p_txxx->description().toCString( true ), txxName ) )            \
            {                                                                            \
                p_txxx->setText( psz_meta );                                             \
                FREENULL( psz_meta );                                                    \
                break;                                                                   \
            }                                                                            \
        }                                                                                \
        if( psz_meta ) /* not found in existing custom fields */                         \
        {                                                                                \
            ByteVector p_byte( "TXXX", 4 );                                              \
            p_txxx = new ID3v2::UserTextIdentificationFrame( p_byte );                   \
            p_txxx->setDescription( txxName );                                           \
            p_txxx->setText( psz_meta );                                                 \
            free( psz_meta );                                                            \
            tag->addFrame( p_txxx );                                                     \
        }                                                                                \
    }

    WRITETXXX( TrackTotal, "TRACKTOTAL" );

#undef WRITETXXX
Angelo Haller's avatar
Angelo Haller committed
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965

    /* Write album art */
    char *psz_url = input_item_GetArtworkURL( p_item );
    if( psz_url == NULL )
        return;

    char *psz_path = make_path( psz_url );
    free( psz_url );
    if( psz_path == NULL )
        return;

    const char *psz_mime = vlc_mime_Ext2Mime( psz_path );

    FILE *p_file = vlc_fopen( psz_path, "rb" );
    if( p_file == NULL )
    {
        free( psz_path );
        return;
    }

    struct stat st;
    if( vlc_stat( psz_path, &st ) == -1 )
    {
        free( psz_path );
        fclose( p_file );
        return;
    }
    off_t file_size = st.st_size;

    free( psz_path );

    /* Limit picture size to 10MiB */
    if( file_size > 10485760 )
    {
      fclose( p_file );
      return;
    }

    char *p_buffer = new (std::nothrow) char[file_size];
    if( p_buffer == NULL )
    {
        fclose( p_file );
        return;
    }

    if( fread( p_buffer, 1, file_size, p_file ) != (unsigned)file_size )
    {
        fclose( p_file );
        delete[] p_buffer;
        return;
    }
    fclose( p_file );

    ByteVector data( p_buffer, file_size );
    delete[] p_buffer;

    ID3v2::FrameList frames = tag->frameList( "APIC" );
    ID3v2::AttachedPictureFrame *frame = NULL;
    if( frames.isEmpty() )
    {
        frame = new TagLib::ID3v2::AttachedPictureFrame;
        tag->addFrame( frame );
    }
    else
    {
        frame = static_cast<ID3v2::AttachedPictureFrame *>( frames.back() );
    }

    frame->setPicture( data );
    frame->setMimeType( psz_mime );
966 967 968 969
}


/**
Rémi Duraffort's avatar
Rémi Duraffort committed
970
 * Write the meta information to XiphComments
971 972 973
 * @param tag: the Xiph Comment
 * @param p_input: the input item
 */
974
static void WriteMetaToXiph( Ogg::XiphComment* tag, input_item_t* p_item )
975
{
976 977 978 979 980 981 982 983 984 985 986
    char* psz_meta;
#define WRITE( metaName, keyName )                      \
    psz_meta = input_item_Get##metaName( p_item );      \
    if( psz_meta )                                      \
    {                                                   \
        String key( keyName, String::UTF8 );            \
        String value( psz_meta, String::UTF8 );         \
        tag->addField( key, value, true );              \
    }                                                   \
    free( psz_meta );

.'s avatar
. committed
987 988
    WRITE( TrackNum, "TRACKNUMBER" );
    WRITE( TrackTotal, "TRACKTOTAL" );
989
    WRITE( Copyright, "COPYRIGHT" );
.'s avatar
. committed
990 991 992 993 994
    WRITE( Publisher, "ORGANIZATION" );
    WRITE( Date, "DATE" );
    WRITE( EncodedBy, "ENCODER" );
    WRITE( Rating, "RATING" );
    WRITE( Language, "LANGUAGE" );
995
    WRITE( TrackID, "MUSICBRAINZ_TRACKID" );
996
#undef WRITE
997 998 999 1000 1001 1002 1003 1004 1005
}


/**
 * Set the tags to the file using TagLib
 * @param p_this: the demux object
 * @return VLC_SUCCESS if the operation success
 */

1006 1007
static int WriteMeta( vlc_object_t *p_this )
{
Rafaël Carré's avatar
Rafaël Carré committed
1008
    vlc_mutex_locker locker (&taglib_lock);
1009
    meta_export_t *p_export = (meta_export_t *)p_this;
Rafaël Carré's avatar
Rafaël Carré committed
1010
    input_item_t *p_item = p_export->p_item;
1011
    FileRef f;
1012

Rémi Duraffort's avatar
Rémi Duraffort committed
1013
    if( !p_item )
1014 1015 1016 1017
    {
        msg_Err( p_this, "Can't save meta data of an empty input" );
        return VLC_EGENERIC;
    }
Rafaël Carré's avatar
Rafaël Carré committed
1018

1019
#if defined(_WIN32)
1020 1021
    wchar_t *wpath = ToWide( p_export->psz_file );
    if( wpath == NULL )
1022
        return VLC_EGENERIC;
1023
    f = FileRef( wpath );
1024
    free( wpath );
1025
#else
1026
    f = FileRef( p_export->psz_file );
1027 1028
#endif

1029
    if( f.isNull() || !f.tag() || f.file()->readOnly() )
1030
    {
1031
        msg_Err( p_this, "File %s can't be opened for tag writing",
1032
                 p_export->psz_file );
1033 1034 1035
        return VLC_EGENERIC;
    }

1036
    msg_Dbg( p_this, "Writing metadata for %s", p_export->psz_file );
1037

1038
    Tag *p_tag = f.tag();