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