taglib.cpp 30.6 KB
Newer Older
1
2
3
/*****************************************************************************
 * taglib.cpp: Taglib tag parser/writer
 *****************************************************************************
4
 * Copyright (C) 2003-2011 VLC authors and VideoLAN
dionoea's avatar
dionoea 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
51
52
#ifdef WIN32
# 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
ivoire's avatar
ivoire committed
70
71
#endif

72
#include <apetag.h>
73
74
75
76
77
#include <flacfile.h>
#include <mpcfile.h>
#include <mpegfile.h>
#include <oggfile.h>
#include <oggflacfile.h>
78
#include "../demux/vorbis.h"
ivoire's avatar
ivoire committed
79

80
81
#include <aifffile.h>
#include <wavfile.h>
ivoire's avatar
ivoire committed
82

83
#if defined(TAGLIB_WITH_MP4)
ivoire's avatar
ivoire committed
84
85
86
# include <mp4file.h>
#endif

87
88
#include <speexfile.h>
#include <trueaudiofile.h>
89
#include <vorbisfile.h>
90
91
#include <wavpackfile.h>

92
#include <attachedpictureframe.h>
93
#include <textidentificationframe.h>
94
95
#include <uniquefileidentifierframe.h>

Rafaël Carré's avatar
Rafaël Carré committed
96
97
// taglib is not thread safe
static vlc_mutex_t taglib_lock = VLC_STATIC_MUTEX;
98

99
100
101
// Local functions
static int ReadMeta    ( vlc_object_t * );
static int WriteMeta   ( vlc_object_t * );
102

103
104
105
106
107
108
109
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 ()
zorglub's avatar
zorglub committed
110

111
112
using namespace TagLib;

113
114
115
116
117
118
119
120
121
122
123
124
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 );
    }
}
125
126

/**
ivoire's avatar
ivoire committed
127
 * Read meta information from APE tags
128
 * @param tag: the APE tag
ivoire's avatar
ivoire committed
129
 * @param p_demux_meta: the demuxer meta
130
131
 * @param p_meta: the meta
 */
132
static void ReadMetaFromAPE( APE::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
133
{
ivoire's avatar
ivoire committed
134
    APE::Item item;
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

    item = tag->itemListMap()["COVER ART (FRONT)"];
    if( !item.isEmpty() )
    {
        input_attachment_t *p_attachment;

        const ByteVector picture = item.value();
        const char *p_data = picture.data();
        unsigned i_data = picture.size();

        size_t desc_len = strnlen(p_data, i_data);
        if (desc_len < i_data) {
            const char *psz_name = p_data;
            p_data += desc_len + 1; /* '\0' */
            i_data -= desc_len + 1;
            msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
                     psz_name, "image/jpeg", i_data );

            p_attachment = vlc_input_attachment_New( "cover", "image/jpeg",
                                    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 );

            vlc_meta_SetArtURL( p_meta, "attachment://cover" );
        }
    }

ivoire's avatar
ivoire committed
164
165
#define SET( keyName, metaName ) \
    item = tag->itemListMap()[keyName]; \
166
    if( !item.isEmpty() ) vlc_meta_Set##metaName( p_meta, item.toString().toCString( true ) ); \
ivoire's avatar
ivoire committed
167

168
169
170
171
172
    SET( "ALBUM", Album );
    SET( "ARTIST", Artist );
    SET( "COMMENT", Description );
    SET( "GENRE", Genre );
    SET( "TITLE", Title );
ivoire's avatar
ivoire committed
173
174
175
176
177
    SET( "COPYRIGHT", Copyright );
    SET( "LANGUAGE", Language );
    SET( "PUBLISHER", Publisher );

#undef SET
178
179
180
181
182
183
184

    /* */
    item = tag->itemListMap()["TRACK"];
    if( !item.isEmpty() )
    {
        ExtractTrackNumberValues( p_meta, item.toString().toCString( true ) );
    }
185
186
}

