]> git.sesse.net Git - vlc/blob - modules/misc/lua/vlc.c
3613e11354aab3e46a55e2f93810c732f4e209c6
[vlc] / modules / misc / lua / vlc.c
1 /*****************************************************************************
2  * vlc.c: Generic lua interface functions
3  *****************************************************************************
4  * Copyright (C) 2007-2008 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 #ifdef HAVE_CONFIG_H
33 # include "config.h"
34 #endif
35
36 #include <assert.h>
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_meta.h>
41 #include <vlc_charset.h>
42 #include <vlc_aout.h>
43
44 #include <lua.h>        /* Low level lua C API */
45 #include <lauxlib.h>    /* Higher level C API */
46 #include <lualib.h>     /* Lua libs */
47
48 #include "vlc.h"
49
50 /*****************************************************************************
51  * Module descriptor
52  *****************************************************************************/
53
54 #define INTF_TEXT N_("Lua interface")
55 #define INTF_LONGTEXT N_("Lua interface module to load")
56
57 #define CONFIG_TEXT N_("Lua interface configuration")
58 #define CONFIG_LONGTEXT N_("Lua interface configuration string. Format is: '[\"<interface module name>\"] = { <option> = <value>, ...}, ...'.")
59
60 vlc_module_begin ()
61         set_shortname( N_( "Lua Art" ) )
62         set_description( N_("Fetch artwork using lua scripts") )
63         set_capability( "art finder", 10 )
64         set_callbacks( FindArt, NULL )
65
66     add_submodule ()
67         set_shortname( N_( "Lua Meta Reader" ) )
68         set_description( N_("Fetch meta data using lua scripts") )
69         set_capability( "meta reader", 10 )
70         set_callbacks( ReadMeta, NULL )
71
72     add_submodule ()
73         add_shortcut( "luaplaylist" )
74         set_category( CAT_INPUT )
75         set_subcategory( SUBCAT_INPUT_DEMUX )
76         set_shortname( N_("Lua Playlist") )
77         set_description( N_("Lua Playlist Parser Interface") )
78         set_capability( "demux", 2 )
79         set_callbacks( Import_LuaPlaylist, Close_LuaPlaylist )
80
81     add_submodule ()
82         set_description( N_("Lua Interface Module (shortcuts)") )
83         add_shortcut( "luarc" )
84         add_shortcut( "rc" )
85         set_capability( "interface", 25 )
86         set_callbacks( Open_LuaIntf, Close_LuaIntf )
87
88     add_submodule ()
89         set_description( N_("Lua Interface Module") )
90         add_shortcut( "luaintf" )
91         add_shortcut( "luahttp" )
92         add_shortcut( "http" )
93         add_shortcut( "luatelnet" )
94         add_shortcut( "telnet" )
95         add_shortcut( "luahotkeys" )
96         /* add_shortcut( "hotkeys" ) */
97         set_capability( "interface", 0 )
98         add_string( "lua-intf", "dummy", NULL,
99                     INTF_TEXT, INTF_LONGTEXT, false )
100         add_string( "lua-config", "", NULL,
101                     CONFIG_TEXT, CONFIG_LONGTEXT, false )
102         set_callbacks( Open_LuaIntf, Close_LuaIntf )
103 vlc_module_end ()
104
105 /*****************************************************************************
106  *
107  *****************************************************************************/
108 static int file_select( const char *file )
109 {
110     int i = strlen( file );
111     return i > 4 && !strcmp( file+i-4, ".lua" );
112 }
113
114 static int file_compare( const char **a, const char **b )
115 {
116     return strcmp( *a, *b );
117 }
118
119 int vlclua_dir_list( vlc_object_t *p_this, const char *luadirname, char **ppsz_dir_list )
120 {
121     int i = 0;
122     char *datadir = config_GetUserDir( VLC_DATA_DIR );
123     if( datadir == NULL )
124         return VLC_ENOMEM;
125
126     if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
127                    datadir, luadirname ) < 0 )
128     {
129         free( datadir );
130         return VLC_ENOMEM;
131     }
132     free( datadir );
133     i++;
134
135     char *psz_datapath = config_GetDataDir( p_this );
136 #   if defined(__APPLE__) || defined(SYS_BEOS) || defined(WIN32)
137     {
138         if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
139                       psz_datapath, luadirname )  < 0 )
140             return VLC_ENOMEM;
141         i++;
142         if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "share" DIR_SEP "lua" DIR_SEP "%s",
143                       psz_datapath, luadirname )  < 0 )
144             return VLC_ENOMEM;
145         i++;
146
147     }
148 #   else
149     if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
150                   psz_datapath, luadirname ) < 0 )
151         return VLC_ENOMEM;
152     i++;
153 #   endif
154     free( psz_datapath );
155     return VLC_SUCCESS;
156 }
157
158 void vlclua_dir_list_free( char **ppsz_dir_list )
159 {
160     char **ppsz_dir;
161     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
162         free( *ppsz_dir );
163 }
164
165 /*****************************************************************************
166  * Will execute func on all scripts in luadirname, and stop if func returns
167  * success.
168  *****************************************************************************/
169 int vlclua_scripts_batch_execute( vlc_object_t *p_this,
170                                   const char * luadirname,
171                                   int (*func)(vlc_object_t *, const char *, lua_State *, void *),
172                                   lua_State * L,
173                                   void * user_data)
174 {
175     int i_ret = VLC_EGENERIC;
176
177     char **ppsz_filelist = NULL;
178     char **ppsz_fileend  = NULL;
179     char **ppsz_file;
180
181     char  *ppsz_dir_list[] = { NULL, NULL, NULL, NULL };
182     char **ppsz_dir;
183
184     i_ret = vlclua_dir_list( p_this, luadirname, ppsz_dir_list );
185     if( i_ret != VLC_SUCCESS )
186         return i_ret;
187     i_ret = VLC_EGENERIC;
188
189
190     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
191     {
192         int i_files;
193
194         if( ppsz_filelist )
195         {
196             for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
197                  ppsz_file++ )
198                 free( *ppsz_file );
199             free( ppsz_filelist );
200             ppsz_filelist = NULL;
201         }
202
203         msg_Dbg( p_this, "Trying Lua scripts in %s", *ppsz_dir );
204         i_files = utf8_scandir( *ppsz_dir, &ppsz_filelist, file_select,
205                                 file_compare );
206         if( i_files < 1 ) continue;
207         ppsz_fileend = ppsz_filelist + i_files;
208
209         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend; ppsz_file++ )
210         {
211             char  *psz_filename;
212             if( asprintf( &psz_filename,
213                           "%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file ) < 0)
214             {
215                 vlclua_dir_list_free( ppsz_dir_list );
216                 return VLC_ENOMEM;
217             }
218             msg_Dbg( p_this, "Trying Lua playlist script %s", psz_filename );
219
220             i_ret = func( p_this, psz_filename, L, user_data );
221
222             free( psz_filename );
223
224             if( i_ret == VLC_SUCCESS ) break;
225         }
226         if( i_ret == VLC_SUCCESS ) break;
227     }
228
229     if( ppsz_filelist )
230     {
231         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
232              ppsz_file++ )
233             free( *ppsz_file );
234         free( ppsz_filelist );
235     }
236     vlclua_dir_list_free( ppsz_dir_list );
237
238     return i_ret;
239 }
240
241
242 /*****************************************************************************
243  * Meta data setters utility.
244  * Playlist item table should be on top of the stack when these are called
245  *****************************************************************************/
246 void __vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
247                               input_item_t *p_input )
248 {
249 #define TRY_META( a, b )                                        \
250     lua_getfield( L, -1, a );                                   \
251     if( lua_isstring( L, -1 ) )                                 \
252     {                                                           \
253         char *psz_value = strdup( lua_tostring( L, -1 ) );      \
254         EnsureUTF8( psz_value );                                \
255         msg_Dbg( p_this, #b ": %s", psz_value );                \
256         input_item_Set ## b ( p_input, psz_value );             \
257         free( psz_value );                                      \
258     }                                                           \
259     lua_pop( L, 1 ); /* pop a */
260     TRY_META( "title", Title );
261     TRY_META( "artist", Artist );
262     TRY_META( "genre", Genre );
263     TRY_META( "copyright", Copyright );
264     TRY_META( "album", Album );
265     TRY_META( "tracknum", TrackNum );
266     TRY_META( "description", Description );
267     TRY_META( "rating", Rating );
268     TRY_META( "date", Date );
269     TRY_META( "setting", Setting );
270     TRY_META( "url", URL );
271     TRY_META( "language", Language );
272     TRY_META( "nowplaying", NowPlaying );
273     TRY_META( "publisher", Publisher );
274     TRY_META( "encodedby", EncodedBy );
275     TRY_META( "arturl", ArtURL );
276     TRY_META( "trackid", TrackID );
277 }
278
279 void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
280                                      input_item_t *p_input )
281 {
282     /* ... item */
283     lua_getfield( L, -1, "meta" );
284     /* ... item meta */
285     if( lua_istable( L, -1 ) )
286     {
287         lua_pushnil( L );
288         /* ... item meta nil */
289         while( lua_next( L, -2 ) )
290         {
291             /* ... item meta key value */
292             if( !lua_isstring( L, -2 ) )
293             {
294                 msg_Warn( p_this, "Custom meta data category name must be "
295                                    "a string" );
296             }
297             else if( !lua_istable( L, -1 ) )
298             {
299                 msg_Warn( p_this, "Custom meta data category contents "
300                                    "must be a table" );
301             }
302             else
303             {
304                 const char *psz_meta_category = lua_tostring( L, -2 );
305                 msg_Dbg( p_this, "Found custom meta data category: %s",
306                          psz_meta_category );
307                 lua_pushnil( L );
308                 /* ... item meta key value nil */
309                 while( lua_next( L, -2 ) )
310                 {
311                     /* ... item meta key value key2 value2 */
312                     if( !lua_isstring( L, -2 ) )
313                     {
314                         msg_Warn( p_this, "Custom meta category item name "
315                                            "must be a string." );
316                     }
317                     else if( !lua_isstring( L, -1 ) )
318                     {
319                         msg_Warn( p_this, "Custom meta category item value "
320                                            "must be a string." );
321                     }
322                     else
323                     {
324                         const char *psz_meta_name =
325                             lua_tostring( L, -2 );
326                         const char *psz_meta_value =
327                             lua_tostring( L, -1 );
328                         msg_Dbg( p_this, "Custom meta %s, %s: %s",
329                                  psz_meta_category, psz_meta_name,
330                                  psz_meta_value );
331                         input_item_AddInfo( p_input, psz_meta_category,
332                                            psz_meta_name, "%s", psz_meta_value );
333                     }
334                     lua_pop( L, 1 ); /* pop item */
335                     /* ... item meta key value key2 */
336                 }
337                 /* ... item meta key value */
338             }
339             lua_pop( L, 1 ); /* pop category */
340             /* ... item meta key */
341         }
342         /* ... item meta */
343     }
344     lua_pop( L, 1 ); /* pop "meta" */
345     /* ... item -> back to original stack */
346 }
347
348 /*****************************************************************************
349  * Playlist utilities
350  ****************************************************************************/
351 /**
352  * Playlist item table should be on top of the stack when this is called
353  */
354 void __vlclua_read_options( vlc_object_t *p_this, lua_State *L,
355                             int *pi_options, char ***pppsz_options )
356 {
357     lua_getfield( L, -1, "options" );
358     if( lua_istable( L, -1 ) )
359     {
360         lua_pushnil( L );
361         while( lua_next( L, -2 ) )
362         {
363             if( lua_isstring( L, -1 ) )
364             {
365                 char *psz_option = strdup( lua_tostring( L, -1 ) );
366                 msg_Dbg( p_this, "Option: %s", psz_option );
367                 INSERT_ELEM( *pppsz_options, *pi_options, *pi_options,
368                              psz_option );
369             }
370             else
371             {
372                 msg_Warn( p_this, "Option should be a string" );
373             }
374             lua_pop( L, 1 ); /* pop option */
375         }
376     }
377     lua_pop( L, 1 ); /* pop "options" */
378 }
379
380 int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
381                                     playlist_t *p_playlist,
382                                     input_item_t *p_parent, bool b_play )
383 {
384     int i_count = 0;
385
386     assert( p_parent || p_playlist );
387
388     /* playlist */
389     if( lua_istable( L, -1 ) )
390     {
391         lua_pushnil( L );
392         /* playlist nil */
393         while( lua_next( L, -2 ) )
394         {
395             /* playlist key item */
396             /* <Parse playlist item> */
397             if( lua_istable( L, -1 ) )
398             {
399                 lua_getfield( L, -1, "path" );
400                 /* playlist key item path */
401                 if( lua_isstring( L, -1 ) )
402                 {
403                     const char   *psz_path     = NULL;
404                     const char   *psz_name     = NULL;
405                     char        **ppsz_options = NULL;
406                     int           i_options    = 0;
407                     mtime_t       i_duration   = -1;
408                     input_item_t *p_input;
409
410                     /* Read path and name */
411                     psz_path = lua_tostring( L, -1 );
412                     msg_Dbg( p_this, "Path: %s", psz_path );
413                     lua_getfield( L, -2, "name" );
414                     /* playlist key item path name */
415                     if( lua_isstring( L, -1 ) )
416                     {
417                         psz_name = lua_tostring( L, -1 );
418                         msg_Dbg( p_this, "Name: %s", psz_name );
419                     }
420                     else
421                     {
422                         if( !lua_isnil( L, -1 ) )
423                             msg_Warn( p_this, "Playlist item name should be a string." );
424                         psz_name = psz_path;
425                     }
426
427                     /* Read duration */
428                     lua_getfield( L, -3, "duration" );
429                     /* playlist key item path name duration */
430                     if( lua_isnumber( L, -1 ) )
431                     {
432                         i_duration = (mtime_t)(lua_tonumber( L, -1 )*1e6);
433                     }
434                     else if( !lua_isnil( L, -1 ) )
435                     {
436                         msg_Warn( p_this, "Playlist item duration should be a number (in seconds)." );
437                     }
438                     lua_pop( L, 1 ); /* pop "duration" */
439
440                     /* playlist key item path name */
441
442                     /* Read options: item must be on top of stack */
443                     lua_pushvalue( L, -3 );
444                     /* playlist key item path name item */
445                     vlclua_read_options( p_this, L, &i_options, &ppsz_options );
446
447                     /* Create input item */
448                     p_input = input_item_NewExt( p_playlist, psz_path,
449                                                 psz_name, i_options,
450                                                 (const char **)ppsz_options,
451                                                 VLC_INPUT_OPTION_TRUSTED,
452                                                 i_duration );
453                     lua_pop( L, 3 ); /* pop "path name item" */
454                     /* playlist key item */
455
456                     /* Read meta data: item must be on top of stack */
457                     vlclua_read_meta_data( p_this, L, p_input );
458
459                     /* Read custom meta data: item must be on top of stack*/
460                     vlclua_read_custom_meta_data( p_this, L, p_input );
461
462                     /* Append item to playlist */
463                     if( p_parent ) /* Add to node */
464                         input_item_AddSubItem( p_parent, p_input );
465                     else /* Play or Enqueue (preparse) */
466                         /* FIXME: playlist_AddInput() can fail */
467                         playlist_AddInput( p_playlist, p_input,
468                                PLAYLIST_APPEND | 
469                                ( b_play ? PLAYLIST_GO : PLAYLIST_PREPARSE ),
470                                PLAYLIST_END, true, false );
471                     i_count ++; /* increment counter */
472                     vlc_gc_decref( p_input );
473                     while( i_options > 0 )
474                         free( ppsz_options[--i_options] );
475                     free( ppsz_options );
476                 }
477                 else
478                 {
479                     lua_pop( L, 1 ); /* pop "path" */
480                     msg_Warn( p_this,
481                              "Playlist item's path should be a string" );
482                 }
483                 /* playlist key item */
484             }
485             else
486             {
487                 msg_Warn( p_this, "Playlist item should be a table" );
488             }
489             /* <Parse playlist item> */
490             lua_pop( L, 1 ); /* pop the value, keep the key for
491                               * the next lua_next() call */
492             /* playlist key */
493         }
494         /* playlist */
495     }
496     else
497     {
498         msg_Warn( p_this, "Playlist should be a table." );
499     }
500     return i_count;
501 }