taglib.cpp 15.5 KB
Newer Older
1
2
3
4
/*****************************************************************************
 * taglib.cpp: Taglib tag parser/writer
 *****************************************************************************
 * Copyright (C) 2003-2006 the VideoLAN team
dionoea's avatar
dionoea committed
5
 * $Id$
6
7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Rafaël Carré <funman@videolanorg>
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU 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
#include <vlc/vlc.h>
26
#include <vlc_playlist.h>
27
#include <vlc_meta.h>
zorglub's avatar
zorglub committed
28
#include <vlc_demux.h>
29
30
31

#include <fileref.h>
#include <tag.h>
32
#include <tstring.h>
zorglub's avatar
zorglub committed
33
#include <id3v2tag.h>
34
35
#include <textidentificationframe.h>
#include <tbytevector.h>
zorglub's avatar
zorglub committed
36
#include <mpegfile.h>
37
#include <flacfile.h>
38
#include <attachedpictureframe.h>
39
40
41
42
43
44
45
#if 0
#include <oggflacfile.h>
#endif
#include <flacfile.h>
#include <flacproperties.h>
#include <vorbisfile.h>
#include <vorbisproperties.h>
46
47
#include <uniquefileidentifierframe.h>
#include <textidentificationframe.h>
Rafaël Carré's avatar
Rafaël Carré committed
48
49
50
#if 0 /* parse the tags without taglib helpers? */
#include <relativevolumeframe.h>
#endif
51

52
53
54
static int  ReadMeta    ( vlc_object_t * );
static int  DownloadArt ( vlc_object_t * );
static int  WriteMeta   ( vlc_object_t * );
55
56
57
58

vlc_module_begin();
    set_capability( "meta reader", 1000 );
    set_callbacks( ReadMeta, NULL );
59
60
61
62
63
64
    add_submodule();
        set_capability( "art downloader", 50 );
        set_callbacks( DownloadArt, NULL );
    add_submodule();
        set_capability( "meta writer", 50 );
        set_callbacks( WriteMeta, NULL );
65
vlc_module_end();
zorglub's avatar
zorglub committed
66

67
/* Try detecting embedded art */
68
static void DetectImage( TagLib::FileRef f, demux_t *p_demux )
69
{
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    demux_meta_t        *p_demux_meta   = (demux_meta_t *)p_demux->p_private;
    vlc_meta_t          *p_meta         = p_demux_meta->p_meta;
    TagLib::ID3v2::Tag  *p_tag;
    int                 i_score         = -1;

    /* 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). */
    };

102
103
104
    if( TagLib::MPEG::File *mpeg =
               dynamic_cast<TagLib::MPEG::File *>(f.file() ) )
    {
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
        p_tag = mpeg->ID3v2Tag();
        if( !p_tag )
            return;
        TagLib::ID3v2::FrameList list = p_tag->frameListMap()[ "APIC" ];
        if( list.isEmpty() )
            return;
        input_thread_t *p_input = (input_thread_t *)
                vlc_object_find( p_demux,VLC_OBJECT_INPUT, FIND_PARENT );
        if( !p_input )
            return;
        input_item_t *p_item = input_GetItem( p_input );
        TagLib::ID3v2::AttachedPictureFrame *p_apic;

        TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
        for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
                iter != list.end(); iter++ )
        {
            p_apic = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*iter);
            input_attachment_t *p_attachment;

            const char *psz_name, *psz_mime, *psz_description;
            TagLib::ByteVector p_data_taglib; const char *p_data; int i_data;

            psz_mime = p_apic->mimeType().toCString(true);
            psz_description = p_apic->description().toCString(true);
            psz_name = psz_description;

            p_data_taglib = p_apic->picture();
            p_data = p_data_taglib.data();
            i_data = p_data_taglib.size();

            msg_Dbg( p_demux, "Found embedded art: %s (%s) is %i bytes",
                    psz_name, psz_mime, i_data );

            p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
                    psz_description, p_data, i_data );
            TAB_APPEND_CAST( (input_attachment_t**),
                    p_demux_meta->i_attachments, p_demux_meta->attachments,
                    p_attachment );

            if( pi_cover_score[p_apic->type()] > i_score )
            {
                i_score = pi_cover_score[p_apic->type()];
                char *psz_url;
                if( asprintf( &psz_url, "attachment://%s",
                        p_attachment->psz_name ) == -1 )
                {
                    vlc_object_release( p_input );
                    return;
                }
                vlc_meta_SetArtURL( p_meta, psz_url );
                free( psz_url );
            }
        }
        vlc_object_release( p_input );