187

188
#ifdef TAGLIB_HAVE_ASFPICTURE_H
ivoire's avatar
ivoire committed
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/**
 * Read meta information from APE tags
 * @param tag: the APE tag
 * @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 )
{
    // List the pictures
    ASF::AttributeList list = tag->attributeListMap()["WM/Picture"];
    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;
        }

218
219
220
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
                 psz_name, psz_mime, i_data );

ivoire's avatar
ivoire committed
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        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 );
        free( psz_name );

        char *psz_url;
        if( asprintf( &psz_url, "attachment://%s",
                      p_attachment->psz_name ) == -1 )
            continue;
        vlc_meta_SetArtURL( p_meta, psz_url );
        free( psz_url );
    }
}
#endif


240
/**
241
 * Read meta information from id3v2 tags
242
 * @param tag: the id3v2 tag
ivoire's avatar
ivoire committed
243
 * @param p_demux_meta: the demuxer meta
244
245
 * @param p_meta: the meta
 */
246
static void ReadMetaFromId3v2( ID3v2::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
247
248
249
250
251
{
    // Get the unique file identifier
    ID3v2::FrameList list = tag->frameListMap()["UFID"];
    ID3v2::FrameList::Iterator iter;
    for( iter = list.begin(); iter != list.end(); iter++ )
252
    {
253
254
        ID3v2::UniqueFileIdentifierFrame* p_ufid =
                dynamic_cast<ID3v2::UniqueFileIdentifierFrame*>(*iter);
255
256
        if( !p_ufid )
            continue;
257
258
259
260
261
262
263
        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];
ivoire's avatar
ivoire committed
264
            int max_size = __MIN( p_ufid->identifier().size(), 63);
265
            strncpy( psz_ufid, p_ufid->identifier().data(), max_size );
ivoire's avatar
ivoire committed
266
            psz_ufid[max_size] = '\0';
267
268
269
            vlc_meta_SetTrackID( p_meta, psz_ufid );
        }
    }
hartman's avatar
hartman committed
270

271
272
273
274
275
276
    // 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);
277
278
        if( !p_txxx )
            continue;
279
280
281
282
283
        if( !strcmp( p_txxx->description().toCString( true ), "TRACKTOTAL" ) )
        {
            vlc_meta_Set( p_meta, vlc_meta_TrackTotal, p_txxx->fieldList().back().toCString( true ) );
            continue;
        }
284
        vlc_meta_AddExtra( p_meta, p_txxx->description().toCString( true ),
285
                           p_txxx->fieldList().back().toCString( true ) );
286
287
    }

ivoire's avatar
ivoire committed
288
    // Get some more information
289
290
291
292
293
294
295
296
297
298
299
300
#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
ivoire's avatar
ivoire committed
301

302
303
304
305
306
307
308
    /* */
    list = tag->frameListMap()["TRCK"];
    if( !list.isEmpty() )
    {
        ExtractTrackNumberValues( p_meta, (*list.begin())->toString().toCString( true ) );
    }

ivoire's avatar
ivoire committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    /* 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). */
    };
335
    #define PI_COVER_SCORE_SIZE (sizeof (pi_cover_score) / sizeof (pi_cover_score[0]))
ivoire's avatar
ivoire committed
336
337
338
339
340
341
    int i_score = -1;

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

ivoire's avatar
ivoire committed
343
    TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
344
    for( iter = list.begin(); iter != list.end(); iter++ )
