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