You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
18 KiB
488 lines
18 KiB
/*****************************************************************************
|
|
* vlc.c: Generic lua interface functions
|
|
*****************************************************************************
|
|
* Copyright (C) 2007-2008 the VideoLAN team
|
|
* $Id$
|
|
*
|
|
* Authors: Antoine Cellerier <dionoea at videolan tod org>
|
|
* Pierre d'Herbemont <pdherbemont # videolan.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Preamble
|
|
*****************************************************************************/
|
|
#ifndef _GNU_SOURCE
|
|
# define _GNU_SOURCE
|
|
#endif
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_meta.h>
|
|
#include <vlc_charset.h>
|
|
#include <vlc_aout.h>
|
|
|
|
#include <lua.h> /* Low level lua C API */
|
|
#include <lauxlib.h> /* Higher level C API */
|
|
#include <lualib.h> /* Lua libs */
|
|
|
|
#include "vlc.h"
|
|
|
|
/*****************************************************************************
|
|
* Module descriptor
|
|
*****************************************************************************/
|
|
|
|
#define INTF_TEXT N_("Lua interface")
|
|
#define INTF_LONGTEXT N_("Lua interface module to load")
|
|
|
|
#define CONFIG_TEXT N_("Lua interface configuration")
|
|
#define CONFIG_LONGTEXT N_("Lua interface configuration string. Format is: '[\"<interface module name>\"] = { <option> = <value>, ...}, ...'.")
|
|
|
|
vlc_module_begin();
|
|
set_shortname( N_( "Lua Art" ) );
|
|
set_description( N_("Fetch artwork using lua scripts") );
|
|
set_capability( "art finder", 10 );
|
|
set_callbacks( FindArt, NULL );
|
|
|
|
add_submodule();
|
|
add_shortcut( "luaplaylist" );
|
|
set_category( CAT_INPUT );
|
|
set_subcategory( SUBCAT_INPUT_DEMUX );
|
|
set_shortname( N_("Lua Playlist") );
|
|
set_description( N_("Lua Playlist Parser Interface") );
|
|
set_capability( "demux", 2 );
|
|
set_callbacks( Import_LuaPlaylist, Close_LuaPlaylist );
|
|
|
|
add_submodule();
|
|
add_shortcut( "luaintf" );
|
|
add_shortcut( "luarc" );
|
|
/* add_shortcut( "rc" ); */
|
|
add_shortcut( "luahotkeys" );
|
|
/* add_shortcut( "hotkeys" ); */
|
|
add_shortcut( "luatelnet" );
|
|
/* add_shortcut( "telnet" ); */
|
|
add_shortcut( "luahttp" );
|
|
/* add_shortcut( "http" ); */
|
|
set_description( N_("Lua Interface Module") );
|
|
set_capability( "interface", 0 );
|
|
add_string( "lua-intf", "dummy", NULL,
|
|
INTF_TEXT, INTF_LONGTEXT, false );
|
|
add_string( "lua-config", "", NULL,
|
|
CONFIG_TEXT, CONFIG_LONGTEXT, false );
|
|
set_callbacks( Open_LuaIntf, Close_LuaIntf );
|
|
vlc_module_end();
|
|
|
|
/*****************************************************************************
|
|
*
|
|
*****************************************************************************/
|
|
static int file_select( const char *file )
|
|
{
|
|
int i = strlen( file );
|
|
return i > 4 && !strcmp( file+i-4, ".lua" );
|
|
}
|
|
|
|
static int file_compare( const char **a, const char **b )
|
|
{
|
|
return strcmp( *a, *b );
|
|
}
|
|
|
|
int vlclua_dir_list( const char *luadirname, char **ppsz_dir_list )
|
|
{
|
|
int i = 0;
|
|
char *datadir = config_GetUserDataDir();
|
|
if( datadir == NULL )
|
|
return VLC_ENOMEM;
|
|
|
|
if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
|
|
datadir, luadirname ) < 0 )
|
|
{
|
|
free( datadir );
|
|
return VLC_ENOMEM;
|
|
}
|
|
free( datadir );
|
|
i++;
|
|
|
|
# if defined(__APPLE__) || defined(SYS_BEOS) || defined(WIN32)
|
|
{
|
|
const char *psz_vlcpath = config_GetDataDir();
|
|
if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
|
|
psz_vlcpath, luadirname ) < 0 )
|
|
return VLC_ENOMEM;
|
|
i++;
|
|
if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "share" DIR_SEP "lua" DIR_SEP "%s",
|
|
psz_vlcpath, luadirname ) < 0 )
|
|
return VLC_ENOMEM;
|
|
i++;
|
|
|
|
}
|
|
# else
|
|
if( asprintf( &ppsz_dir_list[i], "%s" DIR_SEP "lua" DIR_SEP "%s",
|
|
config_GetDataDir (), luadirname ) < 0 )
|
|
return VLC_ENOMEM;
|
|
i++;
|
|
# endif
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
void vlclua_dir_list_free( char **ppsz_dir_list )
|
|
{
|
|
char **ppsz_dir;
|
|
for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
|
|
free( *ppsz_dir );
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Will execute func on all scripts in luadirname, and stop if func returns
|
|
* success.
|
|
*****************************************************************************/
|
|
int vlclua_scripts_batch_execute( vlc_object_t *p_this,
|
|
const char * luadirname,
|
|
int (*func)(vlc_object_t *, const char *, lua_State *, void *),
|
|
lua_State * L,
|
|
void * user_data)
|
|
{
|
|
int i_ret = VLC_EGENERIC;
|
|
|
|
char **ppsz_filelist = NULL;
|
|
char **ppsz_fileend = NULL;
|
|
char **ppsz_file;
|
|
|
|
char *ppsz_dir_list[] = { NULL, NULL, NULL, NULL };
|
|
char **ppsz_dir;
|
|
|
|
i_ret = vlclua_dir_list( luadirname, ppsz_dir_list );
|
|
if( i_ret != VLC_SUCCESS )
|
|
return i_ret;
|
|
i_ret = VLC_EGENERIC;
|
|
|
|
|
|
for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
|
|
{
|
|
int i_files;
|
|
|
|
if( ppsz_filelist )
|
|
{
|
|
for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
|
|
ppsz_file++ )
|
|
free( *ppsz_file );
|
|
free( ppsz_filelist );
|
|
ppsz_filelist = NULL;
|
|
}
|
|
|
|
msg_Dbg( p_this, "Trying Lua scripts in %s", *ppsz_dir );
|
|
i_files = utf8_scandir( *ppsz_dir, &ppsz_filelist, file_select,
|
|
file_compare );
|
|
if( i_files < 1 ) continue;
|
|
ppsz_fileend = ppsz_filelist + i_files;
|
|
|
|
for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend; ppsz_file++ )
|
|
{
|
|
char *psz_filename;
|
|
if( asprintf( &psz_filename,
|
|
"%s" DIR_SEP "%s", *ppsz_dir, *ppsz_file ) < 0)
|
|
{
|
|
vlclua_dir_list_free( ppsz_dir_list );
|
|
return VLC_ENOMEM;
|
|
}
|
|
msg_Dbg( p_this, "Trying Lua playlist script %s", psz_filename );
|
|
|
|
i_ret = func( p_this, psz_filename, L, user_data );
|
|
|
|
free( psz_filename );
|
|
|
|
if( i_ret == VLC_SUCCESS ) break;
|
|
}
|
|
if( i_ret == VLC_SUCCESS ) break;
|
|
}
|
|
|
|
if( ppsz_filelist )
|
|
{
|
|
for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
|
|
ppsz_file++ )
|
|
free( *ppsz_file );
|
|
free( ppsz_filelist );
|
|
}
|
|
vlclua_dir_list_free( ppsz_dir_list );
|
|
|
|
return i_ret;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Meta data setters utility.
|
|
* Playlist item table should be on top of the stack when these are called
|
|
*****************************************************************************/
|
|
void __vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
|
|
input_item_t *p_input )
|
|
{
|
|
#define TRY_META( a, b ) \
|
|
lua_getfield( L, -1, a ); \
|
|
if( lua_isstring( L, -1 ) ) \
|
|
{ \
|
|
char *psz_value = strdup( lua_tostring( L, -1 ) ); \
|
|
EnsureUTF8( psz_value ); \
|
|
msg_Dbg( p_this, #b ": %s", psz_value ); \
|
|
input_item_Set ## b ( p_input, psz_value ); \
|
|
free( psz_value ); \
|
|
} \
|
|
lua_pop( L, 1 ); /* pop a */
|
|
TRY_META( "title", Title );
|
|
TRY_META( "artist", Artist );
|
|
TRY_META( "genre", Genre );
|
|
TRY_META( "copyright", Copyright );
|
|
TRY_META( "album", Album );
|
|
TRY_META( "tracknum", TrackNum );
|
|
TRY_META( "description", Description );
|
|
TRY_META( "rating", Rating );
|
|
TRY_META( "date", Date );
|
|
TRY_META( "setting", Setting );
|
|
TRY_META( "url", URL );
|
|
TRY_META( "language", Language );
|
|
TRY_META( "nowplaying", NowPlaying );
|
|
TRY_META( "publisher", Publisher );
|
|
TRY_META( "encodedby", EncodedBy );
|
|
TRY_META( "arturl", ArtURL );
|
|
TRY_META( "trackid", TrackID );
|
|
}
|
|
|
|
void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
|
|
input_item_t *p_input )
|
|
{
|
|
/* ... item */
|
|
lua_getfield( L, -1, "meta" );
|
|
/* ... item meta */
|
|
if( lua_istable( L, -1 ) )
|
|
{
|
|
lua_pushnil( L );
|
|
/* ... item meta nil */
|
|
while( lua_next( L, -2 ) )
|
|
{
|
|
/* ... item meta key value */
|
|
if( !lua_isstring( L, -2 ) )
|
|
{
|
|
msg_Warn( p_this, "Custom meta data category name must be "
|
|
"a string" );
|
|
}
|
|
else if( !lua_istable( L, -1 ) )
|
|
{
|
|
msg_Warn( p_this, "Custom meta data category contents "
|
|
"must be a table" );
|
|
}
|
|
else
|
|
{
|
|
const char *psz_meta_category = lua_tostring( L, -2 );
|
|
msg_Dbg( p_this, "Found custom meta data category: %s",
|
|
psz_meta_category );
|
|
lua_pushnil( L );
|
|
/* ... item meta key value nil */
|
|
while( lua_next( L, -2 ) )
|
|
{
|
|
/* ... item meta key value key2 value2 */
|
|
if( !lua_isstring( L, -2 ) )
|
|
{
|
|
msg_Warn( p_this, "Custom meta category item name "
|
|
"must be a string." );
|
|
}
|
|
else if( !lua_isstring( L, -1 ) )
|
|
{
|
|
msg_Warn( p_this, "Custom meta category item value "
|
|
"must be a string." );
|
|
}
|
|
else
|
|
{
|
|
const char *psz_meta_name =
|
|
lua_tostring( L, -2 );
|
|
const char *psz_meta_value =
|
|
lua_tostring( L, -1 );
|
|
msg_Dbg( p_this, "Custom meta %s, %s: %s",
|
|
psz_meta_category, psz_meta_name,
|
|
psz_meta_value );
|
|
input_item_AddInfo( p_input, psz_meta_category,
|
|
psz_meta_name, psz_meta_value );
|
|
}
|
|
lua_pop( L, 1 ); /* pop item */
|
|
/* ... item meta key value key2 */
|
|
}
|
|
/* ... item meta key value */
|
|
}
|
|
lua_pop( L, 1 ); /* pop category */
|
|
/* ... item meta key */
|
|
}
|
|
/* ... item meta */
|
|
}
|
|
lua_pop( L, 1 ); /* pop "meta" */
|
|
/* ... item -> back to original stack */
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Playlist utilities
|
|
****************************************************************************/
|
|
/**
|
|
* Playlist item table should be on top of the stack when this is called
|
|
*/
|
|
void __vlclua_read_options( vlc_object_t *p_this, lua_State *L,
|
|
int *pi_options, char ***pppsz_options )
|
|
{
|
|
lua_getfield( L, -1, "options" );
|
|
if( lua_istable( L, -1 ) )
|
|
{
|
|
lua_pushnil( L );
|
|
while( lua_next( L, -2 ) )
|
|
{
|
|
if( lua_isstring( L, -1 ) )
|
|
{
|
|
char *psz_option = strdup( lua_tostring( L, -1 ) );
|
|
msg_Dbg( p_this, "Option: %s", psz_option );
|
|
INSERT_ELEM( *pppsz_options, *pi_options, *pi_options,
|
|
psz_option );
|
|
}
|
|
else
|
|
{
|
|
msg_Warn( p_this, "Option should be a string" );
|
|
}
|
|
lua_pop( L, 1 ); /* pop option */
|
|
}
|
|
}
|
|
lua_pop( L, 1 ); /* pop "options" */
|
|
}
|
|
|
|
int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
|
|
playlist_t *p_playlist,
|
|
input_item_t *p_parent, bool b_play )
|
|
{
|
|
int i_count = 0;
|
|
|
|
assert( p_parent || p_playlist );
|
|
|
|
/* playlist */
|
|
if( lua_istable( L, -1 ) )
|
|
{
|
|
lua_pushnil( L );
|
|
/* playlist nil */
|
|
while( lua_next( L, -2 ) )
|
|
{
|
|
/* playlist key item */
|
|
/* <Parse playlist item> */
|
|
if( lua_istable( L, -1 ) )
|
|
{
|
|
lua_getfield( L, -1, "path" );
|
|
/* playlist key item path */
|
|
if( lua_isstring( L, -1 ) )
|
|
{
|
|
const char *psz_path = NULL;
|
|
const char *psz_name = NULL;
|
|
char **ppsz_options = NULL;
|
|
int i_options = 0;
|
|
mtime_t i_duration = -1;
|
|
input_item_t *p_input;
|
|
|
|
/* Read path and name */
|
|
psz_path = lua_tostring( L, -1 );
|
|
msg_Dbg( p_this, "Path: %s", psz_path );
|
|
lua_getfield( L, -2, "name" );
|
|
/* playlist key item path name */
|
|
if( lua_isstring( L, -1 ) )
|
|
{
|
|
psz_name = lua_tostring( L, -1 );
|
|
msg_Dbg( p_this, "Name: %s", psz_name );
|
|
}
|
|
else
|
|
{
|
|
if( !lua_isnil( L, -1 ) )
|
|
msg_Warn( p_this, "Playlist item name should be a string." );
|
|
psz_name = psz_path;
|
|
}
|
|
|
|
/* Read duration */
|
|
lua_getfield( L, -3, "duration" );
|
|
/* playlist key item path name duration */
|
|
if( lua_isnumber( L, -1 ) )
|
|
{
|
|
i_duration = (mtime_t)(lua_tonumber( L, -1 )*1e6);
|
|
}
|
|
else if( !lua_isnil( L, -1 ) )
|
|
{
|
|
msg_Warn( p_this, "Playlist item duration should be a number (in seconds)." );
|
|
}
|
|
lua_pop( L, 1 ); /* pop "duration" */
|
|
|
|
/* playlist key item path name */
|
|
|
|
/* Read options: item must be on top of stack */
|
|
lua_pushvalue( L, -3 );
|
|
/* playlist key item path name item */
|
|
vlclua_read_options( p_this, L, &i_options, &ppsz_options );
|
|
|
|
/* Create input item */
|
|
p_input = input_item_NewExt( p_playlist, psz_path,
|
|
psz_name, i_options,
|
|
(const char **)ppsz_options,
|
|
i_duration );
|
|
lua_pop( L, 3 ); /* pop "path name item" */
|
|
/* playlist key item */
|
|
|
|
/* Read meta data: item must be on top of stack */
|
|
vlclua_read_meta_data( p_this, L, p_input );
|
|
|
|
/* Read custom meta data: item must be on top of stack*/
|
|
vlclua_read_custom_meta_data( p_this, L, p_input );
|
|
|
|
/* Append item to playlist */
|
|
if( p_parent ) /* Add to node */
|
|
input_item_AddSubItem( p_parent, p_input );
|
|
else /* Play or Enqueue (preparse) */
|
|
/* FIXME: playlist_AddInput() can fail */
|
|
playlist_AddInput( p_playlist, p_input,
|
|
PLAYLIST_APPEND |
|
|
( b_play ? PLAYLIST_GO : PLAYLIST_PREPARSE ),
|
|
PLAYLIST_END, true, false );
|
|
i_count ++; /* increment counter */
|
|
vlc_gc_decref( p_input );
|
|
while( i_options > 0 )
|
|
free( ppsz_options[--i_options] );
|
|
free( ppsz_options );
|
|
}
|
|
else
|
|
{
|
|
lua_pop( L, 1 ); /* pop "path" */
|
|
msg_Warn( p_this,
|
|
"Playlist item's path should be a string" );
|
|
}
|
|
/* playlist key item */
|
|
}
|
|
else
|
|
{
|
|
msg_Warn( p_this, "Playlist item should be a table" );
|
|
}
|
|
/* <Parse playlist item> */
|
|
lua_pop( L, 1 ); /* pop the value, keep the key for
|
|
* the next lua_next() call */
|
|
/* playlist key */
|
|
}
|
|
/* playlist */
|
|
}
|
|
else
|
|
{
|
|
msg_Warn( p_this, "Playlist should be a table." );
|
|
}
|
|
return i_count;
|
|
}
|
|
|