ivoire's avatar
ivoire committed
345
346
347
    {
        ID3v2::AttachedPictureFrame* p_apic =
            dynamic_cast<ID3v2::AttachedPictureFrame*>(*iter);
348
349
        if( !p_apic )
            continue;
ivoire's avatar
ivoire committed
350
351
        input_attachment_t *p_attachment;

352
353
        const char *psz_mime;
        char *psz_name, *psz_description;
ivoire's avatar
ivoire committed
354

355
356
        // Get the mime and description of the image.
        // If the description is empty, take the type as a description
ivoire's avatar
ivoire committed
357
        psz_mime = p_apic->mimeType().toCString( true );
358
359
360
361
362
363
364
365
366
367
368
        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;
ivoire's avatar
ivoire committed
369
370
371
372
373
374
375
376

        /* 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 ) )
        {
377
            msg_Warn( p_demux_meta, "Invalid picture embedded by broken iTunes version" );
378
379
            free( psz_description );
            continue;
ivoire's avatar
ivoire committed
380
381
        }

382
383
384
        const ByteVector picture = p_apic->picture();
        const char *p_data = picture.data();
        const unsigned i_data = picture.size();
ivoire's avatar
ivoire committed
385

386
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
ivoire's avatar
ivoire committed
387
388
389
390
                 psz_name, psz_mime, i_data );

        p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
                                psz_description, p_data, i_data );
391
392
393
394
395
396
397
398
        if( !p_attachment )
        {
            free( psz_description );
            continue;
        }
        TAB_APPEND_CAST( (input_attachment_t**),
                         p_demux_meta->i_attachments, p_demux_meta->attachments,
                         p_attachment );
399
        free( psz_description );
ivoire's avatar
ivoire committed
400

401
402
403
404
405
        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 )
ivoire's avatar
ivoire committed
406
        {
407
            i_score = pi_cover_score[i_pic_type];
ivoire's avatar
ivoire committed
408
409
410
            char *psz_url;
            if( asprintf( &psz_url, "attachment://%s",
                          p_attachment->psz_name ) == -1 )
411
                continue;
ivoire's avatar
ivoire committed
412
413
414
415
            vlc_meta_SetArtURL( p_meta, psz_url );
            free( psz_url );
        }
    }
416
417
}

hartman's avatar
hartman committed
418

419
/**
ivoire's avatar
ivoire committed
420
 * Read the meta information from XiphComments
421
 * @param tag: the Xiph Comment
ivoire's avatar
ivoire committed
422
 * @param p_demux_meta: the demuxer meta
423
424
 * @param p_meta: the meta
 */
425
static void ReadMetaFromXiph( Ogg::XiphComment* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
426
{
ivoire's avatar
ivoire committed
427
    StringList list;
ivoire's avatar
ivoire committed
428
#define SET( keyName, metaName )                                               \
ivoire's avatar
ivoire committed
429
    list = tag->fieldListMap()[keyName];                                       \
ivoire's avatar
ivoire committed
430
431
432
    if( !list.isEmpty() )                                                      \
        vlc_meta_Set##metaName( p_meta, (*list.begin()).toCString( true ) );

ivoire's avatar
ivoire committed
433
    SET( "COPYRIGHT", Copyright );
ivoire's avatar
ivoire committed
434
#undef SET
ivoire's avatar
ivoire committed
435
436
437
438
439
440
441

    // Try now to get embedded art
    StringList mime_list = tag->fieldListMap()[ "COVERARTMIME" ];
    StringList art_list = tag->fieldListMap()[ "COVERART" ];

    input_attachment_t *p_attachment;

442
443
444
445
446
447
    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() );
ivoire's avatar
ivoire committed
448

449
450
451
        const char* psz_name = "cover";
        const char* psz_mime = mime_list[0].toCString(true);
        const char* psz_description = "cover";
ivoire's avatar
ivoire committed
452

453
454
        uint8_t *p_data;
        int i_data = vlc_b64_decode_binary( &p_data, art_list[0].toCString(true) );
ivoire's avatar
ivoire committed
455

456
457
        msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %i bytes",
                psz_name, psz_mime, i_data );
ivoire's avatar
ivoire committed
458

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
        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;
        int type;
        int i_data = vlc_b64_decode_binary( &p_data, art_list[0].toCString(true) );
        p_attachment = ParseFlacPicture( p_data, i_data, 0, &type );
    }

    TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
