]> git.sesse.net Git - vlc/blob - modules/meta_engine/luameta.c
9f89a05cad960a4da56154f49263602be49f61fc
[vlc] / modules / meta_engine / luameta.c
1 /*****************************************************************************
2  * luameta.c: Get meta/artwork using lua scripts
3  *****************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea at videolan tod org>
8  *          Pierre d'Herbemont <pdherbemont # 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 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #ifndef  _GNU_SOURCE
29 #   define  _GNU_SOURCE
30 #endif
31
32 #include <vlc/vlc.h>
33 #include <vlc_input.h>
34 #include <vlc_playlist.h>
35 #include <vlc_meta.h>
36 #include <vlc_url.h>
37 #include <vlc_strings.h>
38 #include <vlc_stream.h>
39 #include <vlc_charset.h>
40
41 #ifdef HAVE_SYS_STAT_H
42 #   include <sys/stat.h>
43 #endif
44
45 #include <lua.h>        /* Low level lua C API */
46 #include <lauxlib.h>    /* Higher level C API */
47 #include <lualib.h>     /* Lua libs */
48
49 /*****************************************************************************
50  * Local prototypes
51  *****************************************************************************/
52 static int FindArt( vlc_object_t * );
53 static int FindMeta( vlc_object_t *p_this );
54
55 /*****************************************************************************
56  * Module descriptor
57  *****************************************************************************/
58
59 vlc_module_begin();
60     set_shortname( N_( "Lua Meta" ) );
61     set_description( _("Fetch Artwork using lua scripts") );
62     set_capability( "meta fetcher", 10 );
63     set_callbacks( FindMeta, NULL );
64     add_submodule();
65         set_capability( "art finder", 10 );
66         set_callbacks( FindArt, NULL );
67 vlc_module_end();
68
69 /*****************************************************************************
70  * Lua function bridge
71  *****************************************************************************/
72 static vlc_object_t * vlclua_get_this( lua_State *p_state )
73 {
74     vlc_object_t * p_this;
75     lua_getglobal( p_state, "vlc" );
76     lua_getfield( p_state, lua_gettop( p_state ), "private" );
77     p_this = (vlc_object_t*)lua_topointer( p_state, lua_gettop( p_state ) );
78     lua_pop( p_state, 2 );
79     return p_this;
80 }
81
82 static int vlclua_stream_new( lua_State *p_state )
83 {
84     vlc_object_t * p_this = vlclua_get_this( p_state );
85     int i = lua_gettop( p_state );
86     stream_t * p_stream;
87     const char * psz_url;
88     if( !i ) return 0;
89     psz_url = lua_tostring( p_state, 1 );
90     lua_pop( p_state, i );
91     p_stream = stream_UrlNew( p_this, psz_url );
92     lua_pushlightuserdata( p_state, p_stream );
93     return 1;
94 }
95
96 static int vlclua_stream_read( lua_State *p_state )
97 {
98     int i = lua_gettop( p_state );
99     stream_t * p_stream;
100     int n;
101     byte_t *p_read;
102     int i_read;
103     if( !i ) return 0;
104     p_stream = (stream_t *)lua_topointer( p_state, 1 );
105     n = lua_tonumber( p_state, 2 );
106     lua_pop( p_state, i );
107     p_read = malloc( n );
108     if( !p_read ) return 0;
109     i_read = stream_Read( p_stream, p_read, n );
110     lua_pushlstring( p_state, (const char *)p_read, i_read );
111     free( p_read );
112     return 1;
113 }
114
115 static int vlclua_stream_readline( lua_State *p_state )
116 {
117     int i = lua_gettop( p_state );
118     stream_t * p_stream;
119     if( !i ) return 0;
120     p_stream = (stream_t *)lua_topointer( p_state, 1 );
121     lua_pop( p_state, i );
122     char *psz_line = stream_ReadLine( p_stream );
123     if( psz_line )
124     {
125         lua_pushstring( p_state, psz_line );
126         free( psz_line );
127     }
128     else
129     {
130         lua_pushnil( p_state );
131     }
132     return 1;
133 }
134
135 static int vlclua_stream_delete( lua_State *p_state )
136 {
137     int i = lua_gettop( p_state );
138     stream_t * p_stream;
139     if( !i ) return 0;
140     p_stream = (stream_t *)lua_topointer( p_state, 1 );
141     lua_pop( p_state, i );
142     stream_Delete( p_stream );
143     return 1;
144 }
145
146 static int vlclua_decode_uri( lua_State *p_state )
147 {
148     int i = lua_gettop( p_state );
149     if( !i ) return 0;
150     const char *psz_cstring = lua_tostring( p_state, 1 );
151     if( !psz_cstring ) return 0;
152     char *psz_string = strdup( psz_cstring );
153     lua_pop( p_state, i );
154     decode_URI( psz_string );
155     lua_pushstring( p_state, psz_string );
156     free( psz_string );
157     return 1;
158 }
159
160 static int vlclua_resolve_xml_special_chars( lua_State *p_state )
161 {
162     int i = lua_gettop( p_state );
163     if( !i ) return 0;
164     const char *psz_cstring = lua_tostring( p_state, 1 );
165     if( !psz_cstring ) return 0;
166     char *psz_string = strdup( psz_cstring );
167     lua_pop( p_state, i );
168     resolve_xml_special_chars( psz_string );
169     lua_pushstring( p_state, psz_string );
170     free( psz_string );
171     return 1;
172 }
173
174 static int vlclua_msg_dbg( lua_State *p_state )
175 {
176     vlc_object_t *p_this = vlclua_get_this( p_state );
177     int i = lua_gettop( p_state );
178     if( !i ) return 0;
179     const char *psz_cstring = lua_tostring( p_state, 1 );
180     if( !psz_cstring ) return 0;
181     msg_Dbg( p_this, "%s", psz_cstring );
182     return 0;
183 }
184 static int vlclua_msg_warn( lua_State *p_state )
185 {
186     vlc_object_t *p_this = vlclua_get_this( p_state );
187     int i = lua_gettop( p_state );
188     if( !i ) return 0;
189     const char *psz_cstring = lua_tostring( p_state, 1 );
190     if( !psz_cstring ) return 0;
191     msg_Warn( p_this, "%s", psz_cstring );
192     return 0;
193 }
194 static int vlclua_msg_err( lua_State *p_state )
195 {
196     vlc_object_t *p_this = vlclua_get_this( p_state );
197     int i = lua_gettop( p_state );
198     if( !i ) return 0;
199     const char *psz_cstring = lua_tostring( p_state, 1 );
200     if( !psz_cstring ) return 0;
201     msg_Err( p_this, "%s", psz_cstring );
202     return 0;
203 }
204 static int vlclua_msg_info( lua_State *p_state )
205 {
206     vlc_object_t *p_this = vlclua_get_this( p_state );
207     int i = lua_gettop( p_state );
208     if( !i ) return 0;
209     const char *psz_cstring = lua_tostring( p_state, 1 );
210     if( !psz_cstring ) return 0;
211     msg_Info( p_this, "%s", psz_cstring );
212     return 0;
213 }
214
215 /* Functions to register */
216 static luaL_Reg p_reg[] =
217 {
218     { "stream_new", vlclua_stream_new },
219     { "stream_read", vlclua_stream_read },
220     { "stream_readline", vlclua_stream_readline },
221     { "stream_delete", vlclua_stream_delete },
222     { "decode_uri", vlclua_decode_uri },
223     { "resolve_xml_special_chars", vlclua_resolve_xml_special_chars },
224     { "msg_dbg", vlclua_msg_dbg },
225     { "msg_warn", vlclua_msg_warn },
226     { "msg_err", vlclua_msg_err },
227     { "msg_info", vlclua_msg_info },
228     { NULL, NULL }
229 };
230 /*****************************************************************************
231  *
232  *****************************************************************************/
233 static int file_select( const char *file )
234 {
235     int i = strlen( file );
236     return i > 4 && !strcmp( file+i-4, ".lua" );
237 }
238
239 static int file_compare( const char **a, const char **b )
240 {
241     return strcmp( *a, *b );
242 }
243
244 /*****************************************************************************
245  * Init lua
246  *****************************************************************************/
247 static lua_State * vlclua_meta_init( vlc_object_t *p_this, input_item_t * p_item )
248 {
249     lua_State * p_state = luaL_newstate();
250     if( !p_state )
251     {
252         msg_Err( p_this, "Could not create new Lua State" );
253         return NULL;
254     }
255     char *psz_meta;
256
257     /* Load Lua libraries */
258     luaL_openlibs( p_state ); /* XXX: Don't open all the libs? */
259     
260     luaL_register( p_state, "vlc", p_reg );
261     
262     lua_pushlightuserdata( p_state, p_this );
263     lua_setfield( p_state, lua_gettop( p_state ) - 1, "private" );
264    
265     psz_meta = input_item_GetName( p_item );
266     lua_pushstring( p_state, psz_meta );
267     lua_setfield( p_state, lua_gettop( p_state ) - 1, "name" );
268     free( psz_meta );
269    
270     psz_meta = input_item_GetArtist( p_item );
271     lua_pushstring( p_state, psz_meta );
272     lua_setfield( p_state, lua_gettop( p_state ) - 1, "artist" );
273     free( psz_meta );
274    
275     psz_meta = input_item_GetTitle( p_item ) ;
276     lua_pushstring( p_state, psz_meta );
277     lua_setfield( p_state, lua_gettop( p_state ) - 1, "title" );
278     free( psz_meta );
279    
280     psz_meta = input_item_GetAlbum( p_item );
281     lua_pushstring( p_state, psz_meta );
282     lua_setfield( p_state, lua_gettop( p_state ) - 1, "album" );
283     free( psz_meta );
284
285     psz_meta = input_item_GetArtURL( p_item );
286     lua_pushstring( p_state, psz_meta );
287     lua_setfield( p_state, lua_gettop( p_state ) - 1, "arturl" );
288     free( psz_meta );
289     /* XXX: all should be passed ( could use macro ) */
290
291     return p_state;
292 }
293
294 /*****************************************************************************
295  * Will execute func on all scripts in luadirname, and stop if func returns
296  * success.
297  *****************************************************************************/
298 static int vlclua_scripts_batch_execute( vlc_object_t *p_this,
299                                          const char * luadirname,
300                                          int (*func)(vlc_object_t *, const char *, lua_State *, void *),
301                                          lua_State * p_state,
302                                          void * user_data)
303 {
304     int i_ret = VLC_EGENERIC;
305
306     DIR   *dir           = NULL;
307     char **ppsz_filelist = NULL;
308     char **ppsz_fileend  = NULL;
309     char **ppsz_file;
310
311     char  *ppsz_dir_list[] = { NULL, NULL, NULL, NULL };
312     char **ppsz_dir;
313
314     if( asprintf( &ppsz_dir_list[0], "%s" DIR_SEP CONFIG_DIR DIR_SEP "%s", p_this->p_libvlc->psz_homedir,
315             luadirname ) < 0 )
316         return VLC_ENOMEM;
317
318 #   if defined(__APPLE__) || defined(SYS_BEOS) || defined(WIN32)
319     {
320         const char *psz_vlcpath = config_GetDataDir();
321         if( asprintf( &ppsz_dir_list[1], "%s" DIR_SEP "%s", psz_vlcpath, luadirname )  < 0 )
322             return VLC_ENOMEM;
323
324         if( asprintf( &ppsz_dir_list[2], "%s" DIR_SEP "share" DIR_SEP "%s", psz_vlcpath, luadirname )  < 0 )
325             return VLC_ENOMEM;
326     }
327 #   else
328     {
329 #   ifdef HAVE_SYS_STAT_H
330         struct stat stat_info;
331         if( ( utf8_stat( "share/luaplaylist", &stat_info ) == -1 )
332             || !S_ISDIR( stat_info.st_mode ) )
333         {
334             if( asprintf( &ppsz_dir_list[1], 
335                           DATA_PATH DIR_SEP "%s", luadirname ) < 0 )
336                 return VLC_ENOMEM;
337         }
338         else
339 #   endif
340         {
341             if( asprintf( &ppsz_dir_list[1], 
342                           "share" DIR_SEP "%s", luadirname ) < 0 )
343                 return VLC_ENOMEM;
344         }
345     }
346 #   endif
347
348     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
349     {
350         int i_files;
351
352         if( ppsz_filelist )
353         {
354             for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
355                  ppsz_file++ )
356                 free( *ppsz_file );
357             free( ppsz_filelist );
358             ppsz_filelist = NULL;
359         }
360
361         if( dir )
362         {
363             closedir( dir );
364         }
365
366         msg_Dbg( p_this, "Trying Lua scripts in %s", *ppsz_dir );
367         dir = utf8_opendir( *ppsz_dir );
368
369         if( !dir ) continue;
370         i_files = utf8_loaddir( dir, &ppsz_filelist, file_select, file_compare );
371         if( i_files < 1 ) continue;
372         ppsz_fileend = ppsz_filelist + i_files;
373
374         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend; ppsz_file++ )
375         {
376             char  *psz_filename;
377             asprintf( &psz_filename, "%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file );
378             msg_Dbg( p_this, "Trying Lua playlist script %s", psz_filename );
379             
380             i_ret = func( p_this, psz_filename, p_state, user_data );
381             
382             free( psz_filename );
383
384             if( i_ret == VLC_SUCCESS ) break;
385         }
386         if( i_ret == VLC_SUCCESS ) break;
387     }
388
389     if( ppsz_filelist )
390     {
391         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
392              ppsz_file++ )
393             free( *ppsz_file );
394         free( ppsz_filelist );
395     }
396     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
397         free( *ppsz_dir );
398
399     if( dir ) closedir( dir );
400
401     return i_ret;
402 }
403
404 /*****************************************************************************
405  * Meta data setters utility.
406  *****************************************************************************/
407 static inline void read_meta_data( vlc_object_t *p_this,
408                                    lua_State *p_state, int o, int t,
409                                    input_item_t *p_input )
410 {
411     const char *psz_value;
412 #define TRY_META( a, b )                                    \
413     lua_getfield( p_state, o, a );                          \
414     if( lua_isstring( p_state, t ) )                        \
415     {                                                       \
416         psz_value = lua_tostring( p_state, t );             \
417         msg_Dbg( p_this, #b ": %s", psz_value );           \
418         input_item_Set ## b ( p_input, psz_value );   \
419     }                                                       \
420     lua_pop( p_state, 1 ); /* pop a */
421     TRY_META( "title", Title );
422     TRY_META( "artist", Artist );
423     TRY_META( "genre", Genre );
424     TRY_META( "copyright", Copyright );
425     TRY_META( "album", Album );
426     TRY_META( "tracknum", TrackNum );
427     TRY_META( "description", Description );
428     TRY_META( "rating", Rating );
429     TRY_META( "date", Date );
430     TRY_META( "setting", Setting );
431     TRY_META( "url", URL );
432     TRY_META( "language", Language );
433     TRY_META( "nowplaying", NowPlaying );
434     TRY_META( "publisher", Publisher );
435     TRY_META( "encodedby", EncodedBy );
436     TRY_META( "arturl", ArtURL );
437     TRY_META( "trackid", TrackID );
438 }
439
440 static inline void read_custom_meta_data( vlc_object_t *p_this,
441                                           lua_State *p_state, int o, int t,
442                                           input_item_t *p_input )
443 {
444     lua_getfield( p_state, o, "meta" );
445     if( lua_istable( p_state, t ) )
446     {
447         lua_pushnil( p_state );
448         while( lua_next( p_state, t ) )
449         {
450             if( !lua_isstring( p_state, t+1 ) )
451             {
452                 msg_Warn( p_this, "Custom meta data category name must be "
453                                    "a string" );
454             }
455             else if( !lua_istable( p_state, t+2 ) )
456             {
457                 msg_Warn( p_this, "Custom meta data category contents "
458                                    "must be a table" );
459             }
460             else
461             {
462                 const char *psz_meta_category = lua_tostring( p_state, t+1 );
463                 msg_Dbg( p_this, "Found custom meta data category: %s",
464                          psz_meta_category );
465                 lua_pushnil( p_state );
466                 while( lua_next( p_state, t+2 ) )
467                 {
468                     if( !lua_isstring( p_state, t+3 ) )
469                     {
470                         msg_Warn( p_this, "Custom meta category item name "
471                                            "must be a string." );
472                     }
473                     else if( !lua_isstring( p_state, t+4 ) )
474                     {
475                         msg_Warn( p_this, "Custom meta category item value "
476                                            "must be a string." );
477                     }
478                     else
479                     {
480                         const char *psz_meta_name =
481                             lua_tostring( p_state, t+3 );
482                         const char *psz_meta_value =
483                             lua_tostring( p_state, t+4 );
484                         msg_Dbg( p_this, "Custom meta %s, %s: %s",
485                                  psz_meta_category, psz_meta_name,
486                                  psz_meta_value );
487                         input_ItemAddInfo( p_input, psz_meta_category,
488                                            psz_meta_name, psz_meta_value );
489                     }
490                     lua_pop( p_state, 1 ); /* pop item */
491                 }
492             }
493             lua_pop( p_state, 1 ); /* pop category */
494         }
495     }
496     lua_pop( p_state, 1 ); /* pop "meta" */
497 }
498
499 /*****************************************************************************
500  * Called through lua_scripts_batch_execute to call 'fetch_art' on the script
501  * pointed by psz_filename.
502  *****************************************************************************/
503 static int fetch_art( vlc_object_t *p_this, const char * psz_filename,
504                       lua_State * p_state, void * user_data )
505 {
506     int i_ret = VLC_EGENERIC;
507     input_item_t * p_input = user_data;
508     int s;
509
510     /* Ugly hack to delete previous versions of the fetchart()
511     * functions. */
512     lua_pushnil( p_state );
513     lua_setglobal( p_state, "fetch_art" );
514     
515     /* Load and run the script(s) */
516     if( luaL_dofile( p_state, psz_filename ) )
517     {
518         msg_Warn( p_this, "Error loading script %s: %s", psz_filename,
519                   lua_tostring( p_state, lua_gettop( p_state ) ) );
520         lua_pop( p_state, 1 );
521         return VLC_EGENERIC;
522     }
523
524     lua_getglobal( p_state, "fetch_art" );
525
526     if( !lua_isfunction( p_state, lua_gettop( p_state ) ) )
527     {
528         msg_Warn( p_this, "Error while runing script %s, "
529                   "function fetch_art() not found", psz_filename );
530         lua_pop( p_state, 1 );
531         return VLC_EGENERIC;
532     }
533
534     if( lua_pcall( p_state, 0, 1, 0 ) )
535     {
536         msg_Warn( p_this, "Error while runing script %s, "
537                   "function fetch_art(): %s", psz_filename,
538                   lua_tostring( p_state, lua_gettop( p_state ) ) );
539         lua_pop( p_state, 1 );
540         return VLC_EGENERIC;
541     }
542
543     if((s = lua_gettop( p_state )))
544     {
545         const char * psz_value;
546
547         if( lua_isstring( p_state, s ) )
548         {
549             psz_value = lua_tostring( p_state, s );
550             if( psz_value && *psz_value != 0 )
551             {
552                 msg_Dbg( p_this, "setting arturl: %s", psz_value );
553                 input_item_SetArtURL ( p_input, psz_value );
554                 i_ret = VLC_SUCCESS;
555             }
556         }
557         else
558         {
559             msg_Err( p_this, "Lua playlist script %s: "
560                  "didn't return a string", psz_filename );
561         }
562     }
563     else
564     {
565         msg_Err( p_this, "Script went completely foobar" );
566     }
567
568     return i_ret;
569 }
570
571 /*****************************************************************************
572  * Called through lua_scripts_batch_execute to call 'fetch_meta' on the script
573  * pointed by psz_filename.
574  *****************************************************************************/
575 static int fetch_meta( vlc_object_t *p_this, const char * psz_filename,
576                        lua_State * p_state, void * user_data )
577 {
578     input_item_t * p_input = user_data;
579     int t;
580
581     /* Ugly hack to delete previous versions of the fetchmeta()
582     * functions. */
583     lua_pushnil( p_state );
584     lua_setglobal( p_state, "fetch_meta" );
585     
586     /* Load and run the script(s) */
587     if( luaL_dofile( p_state, psz_filename ) )
588     {
589         msg_Warn( p_this, "Error loading script %s: %s", psz_filename,
590                   lua_tostring( p_state, lua_gettop( p_state ) ) );
591         lua_pop( p_state, 1 );
592         return VLC_EGENERIC;
593     }
594     
595     lua_getglobal( p_state, "fetch_meta" );
596     
597     if( !lua_isfunction( p_state, lua_gettop( p_state ) ) )
598     {
599         msg_Warn( p_this, "Error while runing script %s, "
600                   "function fetch_meta() not found", psz_filename );
601         lua_pop( p_state, 1 );
602         return VLC_EGENERIC;
603     }
604     
605     if( lua_pcall( p_state, 0, 1, 0 ) )
606     {
607         msg_Warn( p_this, "Error while runing script %s, "
608                   "function fetch_meta(): %s", psz_filename,
609                   lua_tostring( p_state, lua_gettop( p_state ) ) );
610         lua_pop( p_state, 1 );
611         return VLC_EGENERIC;
612     }
613     
614
615     if((t = lua_gettop( p_state )))
616     {
617         if( lua_istable( p_state, t ) )
618         {
619             read_meta_data( p_this, p_state, t, t+1, p_input );
620             read_custom_meta_data( p_this, p_state, t, t+1, p_input );
621         }
622         else
623         {
624             msg_Err( p_this, "Lua playlist script %s: "
625                  "didn't return a table", psz_filename );
626         }
627     }
628     else
629     {
630         msg_Err( p_this, "Script went completely foobar" );
631     }
632
633     /* We tell the batch thing to continue, hence all script
634      * will get the change to add its meta. This behaviour could
635      * be changed. */
636     return VLC_EGENERIC;
637 }
638
639 /*****************************************************************************
640  * Module entry point for meta.
641  *****************************************************************************/
642 static int FindMeta( vlc_object_t *p_this )
643 {
644     meta_engine_t *p_me = (meta_engine_t *)p_this;
645     input_item_t *p_item = p_me->p_item;
646     lua_State *p_state = vlclua_meta_init( p_this, p_item );
647     
648     int i_ret = vlclua_scripts_batch_execute( p_this, "luameta", &fetch_meta, p_state, p_item );
649     lua_close( p_state );
650     return i_ret;
651 }
652
653 /*****************************************************************************
654  * Module entry point for art.
655  *****************************************************************************/
656 static int FindArt( vlc_object_t *p_this )
657 {
658     playlist_t *p_playlist = (playlist_t *)p_this;
659     input_item_t *p_item = (input_item_t *)(p_playlist->p_private);
660     lua_State *p_state = vlclua_meta_init( p_this, p_item );
661     
662     int i_ret = vlclua_scripts_batch_execute( p_this, "luameta", &fetch_art, p_state, p_item );
663     lua_close( p_state );
664     return i_ret;
665 }
666