]> git.sesse.net Git - vlc/blob - src/input/meta.c
Moved input_item_t from input_source_t to input_thread_private_t.
[vlc] / src / input / meta.c
1 /*****************************************************************************
2  * meta.c : Metadata handling
3  *****************************************************************************
4  * Copyright (C) 1998-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea@videolan.org>
8  *          ClĂ©ment Stenac <zorglub@videolan.org
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 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_input.h>
31 #include <vlc_stream.h>
32 #include <vlc_meta.h>
33 #include <vlc_playlist.h>
34 #include <vlc_charset.h>
35 #include <vlc_strings.h>
36 #include "input_internal.h"
37 #include "../playlist/playlist_internal.h"
38 #include <errno.h>
39 #include <limits.h>                                             /* PATH_MAX */
40 #include <assert.h>
41
42 #ifdef HAVE_SYS_STAT_H
43 #   include <sys/stat.h>
44 #endif
45
46 #include "../libvlc.h"
47
48 const char *
49 input_MetaTypeToLocalizedString( vlc_meta_type_t meta_type )
50 {
51     switch( meta_type )
52     {
53     case vlc_meta_Title:        return _("Title");
54     case vlc_meta_Artist:       return _("Artist");
55     case vlc_meta_Genre:        return _("Genre");
56     case vlc_meta_Copyright:    return _("Copyright");
57     case vlc_meta_Album:        return _("Album");
58     case vlc_meta_TrackNumber:  return _("Track number");
59     case vlc_meta_Description:  return _("Description");
60     case vlc_meta_Rating:       return _("Rating");
61     case vlc_meta_Date:         return _("Date");
62     case vlc_meta_Setting:      return _("Setting");
63     case vlc_meta_URL:          return _("URL");
64     case vlc_meta_Language:     return _("Language");
65     case vlc_meta_NowPlaying:   return _("Now Playing");
66     case vlc_meta_Publisher:    return _("Publisher");
67     case vlc_meta_EncodedBy:    return _("Encoded by");
68     case vlc_meta_ArtworkURL:   return _("Artwork URL");
69     case vlc_meta_TrackID:      return _("Track ID");
70
71     default: abort();
72     }
73 };
74
75 #define input_FindArtInCache(a,b) __input_FindArtInCache(VLC_OBJECT(a),b)
76 static int __input_FindArtInCache( vlc_object_t *, input_item_t *p_item );
77
78 /* Return codes:
79  *   0 : Art is in cache or is a local file
80  *   1 : Art found, need to download
81  *  -X : Error/not found
82  */
83 int input_ArtFind( playlist_t *p_playlist, input_item_t *p_item )
84 {
85     int i_ret = VLC_EGENERIC;
86     module_t *p_module;
87     char *psz_title, *psz_artist, *psz_album;
88
89     psz_artist = input_item_GetArtist( p_item );
90     psz_album = input_item_GetAlbum( p_item );
91     psz_title = input_item_GetTitle( p_item );
92     if(!psz_title)
93         psz_title = input_item_GetName( p_item );
94
95     if( !psz_title && !psz_artist && !psz_album )
96         return VLC_EGENERIC;
97
98     free( psz_title );
99
100     /* If we already checked this album in this session, skip */
101     if( psz_artist && psz_album )
102     {
103         FOREACH_ARRAY( playlist_album_t album, pl_priv(p_playlist)->fetcher.albums )
104             if( !strcmp( album.psz_artist, psz_artist ) &&
105                 !strcmp( album.psz_album, psz_album ) )
106             {
107                 msg_Dbg( p_playlist, " %s - %s has already been searched",
108                          psz_artist, psz_album );
109         /* TODO-fenrir if we cache art filename too, we can go faster */
110                 free( psz_artist );
111                 free( psz_album );
112                 if( album.b_found )
113                 {
114                     if( !strncmp( album.psz_arturl, "file://", 7 ) )
115                         input_item_SetArtURL( p_item, album.psz_arturl );
116                     else /* Actually get URL from cache */
117                         input_FindArtInCache( p_playlist, p_item );
118                     return 0;
119                 }
120                 else
121                 {
122                     return VLC_EGENERIC;
123                 }
124             }
125         FOREACH_END();
126     }
127     free( psz_artist );
128     free( psz_album );
129
130     input_FindArtInCache( p_playlist, p_item );
131
132     char *psz_arturl = input_item_GetArtURL( p_item );
133     if( psz_arturl )
134     {
135         /* We already have an URL */
136         if( !strncmp( psz_arturl, "file://", strlen( "file://" ) ) )
137         {
138             free( psz_arturl );
139             return 0; /* Art is in cache, no need to go further */
140         }
141
142         free( psz_arturl );
143         
144         /* Art need to be put in cache */
145         return 1;
146     }
147
148     PL_LOCK;
149     p_playlist->p_private = p_item;
150     psz_album = input_item_GetAlbum( p_item );
151     psz_artist = input_item_GetArtist( p_item );
152     psz_title = input_item_GetTitle( p_item );
153     if( !psz_title )
154         psz_title = input_item_GetName( p_item );
155
156     if( psz_album && psz_artist )
157     {
158         msg_Dbg( p_playlist, "searching art for %s - %s",
159              psz_artist, psz_album );
160     }
161     else
162     {
163         msg_Dbg( p_playlist, "searching art for %s",
164              psz_title );
165     }
166     free( psz_title );
167
168     p_module = module_need( p_playlist, "art finder", 0, false );
169
170     if( p_module )
171         i_ret = 1;
172     else
173         msg_Dbg( p_playlist, "unable to find art" );
174
175     /* Record this album */
176     if( psz_artist && psz_album )
177     {
178         playlist_album_t a;
179         a.psz_artist = psz_artist;
180         a.psz_album = psz_album;
181         a.psz_arturl = input_item_GetArtURL( p_item );
182         a.b_found = (i_ret == VLC_EGENERIC ? false : true );
183         ARRAY_APPEND( pl_priv(p_playlist)->fetcher.albums, a );
184     }
185     else
186     {
187         free( psz_artist );
188         free( psz_album );
189     }
190
191     if( p_module )
192         module_unneed( p_playlist, p_module );
193     p_playlist->p_private = NULL;
194     PL_UNLOCK;
195
196     return i_ret;
197 }
198
199 static void ArtCacheCreateDir( const char *psz_dir )
200 {
201     char newdir[strlen( psz_dir ) + 1];
202     strcpy( newdir, psz_dir );
203     char * psz_newdir = newdir;
204     char * psz = psz_newdir;
205
206     while( *psz )
207     {
208         while( *psz && *psz != DIR_SEP_CHAR) psz++;
209         if( !*psz ) break;
210         *psz = 0;
211         if( !EMPTY_STR( psz_newdir ) )
212             utf8_mkdir( psz_newdir, 0700 );
213         *psz = DIR_SEP_CHAR;
214         psz++;
215     }
216     utf8_mkdir( psz_dir, 0700 );
217 }
218
219 static char * ArtCacheGetSanitizedFileName( const char *psz )
220 {
221     char *dup = strdup(psz);
222     int i;
223
224     filename_sanitize( dup );
225
226     /* Doesn't create a filename with invalid characters
227      * TODO: several filesystems forbid several characters: list them all
228      */
229     for( i = 0; dup[i] != '\0'; i++ )
230     {
231         if( dup[i] == DIR_SEP_CHAR )
232             dup[i] = ' ';
233     }
234     return dup;
235 }
236
237 #define ArtCacheGetDirPath(a,b,c,d,e) __ArtCacheGetDirPath(VLC_OBJECT(a),b,c,d,e)
238 static void __ArtCacheGetDirPath( vlc_object_t *p_obj,
239                                   char *psz_dir,
240                                   const char *psz_title,
241                                   const char *psz_artist, const char *psz_album )
242 {
243     (void)p_obj;
244     char *psz_cachedir = config_GetCacheDir();
245
246     if( !EMPTY_STR(psz_artist) && !EMPTY_STR(psz_album) )
247     {
248         char * psz_album_sanitized = ArtCacheGetSanitizedFileName( psz_album );
249         char * psz_artist_sanitized = ArtCacheGetSanitizedFileName( psz_artist );
250         snprintf( psz_dir, PATH_MAX, "%s" DIR_SEP
251                   "art" DIR_SEP "artistalbum" DIR_SEP "%s" DIR_SEP "%s",
252                   psz_cachedir, psz_artist_sanitized, psz_album_sanitized );
253         free( psz_album_sanitized );
254         free( psz_artist_sanitized );
255     }
256     else
257     {
258         char * psz_title_sanitized = ArtCacheGetSanitizedFileName( psz_title );
259         snprintf( psz_dir, PATH_MAX, "%s" DIR_SEP
260                   "art" DIR_SEP "title" DIR_SEP "%s",
261                   psz_cachedir, psz_title_sanitized );
262         free( psz_title_sanitized );
263     }
264     free( psz_cachedir );
265 }
266
267
268
269 #define ArtCacheGetFilePath(a,b,c,d,e,f) __ArtCacheGetFilePath(VLC_OBJECT(a),b,c,d,e,f)
270 static void __ArtCacheGetFilePath( vlc_object_t *p_obj,
271                                    char * psz_filename,
272                                    const char *psz_title,
273                                    const char *psz_artist, const char *psz_album,
274                                    const char *psz_extension )
275 {
276     char psz_dir[PATH_MAX+1];
277     char * psz_ext;
278     ArtCacheGetDirPath( p_obj, psz_dir, psz_title, psz_artist, psz_album );
279
280     if( psz_extension )
281     {
282         psz_ext = strndup( psz_extension, 6 );
283         filename_sanitize( psz_ext );
284     }
285     else psz_ext = strdup( "" );
286
287     snprintf( psz_filename, PATH_MAX, "file://%s" DIR_SEP "art%s",
288               psz_dir, psz_ext );
289
290     free( psz_ext );
291 }
292
293 static int __input_FindArtInCache( vlc_object_t *p_obj, input_item_t *p_item )
294 {
295     char *psz_artist;
296     char *psz_album;
297     char *psz_title;
298     char psz_dirpath[PATH_MAX+1];
299     char psz_filepath[PATH_MAX+1];
300     char * psz_filename;
301     DIR * p_dir;
302
303     psz_artist = input_item_GetArtist( p_item );
304     psz_album = input_item_GetAlbum( p_item );
305     psz_title = input_item_GetTitle( p_item );
306     if( !psz_title ) psz_title = input_item_GetName( p_item );
307
308     if( !psz_title && ( !psz_album || !psz_artist ) )
309     {
310         free( psz_artist );
311         free( psz_album );
312         free( psz_title );
313         return VLC_EGENERIC;
314     }
315
316     ArtCacheGetDirPath( p_obj, psz_dirpath, psz_title,
317                            psz_artist, psz_album );
318
319     free( psz_artist );
320     free( psz_album );
321     free( psz_title );
322
323     /* Check if file exists */
324     p_dir = utf8_opendir( psz_dirpath );
325     if( !p_dir )
326         return VLC_EGENERIC;
327
328     while( (psz_filename = utf8_readdir( p_dir )) )
329     {
330         if( !strncmp( psz_filename, "art", 3 ) )
331         {
332             snprintf( psz_filepath, PATH_MAX, "file://%s" DIR_SEP "%s",
333                       psz_dirpath, psz_filename );
334             input_item_SetArtURL( p_item, psz_filepath );
335             free( psz_filename );
336             closedir( p_dir );
337             return VLC_SUCCESS;
338         }
339         free( psz_filename );
340     }
341
342     /* Not found */
343     closedir( p_dir );
344     return VLC_EGENERIC;
345 }
346
347 /**
348  * Download the art using the URL or an art downloaded
349  * This function should be called only if data is not already in cache
350  */
351 int input_DownloadAndCacheArt( playlist_t *p_playlist, input_item_t *p_item )
352 {
353     int i_status = VLC_EGENERIC;
354     stream_t *p_stream;
355     char psz_filename[PATH_MAX+1];
356     char *psz_artist = NULL;
357     char *psz_album = NULL;
358     char *psz_title = NULL;
359     char *psz_arturl;
360     char *psz_type;
361
362     psz_artist = input_item_GetArtist( p_item );
363     psz_album = input_item_GetAlbum( p_item );
364     psz_title = input_item_GetTitle( p_item );
365     if( !psz_title )
366         psz_title = input_item_GetName( p_item );
367
368     if( !psz_title && (!psz_artist || !psz_album) )
369     {
370         free( psz_title );
371         free( psz_album );
372         free( psz_artist );
373         return VLC_EGENERIC;
374     }
375
376     psz_arturl = input_item_GetArtURL( p_item );
377     assert( !EMPTY_STR( psz_arturl ) );
378
379     if( !strncmp( psz_arturl , "file://", 7 ) )
380     {
381         msg_Dbg( p_playlist, "Album art is local file, no need to cache" );
382         free( psz_arturl );
383         return VLC_SUCCESS;
384     }
385     else if( !strncmp( psz_arturl , "APIC", 4 ) )
386     {
387         msg_Warn( p_playlist, "APIC fetch not supported yet" );
388         free( psz_arturl );
389         return VLC_EGENERIC;
390     }
391
392     psz_type = strrchr( psz_arturl, '.' );
393     if( psz_type && strlen( psz_type ) > 5 )
394         psz_type = NULL; /* remove extension if it's > to 4 characters */
395
396     /* Warning: psz_title, psz_artist, psz_album may change in ArtCache*() */
397
398     ArtCacheGetDirPath( p_playlist, psz_filename, psz_title, psz_artist,
399                         psz_album );
400     ArtCacheCreateDir( psz_filename );
401     ArtCacheGetFilePath( p_playlist, psz_filename, psz_title, psz_artist,
402                          psz_album, psz_type );
403
404     free( psz_artist );
405     free( psz_album );
406     free( psz_title );
407
408     p_stream = stream_UrlNew( p_playlist, psz_arturl );
409     if( p_stream )
410     {
411         uint8_t p_buffer[65536];
412         long int l_read;
413         FILE *p_file = utf8_fopen( psz_filename+7, "w" );
414         if( p_file == NULL ) {
415             msg_Err( p_playlist, "Unable write album art in %s",
416                      psz_filename + 7 );
417             free( psz_arturl );
418             return VLC_EGENERIC;
419         }
420         int err = 0;
421         while( ( l_read = stream_Read( p_stream, p_buffer, sizeof (p_buffer) ) ) )
422         {
423             if( fwrite( p_buffer, l_read, 1, p_file ) != 1 )
424             {
425                 err = errno;
426                 break;
427             }
428         }
429         if( fclose( p_file ) && !err )
430             err = errno;
431         stream_Delete( p_stream );
432
433         if( err )
434         {
435             errno = err;
436             msg_Err( p_playlist, "%s: %m", psz_filename );
437         }
438         else
439             msg_Dbg( p_playlist, "album art saved to %s\n", psz_filename );
440
441         input_item_SetArtURL( p_item, psz_filename );
442         i_status = VLC_SUCCESS;
443     }
444     free( psz_arturl );
445     return i_status;
446 }
447
448 void input_ExtractAttachmentAndCacheArt( input_thread_t *p_input )
449 {
450     input_item_t *p_item = p_input->p->p_item;
451     const char *psz_arturl;
452     const char *psz_artist = NULL;
453     const char *psz_album = NULL;
454     const char *psz_title = NULL;
455     char *psz_type = NULL;
456     char psz_filename[PATH_MAX+1];
457     FILE *f;
458     input_attachment_t *p_attachment;
459     struct stat s;
460     int i_idx;
461
462     /* TODO-fenrir merge input_ArtFind with download and make it set the flags FETCH
463      * and then set it here to to be faster */
464
465     psz_arturl = vlc_meta_Get( p_item->p_meta, vlc_meta_ArtworkURL );
466
467     if( !psz_arturl || strncmp( psz_arturl, "attachment://", strlen("attachment://") ) )
468     {
469         msg_Err( p_input, "internal input error with input_ExtractAttachmentAndCacheArt" );
470         return;
471     }
472
473     if( input_item_IsArtFetched( p_item ) )
474     {
475         /* XXX Weird, we should not have end up with attachment:// art url unless there is a race
476          * condition */
477         msg_Warn( p_input, "internal input error with input_ExtractAttachmentAndCacheArt" );
478         input_FindArtInCache( p_input, p_item );
479         return;
480     }
481
482     /* */
483     for( i_idx = 0, p_attachment = NULL; i_idx < p_input->p->i_attachment; i_idx++ )
484     {
485         if( !strcmp( p_input->p->attachment[i_idx]->psz_name,
486                      &psz_arturl[strlen("attachment://")] ) )
487         {
488             p_attachment = p_input->p->attachment[i_idx];
489             break;
490         }
491     }
492     if( !p_attachment || p_attachment->i_data <= 0 )
493     {
494         msg_Warn( p_input, "internal input error with input_ExtractAttachmentAndCacheArt" );
495         return;
496     }
497
498     psz_artist = vlc_meta_Get( p_item->p_meta, vlc_meta_Artist );
499     psz_album = vlc_meta_Get( p_item->p_meta, vlc_meta_Album );
500     psz_title = vlc_meta_Get( p_item->p_meta, vlc_meta_Title );
501     if( !strcmp( p_attachment->psz_mime, "image/jpeg" ) )
502         psz_type = strdup( ".jpg" );
503     else if( !strcmp( p_attachment->psz_mime, "image/png" ) )
504         psz_type = strdup( ".png" );
505
506     if( !psz_title )
507         psz_title = p_item->psz_name;
508
509     if( (!psz_artist || !psz_album ) && !psz_title )
510     {
511         free( psz_type );
512         return;
513     }
514
515     ArtCacheGetDirPath( p_input, psz_filename, psz_title, psz_artist, psz_album );
516     ArtCacheCreateDir( psz_filename );
517     ArtCacheGetFilePath( p_input, psz_filename, psz_title, psz_artist, psz_album, psz_type );
518     free( psz_type );
519
520     /* Check if we already dumped it */
521     if( !utf8_stat( psz_filename+7, &s ) )
522     {
523         vlc_meta_Set( p_item->p_meta, vlc_meta_ArtworkURL, psz_filename );
524         return;
525     }
526
527     f = utf8_fopen( psz_filename+7, "w" );
528     if( f )
529     {
530         if( fwrite( p_attachment->p_data, p_attachment->i_data, 1, f ) != 1 )
531             msg_Err( p_input, "%s: %m", psz_filename );
532         else
533         {
534             msg_Dbg( p_input, "album art saved to %s\n", psz_filename );
535             vlc_meta_Set( p_item->p_meta, vlc_meta_ArtworkURL, psz_filename );
536         }
537         fclose( f );
538     }
539 }