]> git.sesse.net Git - vlc/commitdiff
* luaplaylist.c: First usable version of the luaplaylist module
authorAntoine Cellerier <dionoea@videolan.org>
Mon, 14 May 2007 22:27:36 +0000 (22:27 +0000)
committerAntoine Cellerier <dionoea@videolan.org>
Mon, 14 May 2007 22:27:36 +0000 (22:27 +0000)
* share/luaplaylist/*: Lua playlist scripts for youtube, daily motion, metacafe and google video

The luaplaylist demux has a score of 0 so you have to use --demux lua,any to enable it. I'll change its score once people report that it works fine.

modules/demux/playlist/luaplaylist.c
share/Makefile.am
share/luaplaylist/README.txt [new file with mode: 0644]
share/luaplaylist/dailymotion.lua [new file with mode: 0644]
share/luaplaylist/googlevideo.lua [new file with mode: 0644]
share/luaplaylist/metacafe.lua [new file with mode: 0644]
share/luaplaylist/test.lua [deleted file]
share/luaplaylist/youtube.lua [new file with mode: 0644]

index 3eb315d73851a83234ebb03e926f1c4761529916..b8b9a99aa4b8c1ac252cf71d34b9c1b8da09896a 100644 (file)
@@ -28,6 +28,7 @@
 #include <vlc_demux.h>
 #include <vlc_url.h>
 #include <vlc_strings.h>
+#include <vlc_charset.h>
 
 #include <errno.h>                                                 /* ENOMEM */
 #include "playlist.h"
@@ -88,9 +89,8 @@ static int vlclua_peek( lua_State *p_state )
     n = lua_tonumber( p_state, 1 );
     lua_pop( p_state, i );
     i_peek = stream_Peek( p_demux->s, &p_peek, n );
-    lua_pushnumber( p_state, i_peek );
     lua_pushlstring( p_state, (const char *)p_peek, i_peek );
-    return 2;
+    return 1;
 }
 
 static int vlclua_readline( lua_State *p_state )
@@ -115,6 +115,31 @@ static int vlclua_decode_uri( lua_State *p_state )
     return 1;
 }
 
+static int vlclua_resolve_xml_special_chars( lua_State *p_state )
+{
+    int i = lua_gettop( p_state );
+    if( !i ) return 0;
+    const char *psz_cstring = lua_tostring( p_state, 1 );
+    if( !psz_cstring ) return 0;
+    char *psz_string = strdup( psz_cstring );
+    lua_pop( p_state, i );
+    resolve_xml_special_chars( psz_string );
+    lua_pushstring( p_state, psz_string );
+    free( psz_string );
+    return 1;
+}
+
+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 );
+}
+
 /*****************************************************************************
  * Import_LuaPlaylist: main import function
  *****************************************************************************/
