]> git.sesse.net Git - vlc/blob - modules/meta_engine/taglib.cpp
Taglib: don't try to add empty meta in APE
[vlc] / modules / meta_engine / taglib.cpp
1 /*****************************************************************************
2  * taglib.cpp: Taglib tag parser/writer
3  *****************************************************************************
4  * Copyright (C) 2003-2011 the VideoLAN team
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  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_demux.h>              /* demux_meta_t */
33 #include <vlc_strings.h>            /* vlc_b64_decode_binary */
34 #include <vlc_charset.h>            /* ToLocale, LocaleFree */
35 #include <vlc_input.h>              /* for attachment_new */
36
37 #ifdef WIN32
38 # include <io.h>
39 #else
40 # include <unistd.h>
41 #endif
42
43
44 // Taglib headers
45 #include <taglib.h>
46 #define VERSION_INT(a, b, c) ((a)<<16 | (b)<<8 | (c))
47 #define TAGLIB_VERSION VERSION_INT(TAGLIB_MAJOR_VERSION, \
48                                    TAGLIB_MINOR_VERSION, \
49                                    TAGLIB_PATCH_VERSION)
50
51 #include <fileref.h>
52 #include <tag.h>
53 #include <tbytevector.h>
54
55 #if TAGLIB_VERSION >= VERSION_INT(1,7,0)
56 # define TAGLIB_HAVE_APEFILE_H
57 # include <apefile.h>
58 # ifdef TAGLIB_WITH_ASF                     // ASF pictures comes with v1.7.0
59 #  define TAGLIB_HAVE_ASFPICTURE_H
60 #  include <asffile.h>
61 # endif
62 #endif
63
64 #include <apetag.h>
65 #include <flacfile.h>
66 #include <mpcfile.h>
67 #include <mpegfile.h>
68 #include <oggfile.h>
69 #include <oggflacfile.h>
70
71 #if TAGLIB_VERSION >= VERSION_INT(1,6,0)
72 # define TAGLIB_HAVE_AIFF_WAV_H
73 # include <aifffile.h>
74 # include <wavfile.h>
75 #else
76 # include <id3v2tag.h>
77 #endif
78
79 #if TAGLIB_VERSION >= VERSION_INT(1,6,1) && defined(TAGLIB_WITH_MP4)
80 # define TAGLIB_HAVE_MP4COVERTART_H
81 # include <mp4file.h>
82 #endif
83
84 #include <speexfile.h>
85 #include <trueaudiofile.h>
86 #include <vorbisfile.h>
87 #include <wavpackfile.h>
88
89 #include <attachedpictureframe.h>
90 #include <textidentificationframe.h>
91 #include <uniquefileidentifierframe.h>
92
93 // taglib is not thread safe
94 static vlc_mutex_t taglib_lock = VLC_STATIC_MUTEX;
95
96 // Local functions
97 static int ReadMeta    ( vlc_object_t * );
98 static int WriteMeta   ( vlc_object_t * );
99
100 vlc_module_begin ()
101     set_capability( "meta reader", 1000 )
102     set_callbacks( ReadMeta, NULL )
103     add_submodule ()
104         set_capability( "meta writer", 50 )
105         set_callbacks( WriteMeta, NULL )
106 vlc_module_end ()
107
108 using namespace TagLib;
109
110
111 /**
112  * Read meta information from APE tags
113  * @param tag: the APE tag
114  * @param p_demux_meta: the demuxer meta
115  * @param p_meta: the meta
116  */
117 static void ReadMetaFromAPE( APE::Tag* tag, demux_meta_t*, vlc_meta_t* p_meta )
118 {
119     APE::Item item;
120 #define SET( keyName, metaName ) \
121     item = tag->itemListMap()[keyName]; \
122     if( !item.isEmpty() ) vlc_meta_Set##metaName( p_meta, item.toString().toCString( true ) ); \
123
124     SET( "COPYRIGHT", Copyright );
125     SET( "LANGUAGE", Language );
126     SET( "PUBLISHER", Publisher );
127
128 #undef SET
129 }
130
131
132 #ifdef TAGLIB_HAVE_ASFPICTURE_H
133 /**
134  * Read meta information from APE tags
135  * @param tag: the APE tag
136  * @param p_demux_meta: the demuxer meta
137  * @param p_meta: the meta
138  */
139 static void ReadMetaFromASF( ASF::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
140 {
141     // List the pictures
142     ASF::AttributeList list = tag->attributeListMap()["WM/Picture"];
143     ASF::AttributeList::Iterator iter;
144     for( iter = list.begin(); iter != list.end(); iter++ )
145     {
146         const ASF::Picture asfPicture = (*iter).toPicture();
147         const ByteVector picture = asfPicture.picture();
148         const char *psz_mime = asfPicture.mimeType().toCString();
149         const char *p_data = picture.data();
150         const unsigned i_data = picture.size();
151         char *psz_name;
152         input_attachment_t *p_attachment;
153
154         if( asfPicture.description().size() > 0 )
155             psz_name = strdup( asfPicture.description().toCString( true ) );
156         else
157         {
158             if( asprintf( &psz_name, "%i", asfPicture.type() ) == -1 )
159                 continue;
160         }
161
162         msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
163                  psz_name, psz_mime, i_data );
164
165         p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
166                                 psz_name, p_data, i_data );
167         if( p_attachment )
168             TAB_APPEND_CAST( (input_attachment_t**),
169                              p_demux_meta->i_attachments, p_demux_meta->attachments,
170                              p_attachment );
171         free( psz_name );
172
173         char *psz_url;
174         if( asprintf( &psz_url, "attachment://%s",
175                       p_attachment->psz_name ) == -1 )
176             continue;
177         vlc_meta_SetArtURL( p_meta, psz_url );
178         free( psz_url );
179     }
180 }
181 #endif
182
183
184 /**
185  * Read meta information from id3v2 tags
186  * @param tag: the id3v2 tag
187  * @param p_demux_meta: the demuxer meta
188  * @param p_meta: the meta
189  */
190 static void ReadMetaFromId3v2( ID3v2::Tag* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
191 {
192     // Get the unique file identifier
193     ID3v2::FrameList list = tag->frameListMap()["UFID"];
194     ID3v2::FrameList::Iterator iter;
195     for( iter = list.begin(); iter != list.end(); iter++ )
196     {
197         ID3v2::UniqueFileIdentifierFrame* p_ufid =
198                 dynamic_cast<ID3v2::UniqueFileIdentifierFrame*>(*iter);
199         if( !p_ufid )
200             continue;
201         const char *owner = p_ufid->owner().toCString();
202         if (!strcmp( owner, "http://musicbrainz.org" ))
203         {
204             /* ID3v2 UFID contains up to 64 bytes binary data
205              * but in our case it will be a '\0'
206              * terminated string */
207             char psz_ufid[64];
208             int max_size = __MIN( p_ufid->identifier().size(), 63);
209             strncpy( psz_ufid, p_ufid->identifier().data(), max_size );
210             psz_ufid[max_size] = '\0';
211             vlc_meta_SetTrackID( p_meta, psz_ufid );
212         }
213     }
214
215     // Get the use text
216     list = tag->frameListMap()["TXXX"];
217     for( iter = list.begin(); iter != list.end(); iter++ )
218     {
219         ID3v2::UserTextIdentificationFrame* p_txxx =
220                 dynamic_cast<ID3v2::UserTextIdentificationFrame*>(*iter);
221         if( !p_txxx )
222             continue;
223         vlc_meta_AddExtra( p_meta, p_txxx->description().toCString( true ),
224                            p_txxx->fieldList().back().toCString( true ) );
225     }
226
227     // Get some more information
228 #define SET( tagName, metaName )                                               \
229     list = tag->frameListMap()[tagName];                                       \
230     if( !list.isEmpty() )                                                      \
231         vlc_meta_Set##metaName( p_meta,                                        \
232                                 (*list.begin())->toString().toCString( true ) );
233
234     SET( "TCOP", Copyright );
235     SET( "TENC", EncodedBy );
236     SET( "TLAN", Language );
237     SET( "TPUB", Publisher );
238
239 #undef SET
240
241     /* Preferred type of image
242      * The 21 types are defined in id3v2 standard:
243      * http://www.id3.org/id3v2.4.0-frames */
244     static const int pi_cover_score[] = {
245         0,  /* Other */
246         5,  /* 32x32 PNG image that should be used as the file icon */
247         4,  /* File icon of a different size or format. */
248         20, /* Front cover image of the album. */
249         19, /* Back cover image of the album. */
250         13, /* Inside leaflet page of the album. */
251         18, /* Image from the album itself. */
252         17, /* Picture of the lead artist or soloist. */
253         16, /* Picture of the artist or performer. */
254         14, /* Picture of the conductor. */
255         15, /* Picture of the band or orchestra. */
256         9,  /* Picture of the composer. */
257         8,  /* Picture of the lyricist or text writer. */
258         7,  /* Picture of the recording location or studio. */
259         10, /* Picture of the artists during recording. */
260         11, /* Picture of the artists during performance. */
261         6,  /* Picture from a movie or video related to the track. */
262         1,  /* Picture of a large, coloured fish. */
263         12, /* Illustration related to the track. */
264         3,  /* Logo of the band or performer. */
265         2   /* Logo of the publisher (record company). */
266     };
267     #define PI_COVER_SCORE_SIZE (sizeof (pi_cover_score) / sizeof (pi_cover_score[0]))
268     int i_score = -1;
269
270     // Try now to get embedded art
271     list = tag->frameListMap()[ "APIC" ];
272     if( list.isEmpty() )
273         return;
274
275     TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
276     for( iter = list.begin(); iter != list.end(); iter++ )
277     {
278         ID3v2::AttachedPictureFrame* p_apic =
279             dynamic_cast<ID3v2::AttachedPictureFrame*>(*iter);
280         if( !p_apic )
281             continue;
282         input_attachment_t *p_attachment;
283
284         const char *psz_mime;
285         char *psz_name, *psz_description;
286
287         // Get the mime and description of the image.
288         // If the description is empty, take the type as a description
289         psz_mime = p_apic->mimeType().toCString( true );
290         if( p_apic->description().size() > 0 )
291             psz_description = strdup( p_apic->description().toCString( true ) );
292         else
293         {
294             if( asprintf( &psz_description, "%i", p_apic->type() ) == -1 )
295                 psz_description = NULL;
296         }
297
298         if( !psz_description )
299             continue;
300         psz_name = psz_description;
301
302         /* some old iTunes version not only sets incorrectly the mime type
303          * or the description of the image,
304          * but also embeds incorrectly the image.
305          * Recent versions seem to behave correctly */
306         if( !strncmp( psz_mime, "PNG", 3 ) ||
307             !strncmp( psz_name, "\xC2\x89PNG", 5 ) )
308         {
309             msg_Warn( p_demux_meta, "Invalid picture embedded by broken iTunes version" );
310             free( psz_description );
311             continue;
312         }
313
314         const ByteVector picture = p_apic->picture();
315         const char *p_data = picture.data();
316         const unsigned i_data = picture.size();
317
318         msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %u bytes",
319                  psz_name, psz_mime, i_data );
320
321         p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
322                                 psz_description, p_data, i_data );
323         if( p_attachment )
324             TAB_APPEND_CAST( (input_attachment_t**),
325                              p_demux_meta->i_attachments, p_demux_meta->attachments,
326                              p_attachment );
327         free( psz_description );
328
329         unsigned i_pic_type = p_apic->type();
330         if( i_pic_type >= PI_COVER_SCORE_SIZE )
331             i_pic_type = 0; // Defaults to "Other"
332
333         if( pi_cover_score[i_pic_type] > i_score )
334         {
335             i_score = pi_cover_score[i_pic_type];
336             char *psz_url;
337             if( asprintf( &psz_url, "attachment://%s",
338                           p_attachment->psz_name ) == -1 )
339                 continue;
340             vlc_meta_SetArtURL( p_meta, psz_url );
341             free( psz_url );
342         }
343     }
344 }
345
346
347 /**
348  * Read the meta information from XiphComments
349  * @param tag: the Xiph Comment
350  * @param p_demux_meta: the demuxer meta
351  * @param p_meta: the meta
352  */
353 static void ReadMetaFromXiph( Ogg::XiphComment* tag, demux_meta_t* p_demux_meta, vlc_meta_t* p_meta )
354 {
355     StringList list;
356 #define SET( keyName, metaName )                                               \
357     list = tag->fieldListMap()[keyName];                                       \
358     if( !list.isEmpty() )                                                      \
359         vlc_meta_Set##metaName( p_meta, (*list.begin()).toCString( true ) );
360
361     SET( "COPYRIGHT", Copyright );
362 #undef SET
363
364     // Try now to get embedded art
365     StringList mime_list = tag->fieldListMap()[ "COVERARTMIME" ];
366     StringList art_list = tag->fieldListMap()[ "COVERART" ];
367
368     // We get only the first covert art
369     if( mime_list.size() > 1 || art_list.size() > 1 )
370         msg_Warn( p_demux_meta, "Found %i embedded arts, so using only the first one",
371                   art_list.size() );
372     else if( mime_list.size() == 0 || art_list.size() == 0 )
373         return;
374
375     input_attachment_t *p_attachment;
376
377     const char* psz_name = "cover";
378     const char* psz_mime = mime_list[0].toCString(true);
379     const char* psz_description = "cover";
380
381     uint8_t *p_data;
382     int i_data = vlc_b64_decode_binary( &p_data, art_list[0].toCString(true) );
383
384     msg_Dbg( p_demux_meta, "Found embedded art: %s (%s) is %i bytes",
385              psz_name, psz_mime, i_data );
386
387     TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
388               p_attachment = vlc_input_attachment_New( psz_name, psz_mime,
389               psz_description, p_data, i_data );
390     free( p_data );
391
392     TAB_APPEND_CAST( (input_attachment_t**),
393                      p_demux_meta->i_attachments, p_demux_meta->attachments,
394                      p_attachment );
395
396     vlc_meta_SetArtURL( p_meta, "attachment://cover" );
397 }
398
399
400 #ifdef TAGLIB_HAVE_MP4COVERTART_H
401 /**
402  * Read the meta information from mp4 specific tags
403  * @param tag: the mp4 tag
404  * @param p_demux_meta: the demuxer meta
405  * @param p_meta: the meta
406  */
407 static void ReadMetaFromMP4( MP4::Tag* tag, demux_meta_t *p_demux_meta, vlc_meta_t* p_meta )
408 {
409     if( tag->itemListMap().contains("covr") )
410     {
411         MP4::CoverArtList list = tag->itemListMap()["covr"].toCoverArtList();
412         const char *psz_format = list[0].format() == MP4::CoverArt::PNG ? "image/png" : "image/jpeg";
413
414         msg_Dbg( p_demux_meta, "Found embedded art (%s) is %i bytes",
415                  psz_format, list[0].data().size() );
416
417         TAB_INIT( p_demux_meta->i_attachments, p_demux_meta->attachments );
418         input_attachment_t *p_attachment =
419                 vlc_input_attachment_New( "cover", psz_format, "cover",
420                                           list[0].data().data(), list[0].data().size() );
421         TAB_APPEND_CAST( (input_attachment_t**),
422                          p_demux_meta->i_attachments, p_demux_meta->attachments,
423                          p_attachment );
424         vlc_meta_SetArtURL( p_meta, "attachment://cover" );
425     }
426 }
427 #endif
428
429
430 /**
431  * Get the tags from the file using TagLib
432  * @param p_this: the demux object
433  * @return VLC_SUCCESS if the operation success
434  */
435 static int ReadMeta( vlc_object_t* p_this)
436 {
437     vlc_mutex_locker locker (&taglib_lock);
438     demux_meta_t*   p_demux_meta = (demux_meta_t *)p_this;
439     demux_t*        p_demux = p_demux_meta->p_demux;
440     vlc_meta_t*     p_meta;
441     FileRef f;
442
443     p_demux_meta->p_meta = NULL;
444     if( strcmp( p_demux->psz_access, "file" ) )
445         return VLC_EGENERIC;
446
447     char *psz_path = strdup( p_demux->psz_file );
448     if( !psz_path )
449         return VLC_ENOMEM;
450
451 #if defined(WIN32) || defined (UNDER_CE)
452     wchar_t *wpath = ToWide( psz_path );
453     if( wpath == NULL )
454     {
455         free( psz_path );
456         return VLC_EGENERIC;
457     }
458     f = FileRef( wpath );
459     free( wpath );
460 #else
461     const char* local_name = ToLocale( psz_path );
462     if( !local_name )
463     {
464         free( psz_path );
465         return VLC_EGENERIC;
466     }
467     f = FileRef( local_name );
468     LocaleFree( local_name );
469 #endif
470     free( psz_path );
471
472     if( f.isNull() )
473         return VLC_EGENERIC;
474     if( !f.tag() || f.tag()->isEmpty() )
475         return VLC_EGENERIC;
476
477     p_demux_meta->p_meta = p_meta = vlc_meta_New();
478     if( !p_meta )
479         return VLC_ENOMEM;
480
481
482     // Read the tags from the file
483     Tag* p_tag = f.tag();
484
485 #define SET( tag, meta )                                                       \
486     if( !p_tag->tag().isNull() && !p_tag->tag().isEmpty() )                    \
487         vlc_meta_Set##meta( p_meta, p_tag->tag().toCString(true) )
488 #define SETINT( tag, meta )                                                    \
489     if( p_tag->tag() )                                                         \
490     {                                                                          \
491         char psz_tmp[10];                                                      \
492         snprintf( psz_tmp, 10, "%d", p_tag->tag() );                           \
493         vlc_meta_Set##meta( p_meta, psz_tmp );                                 \
494     }
495
496     SET( title, Title );
497     SET( artist, Artist );
498     SET( album, Album );
499     SET( comment, Description );
500     SET( genre, Genre );
501     SETINT( year, Date );
502     SETINT( track, TrackNum );
503
504 #undef SETINT
505 #undef SET
506
507
508     // Try now to read special tags
509 #ifdef TAGLIB_HAVE_APEFILE_H
510     if( APE::File* ape = dynamic_cast<APE::File*>(f.file()) )
511     {
512         if( ape->APETag() )
513             ReadMetaFromAPE( ape->APETag(), p_demux_meta, p_meta );
514     }
515     else
516 #endif
517 #ifdef TAGLIB_HAVE_ASFPICTURE_H
518     if( ASF::File* asf = dynamic_cast<ASF::File*>(f.file()) )
519     {
520         if( asf->tag() )
521             ReadMetaFromASF( asf->tag(), p_demux_meta, p_meta );
522     }
523     else
524 #endif
525     if( FLAC::File* flac = dynamic_cast<FLAC::File*>(f.file()) )
526     {
527         if( flac->ID3v2Tag() )
528             ReadMetaFromId3v2( flac->ID3v2Tag(), p_demux_meta, p_meta );
529         else if( flac->xiphComment() )
530             ReadMetaFromXiph( flac->xiphComment(), p_demux_meta, p_meta );
531     }
532 #ifdef TAGLIB_HAVE_MP4COVERTART_H
533     else if( MP4::File *mp4 = dynamic_cast<MP4::File*>(f.file()) )
534     {
535         if( mp4->tag() )
536             ReadMetaFromMP4( mp4->tag(), p_demux_meta, p_meta );
537     }
538 #endif
539     else if( MPC::File* mpc = dynamic_cast<MPC::File*>(f.file()) )
540     {
541         if( mpc->APETag() )
542             ReadMetaFromAPE( mpc->APETag(), p_demux_meta, p_meta );
543     }
544     else if( MPEG::File* mpeg = dynamic_cast<MPEG::File*>(f.file()) )
545     {
546         if( mpeg->ID3v2Tag() )
547             ReadMetaFromId3v2( mpeg->ID3v2Tag(), p_demux_meta, p_meta );
548         else if( mpeg->APETag() )
549             ReadMetaFromAPE( mpeg->APETag(), p_demux_meta, p_meta );
550     }
551     else if( dynamic_cast<Ogg::File*>(f.file()) )
552     {
553         if( Ogg::FLAC::File* ogg_flac = dynamic_cast<Ogg::FLAC::File*>(f.file()))
554             ReadMetaFromXiph( ogg_flac->tag(), p_demux_meta, p_meta );
555         else if( Ogg::Speex::File* ogg_speex = dynamic_cast<Ogg::Speex::File*>(f.file()) )
556             ReadMetaFromXiph( ogg_speex->tag(), p_demux_meta, p_meta );
557         else if( Ogg::Vorbis::File* ogg_vorbis = dynamic_cast<Ogg::Vorbis::File*>(f.file()) )
558             ReadMetaFromXiph( ogg_vorbis->tag(), p_demux_meta, p_meta );
559     }
560 #ifdef TAGLIB_HAVE_AIFF_WAV_H
561     else if( dynamic_cast<RIFF::File*>(f.file()) )
562     {
563         if( RIFF::AIFF::File* riff_aiff = dynamic_cast<RIFF::AIFF::File*>(f.file()) )
564             ReadMetaFromId3v2( riff_aiff->tag(), p_demux_meta, p_meta );
565         else if( RIFF::WAV::File* riff_wav = dynamic_cast<RIFF::WAV::File*>(f.file()) )
566             ReadMetaFromId3v2( riff_wav->tag(), p_demux_meta, p_meta );
567     }
568 #endif
569     else if( TrueAudio::File* trueaudio = dynamic_cast<TrueAudio::File*>(f.file()) )
570     {
571         if( trueaudio->ID3v2Tag() )
572             ReadMetaFromId3v2( trueaudio->ID3v2Tag(), p_demux_meta, p_meta );
573     }
574     else if( WavPack::File* wavpack = dynamic_cast<WavPack::File*>(f.file()) )
575     {
576         if( wavpack->APETag() )
577             ReadMetaFromAPE( wavpack->APETag(), p_demux_meta, p_meta );
578     }
579
580     return VLC_SUCCESS;
581 }
582
583
584 /**
585  * Write meta information to APE tags
586  * @param tag: the APE tag
587  * @param p_item: the input item
588  */
589 static void WriteMetaToAPE( APE::Tag* tag, input_item_t* p_item )
590 {
591     char* psz_meta;
592 #define WRITE( metaName, keyName )                      \
593     psz_meta = input_item_Get##metaName( p_item );      \
594     if( psz_meta )                                      \
595     {                                                   \
596         String key( keyName, String::UTF8 );            \
597         String value( psz_meta, String::UTF8 );         \
598         tag->addValue( key, value, true );              \
599     }                                                   \
600     free( psz_meta );
601
602     WRITE( Copyright, "COPYRIGHT" );
603     WRITE( Language, "LANGUAGE" );
604     WRITE( Publisher, "PUBLISHER" );
605
606 #undef WRITE
607 }
608
609
610 /**
611  * Write meta information to id3v2 tags
612  * @param tag: the id3v2 tag
613  * @param p_input: the input item
614  */
615 static void WriteMetaToId3v2( ID3v2::Tag* tag, input_item_t* p_item )
616 {
617     char* psz_meta;
618 #define WRITE( metaName, tagName )                                            \
619     psz_meta = input_item_Get##metaName( p_item );                            \
620     if( psz_meta )                                                            \
621     {                                                                         \
622         ByteVector p_byte( tagName, 4 );                                      \
623         tag->removeFrames( p_byte );                                         \
624         ID3v2::TextIdentificationFrame* p_frame =                             \
625             new ID3v2::TextIdentificationFrame( p_byte, String::UTF8 );       \
626         p_frame->setText( psz_meta );                                         \
627         tag->addFrame( p_frame );                                             \
628     }                                                                         \
629     free( psz_meta );
630
631     WRITE( Copyright, "TCOP" );
632     WRITE( EncodedBy, "TENC" );
633     WRITE( Language,  "TLAN" );
634     WRITE( Publisher, "TPUB" );
635
636 #undef WRITE
637 }
638
639
640 /**
641  * Write the meta information to XiphComments
642  * @param tag: the Xiph Comment
643  * @param p_input: the input item
644  */
645 static void WriteMetaToXiph( Ogg::XiphComment* tag, input_item_t* p_item )
646 {
647     char* psz_meta;
648 #define WRITE( metaName, keyName )                      \
649     psz_meta = input_item_Get##metaName( p_item );      \
650     if( psz_meta )                                      \
651     {                                                   \
652         String key( keyName, String::UTF8 );            \
653         String value( psz_meta, String::UTF8 );         \
654         tag->addField( key, value, true );              \
655     }                                                   \
656     free( psz_meta );
657
658     WRITE( Copyright, "COPYRIGHT" );
659
660 #undef WRITE
661 }
662
663
664 /**
665  * Set the tags to the file using TagLib
666  * @param p_this: the demux object
667  * @return VLC_SUCCESS if the operation success
668  */
669
670 static int WriteMeta( vlc_object_t *p_this )
671 {
672     vlc_mutex_locker locker (&taglib_lock);
673     meta_export_t *p_export = (meta_export_t *)p_this;
674     input_item_t *p_item = p_export->p_item;
675     FileRef f;
676
677     if( !p_item )
678     {
679         msg_Err( p_this, "Can't save meta data of an empty input" );
680         return VLC_EGENERIC;
681     }
682
683 #if defined(WIN32) || defined (UNDER_CE)
684     wchar_t *wpath = ToWide( p_export->psz_file );
685     if( wpath == NULL )
686         return VLC_EGENERIC;
687     f = FileRef( wpath );
688     free( wpath );
689 #else
690     const char* local_name = ToLocale( p_export->psz_file );
691     if( !local_name )
692         return VLC_EGENERIC;
693     f = FileRef( local_name );
694     LocaleFree( local_name );
695 #endif
696
697     if( f.isNull() || !f.tag() || f.file()->readOnly() )
698     {
699         msg_Err( p_this, "File %s can't be opened for tag writing",
700                  p_export->psz_file );
701         return VLC_EGENERIC;
702     }
703
704     msg_Dbg( p_this, "Writing metadata for %s", p_export->psz_file );
705
706     Tag *p_tag = f.tag();
707
708     char *psz_meta;
709
710 #define SET( a, b )                                             \
711     psz_meta = input_item_Get ## a( p_item );                   \
712     if( psz_meta )                                              \
713     {                                                           \
714         String tmp( psz_meta, String::UTF8 );                   \
715         p_tag->set##b( tmp );                                   \
716     }                                                           \
717     free( psz_meta );
718
719     // Saving all common fields
720     // If the title is empty, use the name
721     SET( TitleFbName, Title );
722     SET( Artist, Artist );
723     SET( Album, Album );
724     SET( Description, Comment );
725     SET( Genre, Genre );
726
727 #undef SET
728
729     psz_meta = input_item_GetDate( p_item );
730     if( !EMPTY_STR(psz_meta) ) p_tag->setYear( atoi( psz_meta ) );
731     free( psz_meta );
732
733     psz_meta = input_item_GetTrackNum( p_item );
734     if( !EMPTY_STR(psz_meta) ) p_tag->setTrack( atoi( psz_meta ) );
735     free( psz_meta );
736
737
738     // Try now to write special tags
739 #ifdef TAGLIB_HAVE_APEFILE_H
740     if( APE::File* ape = dynamic_cast<APE::File*>(f.file()) )
741     {
742         if( ape->APETag() )
743             WriteMetaToAPE( ape->APETag(), p_item );
744     }
745     else
746 #endif
747     if( FLAC::File* flac = dynamic_cast<FLAC::File*>(f.file()) )
748     {
749         if( flac->ID3v2Tag() )
750             WriteMetaToId3v2( flac->ID3v2Tag(), p_item );
751         else if( flac->xiphComment() )
752             WriteMetaToXiph( flac->xiphComment(), p_item );
753     }
754     else if( MPC::File* mpc = dynamic_cast<MPC::File*>(f.file()) )
755     {
756         if( mpc->APETag() )
757             WriteMetaToAPE( mpc->APETag(), p_item );
758     }
759     else if( MPEG::File* mpeg = dynamic_cast<MPEG::File*>(f.file()) )
760     {
761         if( mpeg->ID3v2Tag() )
762             WriteMetaToId3v2( mpeg->ID3v2Tag(), p_item );
763         else if( mpeg->APETag() )
764             WriteMetaToAPE( mpeg->APETag(), p_item );
765     }
766     else if( dynamic_cast<Ogg::File*>(f.file()) )
767     {
768         if( Ogg::FLAC::File* ogg_flac = dynamic_cast<Ogg::FLAC::File*>(f.file()))
769             WriteMetaToXiph( ogg_flac->tag(), p_item );
770         else if( Ogg::Speex::File* ogg_speex = dynamic_cast<Ogg::Speex::File*>(f.file()) )
771             WriteMetaToXiph( ogg_speex->tag(), p_item );
772         else if( Ogg::Vorbis::File* ogg_vorbis = dynamic_cast<Ogg::Vorbis::File*>(f.file()) )
773             WriteMetaToXiph( ogg_vorbis->tag(), p_item );
774     }
775 #ifdef TAGLIB_HAVE_AIFF_WAV_H
776     else if( dynamic_cast<RIFF::File*>(f.file()) )
777     {
778         if( RIFF::AIFF::File* riff_aiff = dynamic_cast<RIFF::AIFF::File*>(f.file()) )
779             WriteMetaToId3v2( riff_aiff->tag(), p_item );
780         else if( RIFF::WAV::File* riff_wav = dynamic_cast<RIFF::WAV::File*>(f.file()) )
781             WriteMetaToId3v2( riff_wav->tag(), p_item );
782     }
783 #endif
784     else if( TrueAudio::File* trueaudio = dynamic_cast<TrueAudio::File*>(f.file()) )
785     {
786         if( trueaudio->ID3v2Tag() )
787             WriteMetaToId3v2( trueaudio->ID3v2Tag(), p_item );
788     }
789     else if( WavPack::File* wavpack = dynamic_cast<WavPack::File*>(f.file()) )
790     {
791         if( wavpack->APETag() )
792             WriteMetaToAPE( wavpack->APETag(), p_item );
793     }
794
795     // Save the meta data
796     f.save();
797
798     return VLC_SUCCESS;
799 }
800