160
    }
161
162
#if 0
    //flac embedded images are extracted in the flac demuxer
163
164
165
    else if( TagLib::FLAC::File *flac =
             dynamic_cast<TagLib::FLAC::File *>(f.file() ) )
    {
166
167
168
169
170
171
        p_tag = flac->ID3v2Tag();
        if( p_tag )
            return;
        TagLib::ID3v2::FrameList l = p_tag->frameListMap()[ "APIC" ];
        if( l.isEmpty() )
            return;
172
173
            vlc_meta_SetArtURL( p_meta, "APIC" );
    }
174
#endif
175
176
177
178
179
180
181
182
183
184
185
186
#if 0
/* This needs special additions to taglib */
 * else if( TagLib::MP4::File *mp4 =
               dynamic_cast<TagLib::MP4::File *>( f.file() ) )
    {
        TagLib::MP4::Tag *mp4tag =
                dynamic_cast<TagLib::MP4::Tag *>( mp4->tag() );
        if( mp4tag && mp4tag->cover().size() )
            vlc_meta_SetArtURL( p_meta, "MP4C" );
    }
#endif
}
zorglub's avatar
zorglub committed
187

188
189
static int ReadMeta( vlc_object_t *p_this )
{
190
191
192
    demux_t         *p_demux = (demux_t *)p_this;
    demux_meta_t    *p_demux_meta = (demux_meta_t*)p_demux->p_private;
    vlc_meta_t      *p_meta = p_demux_meta->p_meta;
193

194
195
    TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
    p_demux_meta->p_meta = NULL;
Rafaël Carré's avatar
Rafaël Carré committed
196
197
198
199
200
201
202

    TagLib::FileRef f( p_demux->psz_path );
    if( f.isNull() )
        return VLC_EGENERIC;

    if ( !f.tag() || f.tag()->isEmpty() )
        return VLC_EGENERIC;
203

204
    p_demux_meta->p_meta = p_meta = vlc_meta_New();
205
    TagLib::Tag *p_tag = f.tag();
Rafaël Carré's avatar
Rafaël Carré committed
206
207
208
209
210

    if( TagLib::MPEG::File *p_mpeg =
        dynamic_cast<TagLib::MPEG::File *>(f.file() ) )
    {
        if( p_mpeg->ID3v2Tag() )
211
        {
212
213
            TagLib::ID3v2::Tag *p_tag = p_mpeg->ID3v2Tag();
            TagLib::ID3v2::FrameList list = p_tag->frameListMap()["UFID"];
Rafaël Carré's avatar
Rafaël Carré committed
214
215
216
            TagLib::ID3v2::UniqueFileIdentifierFrame* p_ufid;
            for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
                    iter != list.end(); iter++ )
217
            {
Rafaël Carré's avatar
Rafaël Carré committed
218
219
220
                p_ufid = dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(*iter);
                const char *owner = p_ufid->owner().toCString();
                if (!strcmp( owner, "http://musicbrainz.org" ))
221
                {
Rafaël Carré's avatar
Rafaël Carré committed
222
                    /* ID3v2 UFID contains up to 64 bytes binary data
223
                        * but in our case it will be a '\0'
Rafaël Carré's avatar
Rafaël Carré committed
224
225
226
227
228
229
230
231
232
                        * terminated string */
                    char *psz_ufid = (char*) malloc( 64 );
                    int j = 0;
                    while( ( j < 63 ) &&
                            ( j < p_ufid->identifier().size() ) )
                        psz_ufid[j] = p_ufid->identifier()[j++];
                    psz_ufid[j] = '\0';
                    vlc_meta_SetTrackID( p_meta, psz_ufid );
                    free( psz_ufid );
233
234
                }
            }
Rafaël Carré's avatar
Rafaël Carré committed
235

236
            list = p_tag->frameListMap()["TXXX"];
Rafaël Carré's avatar
Rafaël Carré committed
237
238
239
            TagLib::ID3v2::UserTextIdentificationFrame* p_txxx;
            for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
                    iter != list.end(); iter++ )
240
            {
Rafaël Carré's avatar
Rafaël Carré committed
241
242
                p_txxx = dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(*iter);
                const char *psz_desc= p_txxx->description().toCString();
243
                vlc_meta_AddExtra( p_meta, psz_desc,
Rafaël Carré's avatar
Rafaël Carré committed
244
245
246
                            p_txxx->fieldList().toString().toCString());
            }
#if 0
247
            list = p_tag->frameListMap()["RVA2"];
Rafaël Carré's avatar
Rafaël Carré committed
248
249
250
251
252
253
            TagLib::ID3v2::RelativeVolumeFrame* p_rva2;
            for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
                    iter != list.end(); iter++ )
            {
                p_rva2 = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame*>(*iter);
                /* TODO: process rva2 frames */
254
255
            }