476
477
478
479
    if (p_attachment) {
        TAB_APPEND_CAST( (input_attachment_t**),
                p_demux_meta->i_attachments, p_demux_meta->attachments,
                p_attachment );
ivoire's avatar
ivoire committed
480

481
482
483
484
485
        char *psz_url;
        if( asprintf( &psz_url, "attachment://%s", p_attachment->psz_name ) != -1 ) {
            vlc_meta_SetArtURL( p_meta, psz_url );
            free( psz_url );
        }
486
    }
487
488
}

ivoire's avatar
ivoire committed
489

490
#if defined(TAGLIB_WITH_MP4)
ivoire's avatar
ivoire committed
491
492
493
494
495
496
/**
 * 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
 */
497
static void ReadMetaFromMP4( MP4::Tag* tag, demux_meta_t *p_demux_meta, vlc_meta_t* p_meta )
ivoire's avatar
ivoire committed
498
499
500
501
502
503
{
    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";

504
505
506
        msg_Dbg( p_demux_meta, "Found embedded art (%s) is %i bytes",
                 psz_format, list[0].data().size() );

ivoire's avatar
ivoire committed
507
508
509
510
511
512
513
514
515
516
517
        TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
        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
518

ivoire's avatar
ivoire committed
519

520
521
522
523
524
525
526
/**
 * 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
527
    vlc_mutex_locker locker (&taglib_lock);
528
529
    demux_meta_t*   p_demux_meta = (demux_meta_t *)p_this;
    demux_t*        p_demux = p_demux_meta->p_demux;
530
    vlc_meta_t*     p_meta;
531
    FileRef f;
532
533

    p_demux_meta->p_meta = NULL;
534
    if( strcmp( p_demux->psz_access, "file" ) )
535
        return VLC_EGENERIC;
536

537
    char *psz_path = strdup( p_demux->psz_file );
538
539
    if( !psz_path )
        return VLC_ENOMEM;
540

541
#if defined(WIN32)
542
543
    wchar_t *wpath = ToWide( psz_path );
    if( wpath == NULL )
544
545
    {
        free( psz_path );
546
        return VLC_EGENERIC;
547
    }
548
    f = FileRef( wpath );
549
    free( wpath );
550
#else
551
    f = FileRef( psz_path );
552
#endif
Ilkka Ollakka's avatar
Ilkka Ollakka committed
553
    free( psz_path );
554

Rafaël Carré's avatar
Rafaël Carré committed
555
556
    if( f.isNull() )
        return VLC_EGENERIC;
557
    if( !f.tag() || f.tag()->isEmpty() )
Rafaël Carré's avatar
Rafaël Carré committed
558
        return VLC_EGENERIC;
559

560
    p_demux_meta->p_meta = p_meta = vlc_meta_New();
561
562
563
564
565
566
567
    if( !p_meta )
        return VLC_ENOMEM;


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

ivoire's avatar
ivoire committed
568
#define SET( tag, meta )                                                       \
569
570
    if( !p_tag->tag().isNull() && !p_tag->tag().isEmpty() )                    \
        vlc_meta_Set##meta( p_meta, p_tag->tag().toCString(true) )
ivoire's avatar
ivoire committed
571
#define SETINT( tag, meta )                                                    \
572
573
574
575
576
577
    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
578

ivoire's avatar
ivoire committed
579
580
581
582
583
584
    SET( title, Title );
    SET( artist, Artist );
    SET( album, Album );
    SET( comment, Description );
    SET( genre, Genre );
    SETINT( year, Date );
585
    SETINT( track, TrackNum );
Rafaël Carré's avatar
Rafaël Carré committed
586

587
#undef SETINT
Rafaël Carré's avatar
Rafaël Carré committed
588
#undef SET
589

590

591
    // Try now to read special tags
592
#ifdef TAGLIB_HAVE_APEFILE_H
593
594
595
596
597
    if( APE::File* ape = dynamic_cast<APE::File*>(f.file()) )
    {
        if( ape->APETag() )
            ReadMetaFromAPE( ape->APETag(), p_demux_meta, p_meta );
    }
598
    else
ivoire's avatar
ivoire committed
599
#endif
600
#ifdef TAGLIB_HAVE_ASFPICTURE_H
ivoire's avatar
ivoire committed
601
602
603
604
605
606
    if( ASF::File* asf = dynamic_cast<ASF::File*>(f.file()) )
    {
        if( asf->tag() )
            ReadMetaFromASF( asf->tag(), p_demux_meta, p_meta );
    }
    else
607
608
#endif
    if( FLAC::File* flac = dynamic_cast<FLAC::File*>(f.file()) )
609
610
    {
        if( flac->ID3v2Tag() )
611
            ReadMetaFromId3v2( flac->ID3v2Tag(), p_demux_meta, p_meta );
612
        else if( flac->xiphComment() )
613
            ReadMetaFromXiph( flac->xiphComment(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
614
    }
615
#if defined(TAGLIB_WITH_MP4)
ivoire's avatar
ivoire committed
616
617
618
    else if( MP4::File *mp4 = dynamic_cast<MP4::File*>(f.file()) )
    {
        if( mp4->tag() )
619
            ReadMetaFromMP4( mp4->tag(), p_demux_meta, p_meta );
ivoire's avatar
ivoire committed
620
621
    }
#endif
622
    else if( MPC::File* mpc = dynamic_cast<MPC::File*>(f.file()) )
Rafaël Carré's avatar
Rafaël Carré committed
623
    {
624
        if( mpc->APETag() )
625
            ReadMetaFromAPE( mpc->APETag(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
626
    }
627
    else if( MPEG::File* mpeg = dynamic_cast<MPEG::File*>(f.file()) )
Rafaël Carré's avatar
Rafaël Carré committed
628
    {
629
        if( mpeg->ID3v2Tag() )
630
            ReadMetaFromId3v2( mpeg->ID3v2Tag(), p_demux_meta, p_meta );
631
        else if( mpeg->APETag() )
632
            ReadMetaFromAPE( mpeg->APETag(), p_demux_meta, p_meta );
Rafaël Carré's avatar
Rafaël Carré committed
633
    }
634
    else if( dynamic_cast<Ogg::File*>(f.file()) )
635
636
    {
        if( Ogg::FLAC::File* ogg_flac = dynamic_cast<Ogg::FLAC::File*>(f.file()))
637
            ReadMetaFromXiph( ogg_flac->tag(), p_demux_meta, p_meta );
638
        else if( Ogg::Speex::File* ogg_speex = dynamic_cast<Ogg::Speex::File*>(f.file()) )
639
            ReadMetaFromXiph( ogg_speex->tag(), p_demux_meta, p_meta );
640
        else if( Ogg::Vorbis::File* ogg_vorbis = dynamic_cast<Ogg::Vorbis::File*>(f.file()) )
641
            ReadMetaFromXiph( ogg_vorbis->tag(), p_demux_meta, p_meta );
642
    }
643
    else if( dynamic_cast<RIFF::File*>(f.file()) )
644
645
    {
        if( RIFF::AIFF::File* riff_aiff = dynamic_cast<RIFF::AIFF::File*>(f.file()) )
646
            ReadMetaFromId3v2( riff_aiff->tag(), p_demux_meta, p_meta );
647
        else if( RIFF::WAV::File* riff_wav = dynamic_cast<RIFF::WAV::File*>(f.file()) )
648
            ReadMetaFromId3v2( riff_wav->tag(), p_demux_meta, p_meta );
649
    }
650
651
652
    else if( TrueAudio::File* trueaudio = dynamic_cast<TrueAudio::File*>(f.file()) )
    {
        if( trueaudio->ID3v2Tag() )
653
            ReadMetaFromId3v2( trueaudio->ID3v2Tag(), p_demux_meta, p_meta );
654
655
656
657
    }
    else if( WavPack::File* wavpack = dynamic_cast<WavPack::File*>(f.file()) )
    {
        if( wavpack->APETag() )
658
            ReadMetaFromAPE( wavpack->APETag(), p_demux_meta, p_meta );
659
    }
660

Rafaël Carré's avatar
Rafaël Carré committed
661
    return VLC_SUCCESS;
662
}
663

664

665
/**
ivoire's avatar
ivoire committed
666
 * Write meta information to APE tags
667
 * @param tag: the APE tag
ivoire's avatar
ivoire committed
668
 * @param p_item: the input item
669
 */
ivoire's avatar
ivoire committed
670
static void WriteMetaToAPE( APE::Tag* tag, input_item_t* p_item )
671
{
ivoire's avatar
ivoire committed
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
    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" );

#undef WRITE
688
689
690
691
692
693
694
695
}


/**
 * Write meta information to id3v2 tags
 * @param tag: the id3v2 tag
 * @param p_input: the input item
 */
ivoire's avatar
ivoire committed
696
static void WriteMetaToId3v2( ID3v2::Tag* tag, input_item_t* p_item )
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
{
    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
Angelo Haller's avatar
Angelo Haller committed
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787

    /* 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 );
788
789
790
791
}


/**
ivoire's avatar
ivoire committed
792
 * Write the meta information to XiphComments
793
794
795
 * @param tag: the Xiph Comment
 * @param p_input: the input item
 */
ivoire's avatar
ivoire committed
796
static void WriteMetaToXiph( Ogg::XiphComment* tag, input_item_t* p_item )
797
{
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    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 );

    WRITE( Copyright, "COPYRIGHT" );

#undef WRITE
812
813
814
815
816
817
818
819
820
}


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

821
822
static int WriteMeta( vlc_object_t *p_this )
{
Rafaël Carré's avatar
Rafaël Carré committed
823
    vlc_mutex_locker locker (&taglib_lock);
824
    meta_export_t *p_export = (meta_export_t *)p_this;
Rafaël Carré's avatar
Rafaël Carré committed
825
    input_item_t *p_item = p_export->p_item;
826
    FileRef f;
827

ivoire's avatar
ivoire committed
828
    if( !p_item )
829
830
831
832
    {
        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
833

834
#if defined(WIN32)
835
836
    wchar_t *wpath = ToWide( p_export->psz_file );
    if( wpath == NULL )
837
        return VLC_EGENERIC;
838
    f = FileRef( wpath );
839
    free( wpath );
840
#else
841
    f = FileRef( p_export->psz_file );
842
843
#endif

844
    if( f.isNull() || !f.tag() || f.file()->readOnly() )
845
    {
846
        msg_Err( p_this, "File %s can't be opened for tag writing",
847
                 p_export->psz_file );
848
849
850
        return VLC_EGENERIC;
    }

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

853
    Tag *p_tag = f.tag();
854

855
    char *psz_meta;
856

ivoire's avatar
ivoire committed
857
858
859
860
#define SET( a, b )                                             \
    psz_meta = input_item_Get ## a( p_item );                   \
    if( psz_meta )                                              \
    {                                                           \
ivoire's avatar
ivoire committed
861
862
        String tmp( psz_meta, String::UTF8 );                   \
        p_tag->set##b( tmp );                                   \
ivoire's avatar
ivoire committed
863
864
    }                                                           \
    free( psz_meta );
865

ivoire's avatar
ivoire committed
866
867
    // Saving all common fields
    // If the title is empty, use the name
ivoire's avatar
ivoire committed
868
869
870
871
872
    SET( TitleFbName, Title );
    SET( Artist, Artist );
    SET( Album, Album );
    SET( Description, Comment );
    SET( Genre, Genre );
873

874
875
876
#undef SET

    psz_meta = input_item_GetDate( p_item );
877
    if( !EMPTY_STR(psz_meta) ) p_tag->setYear( atoi( psz_meta ) );
878
    free( psz_meta );
879

880
    psz_meta = input_item_GetTrackNum( p_item );
881
    if( !EMPTY_STR(psz_meta) ) p_tag->setTrack( atoi( psz_meta ) );
882
    free( psz_meta );
883

884

885
    // Try now to write special tags
886
#ifdef TAGLIB_HAVE_APEFILE_H
887
888
889
890
891
    if( APE::File* ape = dynamic_cast<APE::File*>(f.file()) )
    {
        if( ape->APETag() )
            WriteMetaToAPE( ape->APETag(), p_item );
    }
892
893
894
    else
#endif
    if( FLAC::File* flac = dynamic_cast<FLAC::File*>(f.file()) )
895
896
    {
        if( flac->ID3v2Tag() )
ivoire's avatar
ivoire committed
897
            WriteMetaToId3v2( flac->ID3v2Tag(), p_item );
898
899
900
901
902
903
904
905
906
907
908
        else if( flac->xiphComment() )
            WriteMetaToXiph( flac->xiphComment(), p_item );
    }
    else if( MPC::File* mpc = dynamic_cast<MPC::File*>(f.file()) )
    {
        if( mpc->APETag() )
            WriteMetaToAPE( mpc->APETag(), p_item );
    }
    else if( MPEG::File* mpeg = dynamic_cast<MPEG::File*>(f.file()) )
    {
        if( mpeg->ID3v2Tag() )
ivoire's avatar
ivoire committed
909
            WriteMetaToId3v2( mpeg->ID3v2Tag(), p_item );
910
911
912
        else if( mpeg->APETag() )
            WriteMetaToAPE( mpeg->APETag(), p_item );
    }
913
    else if( dynamic_cast<Ogg::File*>(f.file()) )
914
915
916
917
918
919
920
921
    {
        if( Ogg::FLAC::File* ogg_flac = dynamic_cast<Ogg::FLAC::File*>(f.file()))
            WriteMetaToXiph( ogg_flac->tag(), p_item );
        else if( Ogg::Speex::File* ogg_speex = dynamic_cast<Ogg::Speex::File*>(f.file()) )
            WriteMetaToXiph( ogg_speex->tag(), p_item );
        else if( Ogg::Vorbis::File* ogg_vorbis = dynamic_cast<Ogg::Vorbis::File*>(f.file()) )
            WriteMetaToXiph( ogg_vorbis->tag(), p_item );
    }
922
    else if( dynamic_cast<RIFF::File*>(f.file()) )
ivoire's avatar
ivoire committed
923
924
925
926
927
928
    {
        if( RIFF::AIFF::File* riff_aiff = dynamic_cast<RIFF::AIFF::File*>(f.file()) )
            WriteMetaToId3v2( riff_aiff->tag(), p_item );
        else if( RIFF::WAV::File* riff_wav = dynamic_cast<RIFF::WAV::File*>(f.file()) )
            WriteMetaToId3v2( riff_wav->tag(), p_item );
    }
929
930
931
    else if( TrueAudio::File* trueaudio = dynamic_cast<TrueAudio::File*>(f.file()) )
    {
        if( trueaudio->ID3v2Tag() )
ivoire's avatar
ivoire committed
932
            WriteMetaToId3v2( trueaudio->ID3v2Tag(), p_item );
933
934
935
936
937
    }
    else if( WavPack::File* wavpack = dynamic_cast<WavPack::File*>(f.file()) )
    {
        if( wavpack->APETag() )
            WriteMetaToAPE( wavpack->APETag(), p_item );
938
    }
939

940
    // Save the meta data
941
    f.save();
942

943
    return VLC_SUCCESS;
944
945
}