]> git.sesse.net Git - vlc/blob - modules/meta_engine/taglib.cpp
e9c7c9dd304281601f995564fef23f6861db8917
[vlc] / modules / meta_engine / taglib.cpp
1 /*****************************************************************************
2  * taglib.cpp: Taglib tag parser/writer
3  *****************************************************************************
4  * Copyright (C) 2003-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Rafaël Carré <funman@videolanorg>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include <vlc/vlc.h>
26 #include <vlc_playlist.h>
27 #include <vlc_meta.h>
28 #include <vlc_demux.h>
29
30 #include <fileref.h>
31 #include <tag.h>
32 #include <tstring.h>
33 #include <id3v2tag.h>
34 #include <textidentificationframe.h>
35 #include <tbytevector.h>
36 #include <mpegfile.h>
37 #include <flacfile.h>
38 #if 0
39 #include <oggflacfile.h>
40 #endif
41 #include <flacfile.h>
42 #include <flacproperties.h>
43 #include <vorbisfile.h>
44 #include <vorbisproperties.h>
45 #include <uniquefileidentifierframe.h>
46 #include <textidentificationframe.h>
47 #if 0 /* parse the tags without taglib helpers? */
48 #include <relativevolumeframe.h>
49 #endif
50
51 static int  ReadMeta    ( vlc_object_t * );
52 static int  DownloadArt ( vlc_object_t * );
53 static int  WriteMeta   ( vlc_object_t * );
54
55 vlc_module_begin();
56     set_capability( "meta reader", 1000 );
57     set_callbacks( ReadMeta, NULL );
58     add_submodule();
59         set_capability( "art downloader", 50 );
60         set_callbacks( DownloadArt, NULL );
61     add_submodule();
62         set_capability( "meta writer", 50 );
63         set_callbacks( WriteMeta, NULL );
64 vlc_module_end();
65
66 static bool checkID3Image( const TagLib::ID3v2::Tag *p_tag )
67 {
68     TagLib::ID3v2::FrameList l = p_tag->frameListMap()[ "APIC" ];
69     return !l.isEmpty();
70 }
71
72 /* Try detecting embedded art */
73 static void DetectImage( TagLib::FileRef f, vlc_meta_t *p_meta )
74 {
75     if( TagLib::MPEG::File *mpeg =
76                dynamic_cast<TagLib::MPEG::File *>(f.file() ) )
77     {
78         if( mpeg->ID3v2Tag() && checkID3Image( mpeg->ID3v2Tag() ) )
79             vlc_meta_SetArtURL( p_meta, "APIC" );
80     }
81     else if( TagLib::FLAC::File *flac =
82              dynamic_cast<TagLib::FLAC::File *>(f.file() ) )
83     {
84         if( flac->ID3v2Tag() && checkID3Image( flac->ID3v2Tag() ) )
85             vlc_meta_SetArtURL( p_meta, "APIC" );
86     }
87 #if 0
88 /* This needs special additions to taglib */
89  * else if( TagLib::MP4::File *mp4 =
90                dynamic_cast<TagLib::MP4::File *>( f.file() ) )
91     {
92         TagLib::MP4::Tag *mp4tag =
93                 dynamic_cast<TagLib::MP4::Tag *>( mp4->tag() );
94         if( mp4tag && mp4tag->cover().size() )
95             vlc_meta_SetArtURL( p_meta, "MP4C" );
96     }
97 #endif
98 }
99
100 static int ReadMeta( vlc_object_t *p_this )
101 {
102     demux_t *p_demux = (demux_t *)p_this;
103
104     if( strncmp( p_demux->psz_access, "file", 4 ) )
105         return VLC_EGENERIC;
106
107     TagLib::FileRef f( p_demux->psz_path );
108     if( f.isNull() )
109         return VLC_EGENERIC;
110
111     if ( !f.tag() || f.tag()->isEmpty() )
112         return VLC_EGENERIC;
113
114     if( !p_demux->p_private )
115         p_demux->p_private = (void*)vlc_meta_New();
116     vlc_meta_t *p_meta = (vlc_meta_t *)(p_demux->p_private );
117     TagLib::Tag *p_tag = f.tag();
118
119     if( TagLib::MPEG::File *p_mpeg =
120         dynamic_cast<TagLib::MPEG::File *>(f.file() ) )
121     {
122         if( p_mpeg->ID3v2Tag() )
123         {
124             TagLib::ID3v2::Tag *p_tag = p_mpeg->ID3v2Tag();
125             TagLib::ID3v2::FrameList list = p_tag->frameListMap()["UFID"];
126             TagLib::ID3v2::UniqueFileIdentifierFrame* p_ufid;
127             for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
128                     iter != list.end(); iter++ )
129             {
130                 p_ufid = dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(*iter);
131                 const char *owner = p_ufid->owner().toCString();
132                 if (!strcmp( owner, "http://musicbrainz.org" ))
133                 {
134                     /* ID3v2 UFID contains up to 64 bytes binary data
135                         * but in our case it will be a '\0' 
136                         * terminated string */
137                     char *psz_ufid = (char*) malloc( 64 );
138                     int j = 0;
139                     while( ( j < 63 ) &&
140                             ( j < p_ufid->identifier().size() ) )
141                         psz_ufid[j] = p_ufid->identifier()[j++];
142                     psz_ufid[j] = '\0';
143                     vlc_meta_SetTrackID( p_meta, psz_ufid );
144                     free( psz_ufid );
145                 }
146             }
147
148             list = p_tag->frameListMap()["TXXX"];
149             TagLib::ID3v2::UserTextIdentificationFrame* p_txxx;
150             for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
151                     iter != list.end(); iter++ )
152             {
153                 p_txxx = dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>(*iter);
154                 const char *psz_desc= p_txxx->description().toCString();
155                 vlc_meta_AddExtra( p_meta, psz_desc, 
156                             p_txxx->fieldList().toString().toCString());
157             }
158 #if 0
159             list = p_tag->frameListMap()["RVA2"];
160             TagLib::ID3v2::RelativeVolumeFrame* p_rva2;
161             for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
162                     iter != list.end(); iter++ )
163             {
164                 p_rva2 = dynamic_cast<TagLib::ID3v2::RelativeVolumeFrame*>(*iter);
165                 /* TODO: process rva2 frames */
166             }
167 #endif
168             list = p_tag->frameList();
169             TagLib::ID3v2::Frame* p_t;
170             char psz_tag[4];
171             for( TagLib::ID3v2::FrameList::Iterator iter = list.begin();
172                     iter != list.end(); iter++ )
173             {
174                 p_t = dynamic_cast<TagLib::ID3v2::Frame*> (*iter);
175                 memcpy( psz_tag, p_t->frameID().data(), 4);
176
177 #define SET( foo, bar ) if( !strncmp( psz_tag, foo, 4 ) ) \
178 vlc_meta_Set##bar( p_meta, p_t->toString().toCString(true))
179                 SET( "TPUB", Publisher );
180                 SET( "TCOP", Copyright );
181                 SET( "TENC", EncodedBy );
182                 SET( "TLAN", Language );
183                 //SET( "POPM", Rating ); /* rating needs special handling in id3v2 */
184                 //if( !strncmp( psz_tag, "RVA2", 4 ) )
185                     /* TODO */
186 #undef SET
187             }
188         }
189     }
190
191     else if( TagLib::Ogg::Vorbis::File *p_ogg_v =
192         dynamic_cast<TagLib::Ogg::Vorbis::File *>(f.file() ) )
193     {
194         int i_ogg_v_length = p_ogg_v->audioProperties()->length();
195
196         input_thread_t *p_input = (input_thread_t *)
197                 vlc_object_find( p_demux,VLC_OBJECT_INPUT, FIND_PARENT );
198         if( p_input )
199         {
200             input_item_t *p_item = input_GetItem( p_input );
201             if( p_item )
202                 input_item_SetDuration( p_item,
203                         (mtime_t) i_ogg_v_length * 1000000 );
204             vlc_object_release( p_input );
205         }
206         
207     }
208 #if 0 /* at this moment, taglib is unable to detect ogg/flac files
209 * becauses type detection is based on file extension:
210 * ogg = ogg/vorbis
211 * flac = flac
212 * ø = ogg/flac
213 */
214     else if( TagLib::Ogg::FLAC::File *p_ogg_f =
215         dynamic_cast<TagLib::Ogg::FLAC::File *>(f.file() ) )
216     {
217         long i_ogg_f_length = p_ogg_f->streamLength();
218         input_thread_t *p_input = (input_thread_t *)
219                 vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
220         if( p_input )
221         {
222             input_item_t *p_item = input_GetItem( p_input );
223             if( p_item )
224                 input_item_SetDuration( p_item,
225                         (mtime_t) i_ogg_f_length * 1000000 );
226             vlc_object_release( p_input );
227         }
228     }
229 #endif
230     else if( TagLib::FLAC::File *p_flac =
231         dynamic_cast<TagLib::FLAC::File *>(f.file() ) )
232     {
233         long i_flac_length = p_flac->audioProperties()->length();
234         input_thread_t *p_input = (input_thread_t *)
235                 vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
236         if( p_input )
237         {
238             input_item_t *p_item = input_GetItem( p_input );
239             if( p_item )
240                 input_item_SetDuration( p_item,
241                         (mtime_t) i_flac_length * 1000000 );
242             vlc_object_release( p_input );
243         }
244     }
245
246 #define SET( foo, bar ) vlc_meta_Set##foo( p_meta, p_tag->bar ().toCString(true))
247 #define SETINT( foo, bar ) { \
248         char psz_tmp[10]; \
249         snprintf( (char*)psz_tmp, 10, "%d", p_tag->bar() ); \
250         vlc_meta_Set##foo( p_meta, (char*)psz_tmp ); \
251     }
252
253     SET( Title, title );
254     SET( Artist, artist );
255     SET( Album, album );
256     SET( Description, comment );
257     SET( Genre, genre );
258     SETINT( Date, year );
259     SETINT( Tracknum , track );
260 #undef SET
261 #undef SETINT
262
263     DetectImage( f, p_meta );
264
265     return VLC_SUCCESS;
266 }
267
268 static int WriteMeta( vlc_object_t *p_this )
269 {
270     playlist_t *p_playlist = (playlist_t *)p_this;
271     meta_export_t *p_export = (meta_export_t *)p_playlist->p_private;
272     input_item_t *p_item = p_export->p_item;
273     
274     if( p_item == NULL )
275     {
276         msg_Err( p_this, "Can't save meta data of an empty input" );
277         return VLC_EGENERIC;
278     }
279
280     TagLib::FileRef f( p_export->psz_file );
281     if( f.isNull() || !f.tag() || f.file()->readOnly() )
282     {
283         msg_Err( p_this, "File %s can't be opened for tag writing\n",
284             p_export->psz_file );
285         return VLC_EGENERIC;
286     }
287
288     msg_Dbg( p_this, "Writing metadata for %s", p_export->psz_file );
289
290     TagLib::Tag *p_tag = f.tag();
291
292     char *psz_meta;
293
294 #define SET(a,b) \
295         if(b) { \
296             TagLib::String *psz_##a = new TagLib::String( b, \
297                 TagLib::String::UTF8 ); \
298             p_tag->set##a( *psz_##a ); \
299             delete psz_##a; \
300         }
301
302
303     psz_meta = input_item_GetArtist( p_item );
304     SET( Artist, psz_meta );
305     free( psz_meta );
306
307     psz_meta = input_item_GetTitle( p_item );
308     if( !psz_meta ) psz_meta = input_item_GetName( p_item );
309     TagLib::String *psz_title = new TagLib::String( psz_meta,
310         TagLib::String::UTF8 );
311     p_tag->setTitle( *psz_title );
312     delete psz_title;
313     free( psz_meta );
314
315     psz_meta = input_item_GetAlbum( p_item );
316     SET( Album, psz_meta );
317     free( psz_meta );
318
319     psz_meta = input_item_GetGenre( p_item );
320     SET( Genre, psz_meta );
321     free( psz_meta );
322
323 #undef SET
324
325     psz_meta = input_item_GetDate( p_item );
326     if( psz_meta ) p_tag->setYear( atoi( psz_meta ) );
327     free( psz_meta );
328
329     psz_meta = input_item_GetTrackNum( p_item );
330     if( psz_meta ) p_tag->setTrack( atoi( psz_meta ) );
331     free( psz_meta );
332
333     if( TagLib::ID3v2::Tag *p_id3tag =
334         dynamic_cast<TagLib::ID3v2::Tag *>(p_tag) )
335     {
336 #define WRITE( foo, bar ) \
337         psz_meta = input_item_Get##foo( p_item ); \
338         if( psz_meta ) \
339         { \
340             TagLib::ByteVector p_byte( bar, 4 ); \
341             TagLib::ID3v2::TextIdentificationFrame p_frame( p_byte ); \
342             p_frame.setText( psz_meta ); \
343             p_id3tag->addFrame( &p_frame ); \
344             free( psz_meta ); \
345         } \
346
347         WRITE( Publisher, "TPUB" );
348         WRITE( Copyright, "TCOP" );
349         WRITE( EncodedBy, "TENC" );
350         WRITE( Language, "TLAN" );
351         
352 #undef WRITE
353     }
354
355     f.save();
356     return VLC_SUCCESS;
357 }
358
359 static int DownloadArt( vlc_object_t *p_this )
360 {
361     /* We need to be passed the file name
362      * Fetch the thing from the file, save it to the cache folder
363      */
364     return VLC_EGENERIC;
365 }
366