#endif
256
            list = p_tag->frameList();
Rafaël Carré's avatar
Rafaël Carré committed
257
258
259
260
            TagLib::ID3v2::Frame* p_t;
            char psz_tag[4];
            for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
                    iter != list.end(); iter++ )
261
            {
Rafaël Carré's avatar
Rafaël Carré committed
262
263
264
265
266
267
268
269
270
271
272
273
274
                p_t = dynamic_cast<TagLib::ID3v2::Frame*> (*iter);
                memcpy( psz_tag, p_t->frameID().data(), 4);

#define SET( foo, bar ) if( !strncmp( psz_tag, foo, 4 ) ) \
vlc_meta_Set##bar( p_meta, p_t->toString().toCString(true))
                SET( "TPUB", Publisher );
                SET( "TCOP", Copyright );
                SET( "TENC", EncodedBy );
                SET( "TLAN", Language );
                //SET( "POPM", Rating ); /* rating needs special handling in id3v2 */
                //if( !strncmp( psz_tag, "RVA2", 4 ) )
                    /* TODO */
#undef SET
275
276
            }
        }
Rafaël Carré's avatar
Rafaël Carré committed
277
    }
278

Rafaël Carré's avatar
Rafaël Carré committed
279
280
281
282
283
284
285
286
    else if( TagLib::Ogg::Vorbis::File *p_ogg_v =
        dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file() ) )
    {
        int i_ogg_v_length = p_ogg_v->audioProperties()->length();

        input_thread_t *p_input = (input_thread_t *)
                vlc_object_find( p_demux,VLC_OBJECT_INPUT, FIND_PARENT );
        if( p_input )
287
        {
Rafaël Carré's avatar
Rafaël Carré committed
288
289
            input_item_t *p_item = input_GetItem( p_input );
            if( p_item )
290
291
                input_item_SetDuration( p_item,
                        (mtime_t) i_ogg_v_length * 1000000 );
Rafaël Carré's avatar
Rafaël Carré committed
292
293
            vlc_object_release( p_input );
        }
294

Rafaël Carré's avatar
Rafaël Carré committed
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
    }
#if 0 /* at this moment, taglib is unable to detect ogg/flac files
* becauses type detection is based on file extension:
* ogg = ogg/vorbis
* flac = flac
* ø = ogg/flac
*/
    else if( TagLib::Ogg::FLAC::File *p_ogg_f =
        dynamic_cast<TagLib::Ogg::FLAC::File *>(f.file() ) )
    {
        long i_ogg_f_length = p_ogg_f->streamLength();
        input_thread_t *p_input = (input_thread_t *)
                vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
        if( p_input )
        {
            input_item_t *p_item = input_GetItem( p_input );
            if( p_item )
312
313
                input_item_SetDuration( p_item,
                        (mtime_t) i_ogg_f_length * 1000000 );
Rafaël Carré's avatar
Rafaël Carré committed
314
315
316
317
318
319
320
321
322
323
324
325
326
327
            vlc_object_release( p_input );
        }
    }
#endif
    else if( TagLib::FLAC::File *p_flac =
        dynamic_cast<TagLib::FLAC::File *>(f.file() ) )
    {
        long i_flac_length = p_flac->audioProperties()->length();
        input_thread_t *p_input = (input_thread_t *)
                vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
        if( p_input )
        {
            input_item_t *p_item = input_GetItem( p_input );
            if( p_item )
328
329
                input_item_SetDuration( p_item,
                        (mtime_t) i_flac_length * 1000000 );
Rafaël Carré's avatar
Rafaël Carré committed
330
331
332
            vlc_object_release( p_input );
        }
    }
333

