]> git.sesse.net Git - vlc/blob - modules/misc/lua/vlc.c
e44e60d900aad42f8b533f8a6cd8cfa5acb811d4
[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_fs.h>
43 #include <vlc_aout.h>
44 #include <vlc_services_discovery.h>
45 #include <sys/stat.h>
46
47 #include <lua.h>        /* Low level lua C API */
48 #include <lauxlib.h>    /* Higher level C API */
49 #include <lualib.h>     /* Lua libs */
50
51 #include "vlc.h"
52
53 /*****************************************************************************
54  * Module descriptor
55  *****************************************************************************/
56 #define INTF_TEXT N_("Lua interface")
57 #define INTF_LONGTEXT N_("Lua interface module to load")
58
59 #define CONFIG_TEXT N_("Lua interface configuration")
60 #define CONFIG_LONGTEXT N_("Lua interface configuration string. Format is: '[\"<interface module name>\"] = { <option> = <value>, ...}, ...'.")
61 #define HOST_TEXT N_( "Host address" )
62 #define HOST_LONGTEXT N_( \
63     "Address and port the HTTP interface will listen on. It defaults to " \
64     "all network interfaces (0.0.0.0)." \
65     " If you want the HTTP interface to be available only on the local " \
66     "machine, enter 127.0.0.1" )
67 #define SRC_TEXT N_( "Source directory" )
68 #define SRC_LONGTEXT N_( "Source directory" )
69 #define INDEX_TEXT N_( "Directory index" )
70 #define INDEX_LONGTEXT N_( "Allow to build directory index" )
71
72 static int vlc_sd_probe_Open( vlc_object_t * );
73
74 vlc_module_begin ()
75         set_shortname( N_("Lua Interface Module") )
76         set_description( N_("Interfaces implemented using lua scripts") )
77         add_shortcut( "luaintf" )
78         add_shortcut( "luahttp" )
79         /* add_shortcut( "http" ) */
80         add_shortcut( "luatelnet" )
81         add_shortcut( "telnet" )
82         add_shortcut( "luahotkeys" )
83         /* add_shortcut( "hotkeys" ) */
84         set_capability( "interface", 0 )
85         set_category( CAT_INTERFACE )
86         set_subcategory( SUBCAT_INTERFACE_CONTROL )
87         add_string( "lua-intf", "dummy", NULL,
88                     INTF_TEXT, INTF_LONGTEXT, false )
89         add_string( "lua-config", "", NULL,
90                     CONFIG_TEXT, CONFIG_LONGTEXT, false )
91         set_section( N_("Lua HTTP" ), 0 )
92             add_string ( "http-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT, true )
93             add_string ( "http-src",  NULL, NULL, SRC_TEXT,  SRC_LONGTEXT,  true )
94             add_bool   ( "http-index", false, NULL, INDEX_TEXT, INDEX_LONGTEXT, true )
95         set_callbacks( Open_LuaIntf, Close_LuaIntf )
96
97     add_submodule ()
98         set_shortname( N_( "Lua Meta Fetcher" ) )
99         set_description( N_("Fetch meta data using lua scripts") )
100         set_capability( "meta fetcher", 10 )
101         set_callbacks( FetchMeta, NULL )
102
103     add_submodule ()
104         set_shortname( N_( "Lua Meta Reader" ) )
105         set_description( N_("Read meta data using lua scripts") )
106         set_capability( "meta reader", 10 )
107         set_callbacks( ReadMeta, NULL )
108
109     add_submodule ()
110         add_shortcut( "luaplaylist" )
111         set_shortname( N_("Lua Playlist") )
112         set_description( N_("Lua Playlist Parser Interface") )
113         set_capability( "demux", 2 )
114         set_callbacks( Import_LuaPlaylist, Close_LuaPlaylist )
115
116     add_submodule ()
117         set_description( N_("Lua Interface Module (shortcuts)") )
118         add_shortcut( "luarc" )
119         add_shortcut( "rc" )
120         set_capability( "interface", 25 )
121         set_callbacks( Open_LuaIntf, Close_LuaIntf )
122
123     add_submodule ()
124         set_shortname( N_( "Lua Art" ) )
125         set_description( N_("Fetch artwork using lua scripts") )
126         set_capability( "art finder", 10 )
127         set_callbacks( FindArt, NULL )
128
129     add_submodule ()
130         set_shortname( N_("Lua Extension") )
131         add_shortcut( "luaextension" )
132         set_capability( "extension", 1 )
133         set_callbacks( Open_Extension, Close_Extension )
134
135     add_submodule ()
136         set_description( N_("Lua SD Module") )
137         add_shortcut( "luasd" )
138         set_capability( "services_discovery", 0 )
139         add_string( "lua-sd", "", NULL, NULL, NULL, false )
140             change_volatile()
141         add_string( "lua-longname", "", NULL, NULL, NULL, false )
142             change_volatile()
143         set_callbacks( Open_LuaSD, Close_LuaSD )
144
145     add_submodule ()
146         set_description( N_("Freebox TV") )
147         add_shortcut( "freebox" )
148         set_capability( "services_discovery", 0 )
149         set_callbacks( Open_LuaSD, Close_LuaSD )
150
151     add_submodule ()
152         set_description( N_("French TV") )
153         add_shortcut( "frenchtv" )
154         set_capability( "services_discovery", 0 )
155         set_callbacks( Open_LuaSD, Close_LuaSD )
156
157     VLC_SD_PROBE_SUBMODULE
158
159 vlc_module_end ()
160
161 /*****************************************************************************
162  *
163  *****************************************************************************/
164 static const char *ppsz_lua_exts[] = { ".luac", ".lua", NULL };
165 static int file_select( const char *file )
166 {
167     int i = strlen( file );
168     int j;
169     for( j = 0; ppsz_lua_exts[j]; j++ )
170     {
171         int l = strlen( ppsz_lua_exts[j] );
172         if( i >= l && !strcmp( file+i-l, ppsz_lua_exts[j] ) )
173             return 1;
174     }
175     return 0;
176 }
177
178 static int file_compare( const char **a, const char **b )
179 {
180     return strcmp( *a, *b );
181 }
182
183 int vlclua_dir_list( vlc_object_t *p_this, const char *luadirname,
184                      char ***pppsz_dir_list )
185 {
186 #define MAX_DIR_LIST_SIZE 5
187     *pppsz_dir_list = malloc(MAX_DIR_LIST_SIZE*sizeof(char *));
188     if (!*pppsz_dir_list)
189         return VLC_EGENERIC;
190     char **ppsz_dir_list = *pppsz_dir_list;
191
192     int i = 0;
193     char *datadir = config_GetUserDir( VLC_DATA_DIR );
194
195     if( likely(datadir != NULL)
196      && likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
197                          datadir, luadirname ) != -1) )
198         i++;
199     free( datadir );
200
201 #if !(defined(__APPLE__) || defined(SYS_BEOS) || defined(WIN32))
202     if( likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
203                          config_GetLibDir(), luadirname ) != -1) )
204             i++;
205 #endif
206
207     char *psz_datapath = config_GetDataDir( p_this );
208     if( likely(psz_datapath != NULL) )
209     {
210         if( likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
211                               psz_datapath, luadirname ) != -1) )
212             i++;
213
214 #if defined(__APPLE__) || defined(SYS_BEOS)
215         if( likely(asprintf( &ppsz_dir_list[i],
216                              "%s"DIR_SEP"share"DIR_SEP"lua"DIR_SEP"%s",
217                              psz_datapath, luadirname ) != -1) )
218             i++;
219 #endif
220         free( psz_datapath );
221     }
222
223     ppsz_dir_list[i] = NULL;
224
225     assert( i < MAX_DIR_LIST_SIZE);
226
227     return VLC_SUCCESS;
228 }
229
230 void vlclua_dir_list_free( char **ppsz_dir_list )
231 {
232     char **ppsz_dir;
233     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
234         free( *ppsz_dir );
235     free( ppsz_dir_list );
236 }
237
238 /*****************************************************************************
239  * Will execute func on all scripts in luadirname, and stop if func returns
240  * success.
241  *****************************************************************************/
242 int vlclua_scripts_batch_execute( vlc_object_t *p_this,
243                                   const char * luadirname,
244                                   int (*func)(vlc_object_t *, const char *, void *),
245                                   void * user_data)
246 {
247     char **ppsz_dir_list = NULL;
248
249     int i_ret = vlclua_dir_list( p_this, luadirname, &ppsz_dir_list );
250     if( i_ret != VLC_SUCCESS )
251         return i_ret;
252     i_ret = VLC_EGENERIC;
253
254     for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
255     {
256         char **ppsz_filelist;
257         int i_files;
258
259         msg_Dbg( p_this, "Trying Lua scripts in %s", *ppsz_dir );
260         i_files = vlc_scandir( *ppsz_dir, &ppsz_filelist, file_select,
261                                 file_compare );
262         if( i_files < 0 )
263             continue;
264
265         char **ppsz_file = ppsz_filelist;
266         char **ppsz_fileend = ppsz_filelist + i_files;
267
268         while( ppsz_file < ppsz_fileend )
269         {
270             char *psz_filename;
271
272             if( asprintf( &psz_filename,
273                           "%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file ) == -1 )
274                 psz_filename = NULL;
275             free( *(ppsz_file++) );
276
277             if( likely(psz_filename != NULL) )
278             {
279                 msg_Dbg( p_this, "Trying Lua playlist script %s",
280                          psz_filename );
281                 i_ret = func( p_this, psz_filename, user_data );
282                 free( psz_filename );
283                 if( i_ret == VLC_SUCCESS )
284                     break;
285             }
286         }
287
288         while( ppsz_file < ppsz_fileend )
289             free( *(ppsz_file++) );
290         free( ppsz_filelist );
291
292         if( i_ret == VLC_SUCCESS )
293             break;
294     }
295     vlclua_dir_list_free( ppsz_dir_list );
296     return i_ret;
297 }
298
299 char *vlclua_find_file( vlc_object_t *p_this, const char *psz_luadirname, const char *psz_name )
300 {
301     char **ppsz_dir_list = NULL;
302     char **ppsz_dir;
303     vlclua_dir_list( p_this, psz_luadirname, &ppsz_dir_list );
304     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
305     {
306         for( const char **ppsz_ext = ppsz_lua_exts; *ppsz_ext; ppsz_ext++ )
307         {
308             char *psz_filename;
309             struct stat st;
310
311             if( asprintf( &psz_filename, "%s"DIR_SEP"%s%s", *ppsz_dir,
312                           psz_name, *ppsz_ext ) < 0 )
313             {
314                 vlclua_dir_list_free( ppsz_dir_list );
315                 return NULL;
316             }
317
318             if( vlc_stat( psz_filename, &st ) == 0
319                 && S_ISREG( st.st_mode ) )
320             {
321                 vlclua_dir_list_free( ppsz_dir_list );
322                 return psz_filename;
323             }
324             free( psz_filename );
325         }
326     }
327     vlclua_dir_list_free( ppsz_dir_list );
328     return NULL;
329 }
330
331 /*****************************************************************************
332  * Meta data setters utility.
333  * Playlist item table should be on top of the stack when these are called
334  *****************************************************************************/
335 void __vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
336                               input_item_t *p_input )
337 {
338 #define TRY_META( a, b )                                        \
339     lua_getfield( L, -1, a );                                   \
340     if( lua_isstring( L, -1 ) &&                                \
341         strcmp( lua_tostring( L, -1 ), "" ) )                   \
342     {                                                           \
343         char *psz_value = strdup( lua_tostring( L, -1 ) );      \
344         EnsureUTF8( psz_value );                                \
345         msg_Dbg( p_this, #b ": %s", psz_value );                \
346         input_item_Set ## b ( p_input, psz_value );             \
347         free( psz_value );                                      \
348     }                                                           \
349     lua_pop( L, 1 ); /* pop a */
350     TRY_META( "title", Title );
351     TRY_META( "artist", Artist );
352     TRY_META( "genre", Genre );
353     TRY_META( "copyright", Copyright );
354     TRY_META( "album", Album );
355     TRY_META( "tracknum", TrackNum );
356     TRY_META( "description", Description );
357     TRY_META( "rating", Rating );
358     TRY_META( "date", Date );
359     TRY_META( "setting", Setting );
360     TRY_META( "url", URL );
361     TRY_META( "language", Language );
362     TRY_META( "nowplaying", NowPlaying );
363     TRY_META( "publisher", Publisher );
364     TRY_META( "encodedby", EncodedBy );
365     TRY_META( "arturl", ArtURL );
366     TRY_META( "trackid", TrackID );
367 }
368
369 void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
370                                      input_item_t *p_input )
371 {
372     /* ... item */
373     lua_getfield( L, -1, "meta" );
374     /* ... item meta */
375     if( lua_istable( L, -1 ) )
376     {
377         lua_pushnil( L );
378         /* ... item meta nil */
379         while( lua_next( L, -2 ) )
380         {
381             /* ... item meta key value */
382             if( !lua_isstring( L, -2 ) )
383             {
384                 msg_Warn( p_this, "Custom meta data category name must be "
385                                    "a string" );
386             }
387             else if( !lua_istable( L, -1 ) )
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( L, -2 );
395                 msg_Dbg( p_this, "Found custom meta data category: %s",
396                          psz_meta_category );
397                 lua_pushnil( L );
398                 /* ... item meta key value nil */
399                 while( lua_next( L, -2 ) )
400                 {
401                     /* ... item meta key value key2 value2 */
402                     if( !lua_isstring( L, -2 ) )
403                     {
404                         msg_Warn( p_this, "Custom meta category item name "
405                                            "must be a string." );
406                     }
407                     else if( !lua_isstring( L, -1 ) )
408                     {
409                         msg_Warn( p_this, "Custom meta category item value "
410                                            "must be a string." );
411                     }
412                     else
413                     {
414                         const char *psz_meta_name =
415                             lua_tostring( L, -2 );
416                         const char *psz_meta_value =
417                             lua_tostring( L, -1 );
418                         msg_Dbg( p_this, "Custom meta %s, %s: %s",
419                                  psz_meta_category, psz_meta_name,
420                                  psz_meta_value );
421                         input_item_AddInfo( p_input, psz_meta_category,
422                                            psz_meta_name, "%s", psz_meta_value );
423                     }
424                     lua_pop( L, 1 ); /* pop item */
425                     /* ... item meta key value key2 */
426                 }
427                 /* ... item meta key value */
428             }
429             lua_pop( L, 1 ); /* pop category */
430             /* ... item meta key */
431         }
432         /* ... item meta */
433     }
434     lua_pop( L, 1 ); /* pop "meta" */
435     /* ... item -> back to original stack */
436 }
437
438 /*****************************************************************************
439  * Playlist utilities
440  ****************************************************************************/
441 /**
442  * Playlist item table should be on top of the stack when this is called
443  */
444 void __vlclua_read_options( vlc_object_t *p_this, lua_State *L,
445                             int *pi_options, char ***pppsz_options )
446 {
447     lua_getfield( L, -1, "options" );
448     if( lua_istable( L, -1 ) )
449     {
450         lua_pushnil( L );
451         while( lua_next( L, -2 ) )
452         {
453             if( lua_isstring( L, -1 ) )
454             {
455                 char *psz_option = strdup( lua_tostring( L, -1 ) );
456                 msg_Dbg( p_this, "Option: %s", psz_option );
457                 INSERT_ELEM( *pppsz_options, *pi_options, *pi_options,
458                              psz_option );
459             }
460             else
461             {
462                 msg_Warn( p_this, "Option should be a string" );
463             }
464             lua_pop( L, 1 ); /* pop option */
465         }
466     }
467     lua_pop( L, 1 ); /* pop "options" */
468 }
469
470 int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
471                                     playlist_t *p_playlist,
472                                     input_item_t *p_parent, bool b_play )
473 {
474     int i_count = 0;
475     input_item_node_t *p_parent_node = NULL;
476
477     assert( p_parent || p_playlist );
478
479     /* playlist */
480     if( lua_istable( L, -1 ) )
481     {
482         if( p_parent ) p_parent_node = input_item_node_Create( p_parent );
483         lua_pushnil( L );
484         /* playlist nil */
485         while( lua_next( L, -2 ) )
486         {
487             /* playlist key item */
488             /* <Parse playlist item> */
489             if( lua_istable( L, -1 ) )
490             {
491                 lua_getfield( L, -1, "path" );
492                 /* playlist key item path */
493                 if( lua_isstring( L, -1 ) )
494                 {
495                     const char   *psz_path     = NULL;
496                     const char   *psz_name     = NULL;
497                     char        **ppsz_options = NULL;
498                     int           i_options    = 0;
499                     mtime_t       i_duration   = -1;
500                     input_item_t *p_input;
501
502                     /* Read path and name */
503                     psz_path = lua_tostring( L, -1 );
504                     msg_Dbg( p_this, "Path: %s", psz_path );
505                     lua_getfield( L, -2, "name" );
506                     /* playlist key item path name */
507                     if( lua_isstring( L, -1 ) )
508                     {
509                         psz_name = lua_tostring( L, -1 );
510                         msg_Dbg( p_this, "Name: %s", psz_name );
511                     }
512                     else
513                     {
514                         if( !lua_isnil( L, -1 ) )
515                             msg_Warn( p_this, "Playlist item name should be a string." );
516                         psz_name = psz_path;
517                     }
518
519                     /* Read duration */
520                     lua_getfield( L, -3, "duration" );
521                     /* playlist key item path name duration */
522                     if( lua_isnumber( L, -1 ) )
523                     {
524                         i_duration = (mtime_t)(lua_tonumber( L, -1 )*1e6);
525                     }
526                     else if( !lua_isnil( L, -1 ) )
527                     {
528                         msg_Warn( p_this, "Playlist item duration should be a number (in seconds)." );
529                     }
530                     lua_pop( L, 1 ); /* pop "duration" */
531
532                     /* playlist key item path name */
533
534                     /* Read options: item must be on top of stack */
535                     lua_pushvalue( L, -3 );
536                     /* playlist key item path name item */
537                     vlclua_read_options( p_this, L, &i_options, &ppsz_options );
538
539                     /* Create input item */
540                     p_input = input_item_NewExt( p_playlist, psz_path,
541                                                 psz_name, i_options,
542                                                 (const char **)ppsz_options,
543                                                 VLC_INPUT_OPTION_TRUSTED,
544                                                 i_duration );
545                     lua_pop( L, 3 ); /* pop "path name item" */
546                     /* playlist key item */
547
548                     /* Read meta data: item must be on top of stack */
549                     vlclua_read_meta_data( p_this, L, p_input );
550
551                     /* Read custom meta data: item must be on top of stack*/
552                     vlclua_read_custom_meta_data( p_this, L, p_input );
553
554                     /* Append item to playlist */
555                     if( p_parent ) /* Add to node */
556                     {
557                         input_item_node_AppendItem( p_parent_node, p_input );
558                     }
559                     else /* Play or Enqueue (preparse) */
560                         /* FIXME: playlist_AddInput() can fail */
561                         playlist_AddInput( p_playlist, p_input,
562                                PLAYLIST_APPEND |
563                                ( b_play ? PLAYLIST_GO : PLAYLIST_PREPARSE ),
564                                PLAYLIST_END, true, false );
565                     i_count ++; /* increment counter */
566                     vlc_gc_decref( p_input );
567                     while( i_options > 0 )
568                         free( ppsz_options[--i_options] );
569                     free( ppsz_options );
570                 }
571                 else
572                 {
573                     lua_pop( L, 1 ); /* pop "path" */
574                     msg_Warn( p_this,
575                              "Playlist item's path should be a string" );
576                 }
577                 /* playlist key item */
578             }
579             else
580             {
581                 msg_Warn( p_this, "Playlist item should be a table" );
582             }
583             /* <Parse playlist item> */
584             lua_pop( L, 1 ); /* pop the value, keep the key for
585                               * the next lua_next() call */
586             /* playlist key */
587         }
588         /* playlist */
589         if( p_parent )
590         {
591             if( i_count ) input_item_node_PostAndDelete( p_parent_node );
592             else input_item_node_Delete( p_parent_node );
593         }
594     }
595     else
596     {
597         msg_Warn( p_this, "Playlist should be a table." );
598     }
599     return i_count;
600 }
601
602 static int vlc_sd_probe_Open( vlc_object_t *obj )
603 {
604     vlc_probe_t *probe = (vlc_probe_t *)obj;
605     char **ppsz_filelist = NULL;
606     char **ppsz_fileend  = NULL;
607     char **ppsz_file;
608     char *psz_name;
609     char **ppsz_dir_list = NULL;
610     char **ppsz_dir;
611     lua_State *L = NULL;
612     vlclua_dir_list( obj, "sd", &ppsz_dir_list );
613     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
614     {
615         int i_files;
616         if( ppsz_filelist )
617         {
618             for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
619                  ppsz_file++ )
620                 free( *ppsz_file );
621             free( ppsz_filelist );
622             ppsz_filelist = NULL;
623         }
624         i_files = vlc_scandir( *ppsz_dir, &ppsz_filelist, file_select,
625                                 file_compare );
626         if( i_files < 1 ) continue;
627         ppsz_fileend = ppsz_filelist + i_files;
628         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend; ppsz_file++ )
629         {
630             char  *psz_filename;
631             if( asprintf( &psz_filename,
632                           "%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file ) < 0 )
633             {
634                 goto error;
635             }
636             L = luaL_newstate();
637             if( !L )
638             {
639                 msg_Err( probe, "Could not create new Lua State" );
640                 free( psz_filename );
641                 goto error;
642             }
643             luaL_openlibs( L );
644             if( vlclua_add_modules_path( probe, L, psz_filename ) )
645             {
646                 msg_Err( probe, "Error while setting the module search path for %s",
647                           psz_filename );
648                 free( psz_filename );
649                 goto error;
650             }
651             if( luaL_dofile( L, psz_filename ) )
652             {
653
654                 msg_Err( probe, "Error loading script %s: %s", psz_filename,
655                           lua_tostring( L, lua_gettop( L ) ) );
656                 lua_pop( L, 1 );
657                 free( psz_filename );
658                 lua_close( L );
659                 continue;
660             }
661             char *psz_longname;
662             char *temp = strchr( *ppsz_file, '.' );
663             if( temp )
664                 *temp = '\0';
665             lua_getglobal( L, "descriptor" );
666             if( !lua_isfunction( L, lua_gettop( L ) ) || lua_pcall( L, 0, 1, 0 ) )
667             {
668                 lua_pop( L, 1 );
669                 if( !( psz_longname = strdup( *ppsz_file ) ) )
670                 {
671                     free( psz_filename );
672                     goto error;
673                 }
674             }
675             else
676             {
677                 lua_getfield( L, -1, "title" );
678                 if( !lua_isstring( L, -1 ) ||
679                     !( psz_longname = strdup( lua_tostring( L, -1 ) ) ) )
680                 {
681                     free( psz_filename );
682                     goto error;
683                 }
684             }
685
686             char *psz_file_esc = config_StringEscape( *ppsz_file );
687             char *psz_longname_esc = config_StringEscape( psz_longname );
688             if( asprintf( &psz_name, "lua{sd='%s',longname='%s'}",
689                           psz_file_esc, psz_longname_esc ) < 0 )
690             {
691                 free( psz_file_esc );
692                 free( psz_longname_esc );
693                 free( psz_filename );
694                 free( psz_longname );
695                 goto error;
696             }
697             free( psz_file_esc );
698             free( psz_longname_esc );
699             vlc_sd_probe_Add( probe, psz_name, psz_longname, SD_CAT_INTERNET );
700             free( psz_name );
701             free( psz_longname );
702             free( psz_filename );
703             lua_close( L );
704         }
705     }
706     if( ppsz_filelist )
707     {
708         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
709              ppsz_file++ )
710             free( *ppsz_file );
711         free( ppsz_filelist );
712     }
713     vlclua_dir_list_free( ppsz_dir_list );
714     return VLC_PROBE_CONTINUE;
715 error:
716     if( ppsz_filelist )
717     {
718         for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
719              ppsz_file++ )
720             free( *ppsz_file );
721         free( ppsz_filelist );
722     }
723     if( L )
724         lua_close( L );
725     vlclua_dir_list_free( ppsz_dir_list );
726     return VLC_ENOMEM;
727 }
728
729 static int vlclua_add_modules_path_inner( lua_State *L, const char *psz_path )
730 {
731     int count = 0;
732     for( const char **ppsz_ext = ppsz_lua_exts; *ppsz_ext; ppsz_ext++ )
733     {
734         lua_pushfstring( L, "%s"DIR_SEP"modules"DIR_SEP"?%s;",
735                          psz_path, *ppsz_ext );
736         count ++;
737     }
738
739     return count;
740 }
741
742 int __vlclua_add_modules_path( vlc_object_t *obj, lua_State *L, const char *psz_filename )
743 {
744     /* Setup the module search path:
745      *   * "The script's directory"/modules
746      *   * "The script's parent directory"/modules
747      *   * and so on for all the next directories in the directory list
748      */
749     char *psz_path = strdup( psz_filename );
750     if( !psz_path )
751         return 1;
752
753     char *psz_char = strrchr( psz_path, DIR_SEP_CHAR );
754     if( !psz_char )
755     {
756         free( psz_path );
757         return 1;
758     }
759     *psz_char = '\0';
760
761     /* psz_path now holds the file's directory */
762     psz_char = strrchr( psz_path, DIR_SEP_CHAR );
763     if( !psz_char )
764     {
765         free( psz_path );
766         return 1;
767     }
768     *psz_char = '\0';
769
770     /* Push package on stack */
771     int count = 0;
772     lua_getglobal( L, "package" );
773
774     /* psz_path now holds the file's parent directory */
775     count += vlclua_add_modules_path_inner( L, psz_path );
776     *psz_char = DIR_SEP_CHAR;
777
778     /* psz_path now holds the file's directory */
779     count += vlclua_add_modules_path_inner( L, psz_path );
780
781     char **ppsz_dir_list = NULL;
782     vlclua_dir_list( obj, psz_char+1/* gruik? */, &ppsz_dir_list );
783     char **ppsz_dir = ppsz_dir_list;
784
785     for( ; *ppsz_dir && strcmp( *ppsz_dir, psz_path ); ppsz_dir++ );
786     free( psz_path );
787
788     for( ; *ppsz_dir; ppsz_dir++ )
789     {
790         psz_path = *ppsz_dir;
791         /* FIXME: doesn't work well with meta/... modules due to the double
792          * directory depth */
793         psz_char = strrchr( psz_path, DIR_SEP_CHAR );
794         if( !psz_char )
795         {
796             vlclua_dir_list_free( ppsz_dir_list );
797             return 1;
798         }
799
800         *psz_char = '\0';
801         count += vlclua_add_modules_path_inner( L, psz_path );
802         *psz_char = DIR_SEP_CHAR;
803         count += vlclua_add_modules_path_inner( L, psz_path );
804     }
805
806     lua_getfield( L, -(count+1), "path" ); /* Get package.path */
807     lua_concat( L, count+1 ); /* Concat vlc module paths and package.path */
808     lua_setfield( L, -2, "path"); /* Set package.path */
809     lua_pop( L, 1 ); /* Pop the package module */
810
811     vlclua_dir_list_free( ppsz_dir_list );
812     return 0;
813 }
814