@@ -122,6 +147,22 @@ int E_(Import_LuaPlaylist)( vlc_object_t *p_this )
 {
     demux_t *p_demux = (demux_t *)p_this;
     lua_State *p_state;
+    int i_ret = VLC_EGENERIC;
+    char **ppsz_filelist = NULL;
+    char **ppsz_fileend = NULL;
+    char **ppsz_file;
+    DIR *dir;
+    char *psz_filename = NULL;
+    int i_files;
+    const char psz_dir[] = "share/luaplaylist"; /* FIXME */
+
+    static luaL_Reg p_reg[] =
+    {
+        { "readline", vlclua_readline },
+        { "peek", vlclua_peek },
+        { "decode_uri", vlclua_decode_uri },
+        { "resolve_xml_special_chars", vlclua_resolve_xml_special_chars }
+    };
 
     p_demux->p_sys = (demux_sys_t*)malloc( sizeof( demux_sys_t ) );
     if( !p_demux->p_sys )
@@ -145,13 +186,6 @@ int E_(Import_LuaPlaylist)( vlc_object_t *p_this )
     /* Load Lua libraries */
     luaL_openlibs( p_state ); /* FIXME: Don't open all the libs? */
 
-    static luaL_Reg p_reg[] =
-    {
-        { "readline", vlclua_readline },
-        { "peek", vlclua_peek },
-        { "decode_uri", vlclua_decode_uri }
-    };
-
     luaL_register( p_state, "vlc", p_reg );
     lua_pushlightuserdata( p_state, p_demux );
     lua_setfield( p_state, lua_gettop( p_state ) - 1, "private" );
@@ -162,45 +196,82 @@ int E_(Import_LuaPlaylist)( vlc_object_t *p_this )
 
     lua_pop( p_state, 1 );
 
+    dir = utf8_opendir( psz_dir );
+    if( !dir ) goto error;
+    i_files = utf8_loaddir( dir, &ppsz_filelist, file_select, file_compare );
+    if( i_files < 1 ) goto error;
+    ppsz_fileend = ppsz_filelist + i_files;
+
+    for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend; ppsz_file++ )
     {
-        const char *psz_filename = "share/luaplaylist/test.lua";
-        int i_ret;
+        free( psz_filename ); psz_filename = NULL;
+        asprintf( &psz_filename, "%s/%s", psz_dir, *ppsz_file );
+        msg_Dbg( p_demux, "Trying Lua playlist script %s", psz_filename );
+
+        /* Ugly hack to delete previous versions of the probe() and parse()
+         * functions. */
+        lua_pushnil( p_state );
+        lua_pushnil( p_state );
+        lua_setglobal( p_state, "probe" );
+        lua_setglobal( p_state, "parse" );
 
         /* Load and run the script(s) */
         if( luaL_dofile( p_state, psz_filename ) )
         {
-            msg_Warn( p_demux, "Error while runing script %s: %s", psz_filename, lua_tostring( p_state, lua_gettop( p_state ) ) );
-            return VLC_EGENERIC;
+            msg_Warn( p_demux, "Error loading script %s: %s", psz_filename,
+                      lua_tostring( p_state, lua_gettop( p_state ) ) );
+            lua_pop( p_state, 1 );
+            continue;
         }
 
-        if( lua_gettop( p_state ) )
-        {
-            i_ret = lua_toboolean( p_state, 1 );
-            printf( "Script returned %d: %d\n", 1, i_ret );
+        lua_getglobal( p_state, "probe" );
 
-            while( lua_gettop( p_state ) != 1 )
-            {
-                printf( "Script returned %d: %s\n", lua_gettop( p_state ),
-                        lua_tostring( p_state, lua_gettop( p_state ) ) );
-                lua_pop( p_state, 1 );
-            }
+        if( !lua_isfunction( p_state, lua_gettop( p_state ) ) )
+        {
+            msg_Warn( p_demux, "Error while runing script %s, "
+                      "function probe() not found", psz_filename );
+            lua_pop( p_state, 1 );
+            continue;
+        }
 
+        if( lua_pcall( p_state, 0, 1, 0 ) )
+        {
+            msg_Warn( p_demux, "Error while runing script %s, "
+                      "function probe(): %s", psz_filename,
+                      lua_tostring( p_state, lua_gettop( p_state ) ) );
             lua_pop( p_state, 1 );
+            continue;
+        }
 
-            if( !i_ret )
+        if( lua_gettop( p_state ) )
+        {
+            if( lua_toboolean( p_state, 1 ) )
             {
-                E_(Close_LuaPlaylist)( p_this );
-                return VLC_EGENERIC;
+                msg_Dbg( p_demux, "Lua playlist script %s's "
+                         "probe() function was successful", psz_filename );
+                i_ret = VLC_SUCCESS;
             }
+            lua_pop( p_state, 1 );
+
+            if( i_ret == VLC_SUCCESS ) break;
         }
-        else
+    }
+
+    error:
+        free( psz_filename );
+
+        if( ppsz_filelist )
         {
-            E_(Close_LuaPlaylist)( p_this );
-            return VLC_EGENERIC;
+            for( ppsz_file = ppsz_filelist; ppsz_file < ppsz_fileend;
+                 ppsz_file++ )
+                free( *ppsz_file );
+            free( ppsz_filelist );
         }
-    }
 
-    return VLC_SUCCESS;
+        if( dir ) closedir( dir );
+        if( i_ret != VLC_SUCCESS )
+            E_(Close_LuaPlaylist)( p_this );
+        return i_ret;
 }
 
 /*****************************************************************************
@@ -215,17 +286,91 @@ void E_(Close_LuaPlaylist)( vlc_object_t *p_this )
 
 static int Demux( demux_t *p_demux )
 {
-    /*input_item_t *p_input;*/
+    input_item_t *p_input;
+    lua_State *p_state = p_demux->p_sys->p_state;
+    char psz_filename[] = "FIXME";
 
     INIT_PLAYLIST_STUFF;
 