334
#define SET( foo, bar ) vlc_meta_Set##foo( p_meta, p_tag->bar ().toCString(true))
335
#define SETINT( foo, bar ) { \
336
337
338
339
        char psz_tmp[10]; \
        snprintf( (char*)psz_tmp, 10, "%d", p_tag->bar() ); \
        vlc_meta_Set##foo( p_meta, (char*)psz_tmp ); \
    }
340

Rafaël Carré's avatar
Rafaël Carré committed
341
342
343
344
345
346
347
    SET( Title, title );
    SET( Artist, artist );
    SET( Album, album );
    SET( Description, comment );
    SET( Genre, genre );
    SETINT( Date, year );
    SETINT( Tracknum , track );
348
#undef SET
349
#undef SETINT
350

351
    DetectImage( f, p_demux );
Rafaël Carré's avatar
Rafaël Carré committed
352

Rafaël Carré's avatar
Rafaël Carré committed
353
    return VLC_SUCCESS;
354
}
355
356
357

static int WriteMeta( vlc_object_t *p_this )
{
Rafaël Carré's avatar
Rafaël Carré committed
358
359
360
    playlist_t *p_playlist = (playlist_t *)p_this;
    meta_export_t *p_export = (meta_export_t *)p_playlist->p_private;
    input_item_t *p_item = p_export->p_item;
361

362
363
364
365
366
    if( p_item == NULL )
    {
        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
367

Rafaël Carré's avatar
Rafaël Carré committed
368
    TagLib::FileRef f( p_export->psz_file );
369
    if( f.isNull() || !f.tag() || f.file()->readOnly() )
370
    {
371
372
373
374
375
376
        msg_Err( p_this, "File %s can't be opened for tag writing\n",
            p_export->psz_file );
        return VLC_EGENERIC;
    }

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

378
    TagLib::Tag *p_tag = f.tag();
379

380
    char *psz_meta;
381

382
383
384
385
386
387
388
389
390
391
392
393
#define SET(a,b) \
        if(b) { \
            TagLib::String *psz_##a = new TagLib::String( b, \
                TagLib::String::UTF8 ); \
            p_tag->set##a( *psz_##a ); \
            delete psz_##a; \
        }


    psz_meta = input_item_GetArtist( p_item );
    SET( Artist, psz_meta );
    free( psz_meta );
394

395
396
397
398
399
400
401
    psz_meta = input_item_GetTitle( p_item );
    if( !psz_meta ) psz_meta = input_item_GetName( p_item );
    TagLib::String *psz_title = new TagLib::String( psz_meta,
        TagLib::String::UTF8 );
    p_tag->setTitle( *psz_title );
    delete psz_title;
    free( psz_meta );
402

403
404
405
    psz_meta = input_item_GetAlbum( p_item );
    SET( Album, psz_meta );
    free( psz_meta );
406

407
408
409
    psz_meta = input_item_GetGenre( p_item );
    SET( Genre, psz_meta );
    free( psz_meta );
410

411
412
413
414
415
#undef SET

    psz_meta = input_item_GetDate( p_item );
    if( psz_meta ) p_tag->setYear( atoi( psz_meta ) );
    free( psz_meta );
416

417
418
419
    psz_meta = input_item_GetTrackNum( p_item );
    if( psz_meta ) p_tag->setTrack( atoi( psz_meta ) );
    free( psz_meta );
420

421
422
423
424
425
426
427
428
429
430
431
    if( TagLib::ID3v2::Tag *p_id3tag =
        dynamic_cast<TagLib::ID3v2::Tag *>(p_tag) )
    {
#define WRITE( foo, bar ) \
        psz_meta = input_item_Get##foo( p_item ); \
        if( psz_meta ) \
        { \
            TagLib::ByteVector p_byte( bar, 4 ); \
            TagLib::ID3v2::TextIdentificationFrame p_frame( p_byte ); \
            p_frame.setText( psz_meta ); \
            p_id3tag->addFrame( &p_frame ); \
432
            free( psz_meta ); \
433
434
435
436
437
438
        } \

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

440
#undef WRITE
441
    }
442
443
444

    f.save();
    return VLC_SUCCESS;
445
446
447
448
449
450
451
452
453
}

static int DownloadArt( vlc_object_t *p_this )
{
    /* We need to be passed the file name
     * Fetch the thing from the file, save it to the cache folder
     */
    return VLC_EGENERIC;
}
454