]> git.sesse.net Git - vlc/blob - modules/meta_engine/taglib.cpp
Adds some metadata writing capability (ref #1248 )
[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             {
203                 vlc_mutex_lock( &p_item->lock );
204                 p_item->i_duration = i_ogg_v_length * 1000000;
205                 vlc_mutex_unlock( &p_item->lock );
206             }
207             vlc_object_release( p_input );
208         }
209         
210     }
211 #if 0 /* at this moment, taglib is unable to detect ogg/flac files
212 * becauses type detection is based on file extension:
213 * ogg = ogg/vorbis
214 * flac = flac
215 * ø = ogg/flac
216 */
217     else if( TagLib::Ogg::FLAC::File *p_ogg_f =
218         dynamic_cast<TagLib::Ogg::FLAC::File *>(f.file() ) )
219     {
220         long i_ogg_f_length = p_ogg_f->streamLength();
221         input_thread_t *p_input = (input_thread_t *)
222                 vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
223         if( p_input )
224         {
225             input_item_t *p_item = input_GetItem( p_input );
226             if( p_item )
227             {
228                 vlc_mutex_lock( &p_item->lock );
229                 p_item->i_duration = i_ogg_f_length * 1000000;
230                 vlc_mutex_unlock( &p_item->lock );
231             }
232             vlc_object_release( p_input );
233         }
234     }
235 #endif
236     else if( TagLib::FLAC::File *p_flac =
237         dynamic_cast<TagLib::FLAC::File *>(f.file() ) )
238     {
239         long i_flac_length = p_flac->audioProperties()->length();
240         input_thread_t *p_input = (input_thread_t *)
241                 vlc_object_find( p_demux, VLC_OBJECT_INPUT, FIND_PARENT );
242         if( p_input )
243         {
244             input_item_t *p_item = input_GetItem( p_input );
245             if( p_item )
246             {
247                 vlc_mutex_lock( &p_item->lock );
248                 p_item->i_duration = i_flac_length * 1000000;
249                 vlc_mutex_unlock( &p_item->lock );
250             }
251             vlc_object_release( p_input );
252         }
253     }
254
255 #define SET( foo, bar ) vlc_meta_Set##foo( p_meta, p_tag->bar ().toCString(true))
256 #define SETINT( foo, bar ) { \
257         char psz_tmp[10]; \
258         snprintf( (char*)psz_tmp, 10, "%d", p_tag->bar() ); \
259         vlc_meta_Set##foo( p_meta, (char*)psz_tmp ); \
260     }
261
262     SET( Title, title );
263     SET( Artist, artist );
264     SET( Album, album );
265     SET( Description, comment );
266     SET( Genre, genre );
267     SETINT( Date, year );
268     SETINT( Tracknum , track );
269 #undef SET
270 #undef SETINT
271
272     DetectImage( f, p_meta );
273
274     return VLC_SUCCESS;
275 }
276
277 static int WriteMeta( vlc_object_t *p_this )
278 {
279     playlist_t *p_playlist = (playlist_t *)p_this;
280     meta_export_t *p_export = (meta_export_t *)p_playlist->p_private;
281     input_item_t *p_item = p_export->p_item;
282     
283     if( p_item == NULL )
284     {
285         msg_Err( p_this, "Can't save meta data of an empty input" );
286         return VLC_EGENERIC;
287     }
288
289     TagLib::FileRef f( p_export->psz_file );
290     if( f.isNull() || !f.tag() || f.file()->readOnly() )
291     {
292         msg_Err( p_this, "File %s can't be opened for tag writing\n",
293             p_export->psz_file );
294         return VLC_EGENERIC;
295     }
296
297     msg_Dbg( p_this, "Writing metadata for %s", p_export->psz_file );
298
299     TagLib::Tag *p_tag = f.tag();
300
301     char *psz_meta;
302
303 #define SET(a,b) \
304         if(b) { \
305             TagLib::String *psz_##a = new TagLib::String( b, \
306                 TagLib::String::UTF8 ); \
307             p_tag->set##a( *psz_##a ); \
308             delete psz_##a; \
309         }
310
311
312     psz_meta = input_item_GetArtist( p_item );
313     SET( Artist, psz_meta );
314     free( psz_meta );
315
316     psz_meta = input_item_GetTitle( p_item );
317     if( !psz_meta ) psz_meta = input_item_GetName( p_item );
318     TagLib::String *psz_title = new TagLib::String( psz_meta,
319         TagLib::String::UTF8 );
320     p_tag->setTitle( *psz_title );
321     delete psz_title;
322     free( psz_meta );
323
324     psz_meta = input_item_GetAlbum( p_item );
325     SET( Album, psz_meta );
326     free( psz_meta );
327
328     psz_meta = input_item_GetGenre( p_item );
329     SET( Genre, psz_meta );
330     free( psz_meta );
331
332 #undef SET
333
334     psz_meta = input_item_GetDate( p_item );
335     if( psz_meta ) p_tag->setYear( atoi( psz_meta ) );
336     free( psz_meta );
337
338     psz_meta = input_item_GetTrackNum( p_item );
339     if( psz_meta ) p_tag->setTrack( atoi( psz_meta ) );
340     free( psz_meta );
341
342     if( TagLib::ID3v2::Tag *p_id3tag =
343         dynamic_cast<TagLib::ID3v2::Tag *>(p_tag) )
344     {
345 #define WRITE( foo, bar ) \
346         psz_meta = input_item_Get##foo( p_item ); \
347         if( psz_meta ) \
348         { \
349             TagLib::ByteVector p_byte( bar, 4 ); \
350             TagLib::ID3v2::TextIdentificationFrame p_frame( p_byte ); \
351             p_frame.setText( psz_meta ); \
352             p_id3tag->addFrame( &p_frame ); \
353         } \
354         else free( psz_meta );
355
356         WRITE( Publisher, "TPUB" );
357         WRITE( Copyright, "TCOP" );
358         WRITE( EncodedBy, "TENC" );
359         WRITE( Language, "TLAN" );
360         
361 #undef WRITE
362     }
363
364     f.save();
365     return VLC_SUCCESS;
366 }
367
368 static int DownloadArt( vlc_object_t *p_this )
369 {
370     /* We need to be passed the file name
371      * Fetch the thing from the file, save it to the cache folder
372      */
373     return VLC_EGENERIC;
374 }
375