-#if 0
-    p_input = input_ItemNewExt( p_playlist, psz_url, psz_title, 0, NULL, -1 );
-    playlist_BothAddInput( p_playlist, p_input,
-                           p_item_in_category,
-                           PLAYLIST_APPEND | PLAYLIST_SPREPARSE,
-                           PLAYLIST_END, NULL, NULL, VLC_FALSE );
-#endif
+    lua_getglobal( p_state, "parse" );
+
+    if( !lua_isfunction( p_state, lua_gettop( p_state ) ) )
+    {
+        msg_Warn( p_demux, "Error while runing script %s, "
+                  "function parse() not found", psz_filename );
+        E_(Close_LuaPlaylist)( VLC_OBJECT( p_demux ) );
+        return VLC_EGENERIC;
+    }
+
+    if( lua_pcall( p_state, 0, 1, 0 ) )
+    {
+        msg_Warn( p_demux, "Error while runing script %s, "
+                  "function parse(): %s", psz_filename,
+                  lua_tostring( p_state, lua_gettop( p_state ) ) );
+        E_(Close_LuaPlaylist)( VLC_OBJECT( p_demux ) );
+        return VLC_EGENERIC;
+    }
+
+    if( lua_gettop( p_state ) )
+    {
+        int t = lua_gettop( p_state );
+        if( lua_istable( p_state, t ) )
+        {
+            lua_pushnil( p_state );
+            while( lua_next( p_state, t ) )
+            {
+                if( lua_istable( p_state, t+2 ) )
+                {
+                    const char *psz_url = NULL;
+                    const char *psz_title = NULL;
+                    lua_getfield( p_state, t+2, "url" );
+                    if( lua_isstring( p_state, t+3 ) )
+                    {
+                        psz_url = lua_tostring( p_state, t+3 );
+                        printf("URL: %s\n", psz_url );
+                        lua_getfield( p_state, t+2, "title" );
+                        if( lua_isstring( p_state, t+4 ) )
+                        {
+                            psz_title = lua_tostring( p_state, t+4 );
+                            printf("Title: %s\n", psz_title );
+                        }
+                        else
+                        {
+                            psz_title = psz_url;
+                        }
+                        p_input = input_ItemNewExt( p_playlist, psz_url,
+                                                    psz_title, 0, NULL, -1 );
+                        playlist_BothAddInput(
+                            p_playlist, p_input,
+                            p_item_in_category,
+                            PLAYLIST_APPEND | PLAYLIST_SPREPARSE,
+                            PLAYLIST_END, NULL, NULL, VLC_FALSE );
+                        lua_pop( p_state, 1 ); /* pop "title" */
+                    }
+                    else
+                    {
+                        printf("URL isn't a string\n");
+                    }
+                    lua_pop( p_state, 1 ); /* pop "url" */
+                }
+                else
+                {
+                    printf("This isn't a table !!!\n" );
+                }
+                lua_pop( p_state, 1 ); /* pop the value, keep the key for
+                                        * the next lua_next() call */
+            }
+            lua_pop( p_state, 1 ); /* pop the last key */
+        }
+        else
+        {
+            msg_Warn( p_demux, "Script didn't return a table" );
+        }
+    }
+    else
+    {
+        msg_Err( p_demux, "Script went completely foobar" );
+    }
 
     HANDLE_PLAY_AND_RELEASE;
 
index 31b257e81077e33397a33d6c84f788a4fd8ae19c..b5420069b930b9abb46bce4e7cea7646369f16d3 100644 (file)
@@ -234,4 +234,9 @@ DIST_osdmenu_dvd = \
        osdmenu/dvd/volume/volume05.png \
        osdmenu/dvd/unselect/barroff.png
 
-DIST_lua= luaplaylist/test.lua
+DIST_lua= \
+       luaplaylist/README \
+       luaplaylist/dailymotion.lua \
+       luaplaylist/youtube.lua \
+       luaplaylist/metacafe.lua \
+       luaplaylist/googlevideo.lua
diff --git a/share/luaplaylist/README.txt b/share/luaplaylist/README.txt
new file mode 100644 (file)
index 0000000..03d1072
--- /dev/null
@@ -0,0 +1 @@
+See "youtube.lua" for instructions to code your own Lua playlist script.
diff --git a/share/luaplaylist/dailymotion.lua b/share/luaplaylist/dailymotion.lua
new file mode 100644 (file)
index 0000000..8943f5f
--- /dev/null
@@ -0,0 +1,22 @@
+-- $Id$
+
+-- Probe function.
+function probe()
+    return vlc.access == "http"
+        and string.match( vlc.path, "dailymotion.com" ) 
+        and vlc.peek( 9 ) == "<!DOCTYPE"
+end
+
+-- Parse function.
+function parse()
+    while true
+    do 
+        line = vlc.readline()
+        if not line then break end
+        if string.match( line, "param name=\"flashvars\" value=\"url=" )
+        then
+            return { { url = vlc.decode_uri( string.gsub( line, "^.*param name=\"flashvars\" value=\"url=([^&]*).*$", "%1" ) ) } }
+        end
+    end
+    return {}
+end
diff --git a/share/luaplaylist/googlevideo.lua b/share/luaplaylist/googlevideo.lua
new file mode 100644 (file)
index 0000000..be0d81c
--- /dev/null
@@ -0,0 +1,13 @@
+-- $Id$
+
+-- Probe function.
+function probe()
+    return vlc.access == "http"
+        and string.match( vlc.path, "video.google.com" ) 
+        and string.match( vlc.path, "videoplay" )
+end
+
+-- Parse function.
+function parse()
+    return { { url = string.gsub( vlc.path, "^.*(docid=[^&]*).*$", "http://video.google.com/videogvp?%1" ) } }
+end
diff --git a/share/luaplaylist/metacafe.lua b/share/luaplaylist/metacafe.lua
new file mode 100644 (file)
index 0000000..8490d9b
--- /dev/null
@@ -0,0 +1,19 @@
+-- $Id$
+
+-- Probe function.
+function probe()
+    return vlc.access == "http"
+        and string.match( vlc.path, "metacafe.com" ) 
+        and (  string.match( vlc.path, "watch/" )
+            or string.match( vlc.path, "mediaURL=" ) )
+end
+
+-- Parse function.
+function parse()
+    if string.match( vlc.path, "watch/" )
+    then -- This is the HTML page's URL
+        return { { url = string.gsub( vlc.path, "^.*watch/(.*[^/])/?$", "http://www.metacafe.com/fplayer/%1.swf" ) } }
+    else -- This is the flash player's URL
+        return { { url = string.gsub( vlc.path, "^.*mediaURL=([^&]*).*$", "%1" ) } }
+    end
+end
diff --git a/share/luaplaylist/test.lua b/share/luaplaylist/test.lua
deleted file mode 100644 (file)
index 234f205..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
--- $Id$
-
-if vlc.access ~= "http"
-then
-    return false
-end 
-
-url = nil
-title = nil
-
-function get_url_param( url, name )
-    return string.gsub( vlc.path, "^.*"..name.."=([^&]*).*$", "%1" )
-end
-
-if string.match( vlc.path, "youtube.com" ) 
-then
-    if string.match( vlc.path, "watch%?v=" )
-    then
-        url = string.gsub( vlc.path, "^(.*)watch%?v=([^&]*).*$", "http://%1v/%2" )
-        while not title
-        do
-            line = vlc.readline()
-            if not line
-            then
-                break
-            end
-            if string.match( line, "<meta name=\"title\"" )
-            then
-                title = string.gsub( line, "^.*content=\"([^\"]*).*$", "%1" )
-            end
-        end
-    elseif string.match( vlc.path, "watch_fullscreen%?video_id=" ) or string.match( vlc.path, "p.swf" ) or string.match( vlc.path, "player2.swf" )
-    then
-        video_id = get_url_param( vlc.path, "video_id" )
-        t = get_url_param( vlc.path, "t" )
-        url = "http://www.youtube.com/get_video.php?video_id="..video_id.."&t="..t
-        if string.match( vlc.path, "title=" )
-        then
-            title = get_url_param( vlc.path, "title" )
-        end
-    end
-elseif string.match( vlc.path, "dailymotion.com" )
-then
-    len, str = vlc.peek( 9 )
-    if str == "<!DOCTYPE"
-    then
-        while not url
-        do
-            line = vlc.readline()
-            if string.match( line, "param name=\"flashvars\" value=\"url=" )
-            then
-                url = vlc.decode_uri( string.gsub( line, "^.*param name=\"flashvars\" value=\"url=([^&]*).*$", "%1" ) )
-            end
-        end
-    end
-elseif string.match( vlc.path, "video.google.com" ) and string.match( vlc.path, "videoplay" )
-then
-    url = string.gsub( vlc.path, "^.*(docid=[^&]*).*$", "http://video.google.com/videogvp?%1" )
-elseif string.match( vlc.path, "metacafe.com" )
-then
-    if string.match( vlc.path, "watch/" )
-    then
-        url = string.gsub( vlc.path, "^.*watch/(.*[^/])/?$", "http://www.metacafe.com/fplayer/%1.swf" )
-    elseif string.match( vlc.path, "mediaURL=" )
-    then
-        url = string.gsub( vlc.path, "^.*mediaURL=([^&]*).*$", "%1" )
-    end
-end
-
-if url == nil
-then
-    return false
-else
-    return true, url, title
-end
diff --git a/share/luaplaylist/youtube.lua b/share/luaplaylist/youtube.lua
new file mode 100644 (file)
index 0000000..5093e75
--- /dev/null
@@ -0,0 +1,66 @@
+-- $Id$
+
+--[[
+VLC Lua playlist modules should define two functions:
+ * probe(): returns true if we want to handle the playlist in this script
+ * parse(): read the incoming data and return playlist item(s)
+            The playlist is a table of playlist objects.
+            A playlist object has the following members:
+                .url: the item's full URL
+                .title: the item's title (OPTIONAL)
+            Invalid playlist items will be discarded by VLC.
+
+VLC defines a global vlc object with the following members:
+ * vlc.path: the URL string (without the leading http:// or file:// element)
+ * vlc.access: the access used ("http" for http://, "file" for file://, etc.)
+ * vlc.peek( <int> ): return the first <int> characters from the playlist file.
+ * vlc.readline(): return a new line of playlist data on each call.
+                   THIS FUNCTION SHOULD NOT BE USED IN peek().
+ * vlc.decode_uri( <string> ): decode %xy characters in a string.
+ * vlc.resolve_xml_special_chars( <string> ): decode &abc; characters in a string.
+
+Lua scripts are tried in alphabetical order in the luaplaylist/ directory.
+
+Lua documentation is available on http://www.lua.org .
+VLC uses Lua 5.1
+All the Lua standard libraries are available.
+--]]
+
+-- Helper function to get a parameter's value in a URL
+function get_url_param( url, name )
+    return string.gsub( vlc.path, "^.*"..name.."=([^&]*).*$", "%1" )
+end
+
+-- Probe function.
+function probe()
+    return vlc.access == "http"
+        and string.match( vlc.path, "youtube.com" ) 
+        and (  string.match( vlc.path, "watch%?v=" )
+            or string.match( vlc.path, "watch_fullscreen%?video_id=" )
+            or string.match( vlc.path, "p.swf" )
+            or string.match( vlc.path, "player2.swf" ) )
+end
+
+-- Parse function.
+function parse()
+    local p = {}
+    if string.match( vlc.path, "watch%?v=" )
+    then -- This is the HTML page's URL
+        p[1] = { url = string.gsub( vlc.path, "^(.*)watch%?v=([^&]*).*$", "http://%1v/%2" ) }
+        while true do
+            -- Try to find the video's title
+            line = vlc.readline()
+            if not line then break end
+            if string.match( line, "<meta name=\"title\"" ) then
+                p[1].title = string.gsub( line, "^.*content=\"([^\"]*).*$", "%1" )
+                break
+            end
+        end
+    else -- This is the flash player's URL
+        p[1] = { url = "http://www.youtube.com/get_video.php?video_id="..get_url_param( vlc.path, "video_id" ).."&t="..get_url_param( vlc.patch, "t" ) }
+        if string.match( vlc.path, "title=" ) then
+            p[1].title = get_url_param( vlc.path, "title" )
+        end
+    end
+    return p
+end