[vlc-devel] [PATCH] Download subtitles using OpenSubtitles.Org

Pierre d'Herbemont pdherbemont at free.fr
Wed Jan 16 01:50:39 CET 2008


Woaw, That's a killer feature!

Congrat :)

Pierre.

On Jan 15, 2008, at 11:00 PM, Jean-Philippe André wrote:

> Hello,
>
> I've started writing a patch for this trac entry :
> http://trac.videolan.org/vlc/ticket/899
> * This feature allows users download/upload subtitles directly from  
> VLC.
>
> It is not finished yet (really not !), but we have now a simple Qt
> interface that calls a C module which call a lua script. The lua  
> script
> has to fetch a list of subtitles available for a movie name.
>
> There are still some bugs and things left to do, but it should work  
> fine.
>
> In the next patch, it will be possible to choose different lua scripts
> (meaning different websites), and languages.
>
> Screenshot :
> http://people.via.ecp.fr/~jpeg/VideoLAN/capture5.png
>
> Patch is available on :
> http://people.via.ecp.fr/~jpeg/VideoLAN/patch1.diff
>
> All comments welcome.
>
> Best Regards,
>
> -- 
> Jean-Philippe André
> JPeg
> Index: share/luasubtitles/opensubtitles.lua
> ===================================================================
> --- share/luasubtitles/opensubtitles.lua	(révision 0)
> +++ share/luasubtitles/opensubtitles.lua	(révision 0)
> @@ -0,0 +1,164 @@
> +-- Author: Jean-Philippe ANDRE <jpeg CHEZ via POINT ecp POINT fr>
> +-- TODO: proprifier le code, corriger l'anglais,
> +-- faire quelque chose de chouette, quoi
> +
> +
> +-- These two first functions are designed to parse an XML file
> +function parseargs(s)
> +    local arg = {}
> +    string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a)
> +    arg[w] = a
> +    end)
> +    return arg
> +end
> +
> +function collect(s)
> +    local stack = {}
> +    local top = {}
> +    table.insert(stack, top)
> +    local ni,c,label,xarg, empty
> +    local i, j = 1, 1
> +
> +    while true do
> +        ni,j,c,label,xarg, empty = string.find(s, "<(%/?)(%w+)(.-) 
> (%/?)>", j)
> +        if not ni then break end
> +        local text = string.sub(s, i, ni-1)
> +        if not string.find(text, "^%s*$") then
> +            table.insert(top, text)
> +        end
> +        if empty == "/" then  -- empty element tag
> +    --         print( label )
> +            table.insert(top, {label=label, xarg=parseargs(xarg),  
> empty=1})
> +        elseif c == "" then   -- start tag
> +            top = {label=label, xarg=parseargs(xarg)}
> +    --         print( top.label )
> +            table.insert(stack, top)   -- new level
> +        else  -- end tag
> +            local toclose = table.remove(stack)  -- remove top
> +            top = stack[#stack]
> +            if #stack < 1 then
> +            error("nothing to close with "..label)
> +            end
> +            if toclose.label ~= label then
> +            error("trying to close "..toclose.label.." with "..label)
> +            end
> +    --         print( toclose.label )
> +            table.insert(top, toclose)
> +        end
> +        i = j+1
> +    end
> +
> +    local text = string.sub(s, i)
> +    if not string.find(text, "^%s*$") then
> +    table.insert(stack[stack.n], text)
> +    end
> +    if #stack > 1 then
> +    error("unclosed "..stack[stack.n].label)
> +    end
> +    return stack[1]
> +end
> +-- Return the artwork
> +function fetch_sublist()
> +    if vlc.title then
> +        title = vlc.title
> +    else
> +        return nil
> +    end
> +
> +    -- Look on OpenSubtitles.Org
> +    -- http://www.opensubtitles.com/en/search/sublanguageid-eng/moviename-the%20wall/simplexml
> +    lang = "fre"
> +    if vlc.language then
> +        lang = vlc.language
> +    end
> +
> +    xmlurl = "http://www.opensubtitles.org/en/search/ 
> sublanguageid-" .. lang .. "/moviename-" .. title .. "/simplexml"
> +--    print( "Opening " .. xmlurl )
> +    fd = vlc.stream_new( xmlurl )
> +    xmlpage = vlc.stream_read( fd, 65653 )
> +    vlc.stream_delete( fd )
> +--    print( "We received :" )
> +--    print( xmlpage )
> +    if xmlpage == nil then return nil end
> +
> +--     local fd = io.open( "/home/videolan/vlc/trunk/share/luameta/ 
> example.xml" )
> +--     local xmlpage = fd:read( "*a" )
> +--     io.close( fd )
> +
> +    local xmldata = collect( xmlpage )
> +    for a,b in pairs(xmldata) do
> +        if type(b) == "table" then
> +            if b.label == "search" then
> +                xmldata = b
> +            end
> +        end
> +    end
> +
> +    if xmldata == nil then return nil end
> +
> +    -- Subtitles information data
> +    local subname = {}
> +    local suburl = {}
> +    local sublang = {}
> +    local subformat = {}
> +    local subfilenum = {}
> +    local subnum = 1
> +    local baseurl = ""
> +
> +    -- Let's browse iteratively the 'xmldata' tree
> +    -- OK, the variables' names aren't explicit enough, but just  
> remember a couple
> +    -- a,b contains the index (a) and the data (b) of the table,  
> which might also be a table
> +    for a,b in pairs(xmldata) do
> +        if type(b) == "table" then
> +            if b.label == "results" then
> +                for c,d in pairs(b) do
> +                    if type(d) == "table" then
> +                        if d.label == "subtitle" then
> +                            for e,f in pairs(d) do
> +                                if type(f) == "table" then
> +                                    if f.label == "releasename" then
> +                                        if f[1] ~= nil then  
> subname[subnum] = f[1]
> +                                        else subname[subnum] = "" end
> +                                    else if f.label == "download"  
> then
> +                                        if f[1] ~= nil then  
> suburl[subnum] = f[1]
> +                                        else suburl[subnum] = "" end
> +                                    else if f.label == "language"  
> then
> +                                        if f[1] ~= nil then  
> sublang[subnum] = f[1]
> +                                        else sublang[subnum] = "" end
> +                                    else if f.label == "files" then
> +                                        if f[1] ~= nil then  
> subfilenum[subnum] = f[1]
> +                                        else subfilenum[subnum] =  
> "" end
> +                                    else if f.label == "format" then
> +                                        if f[1] ~= nil then  
> subformat[subnum] = f[1]
> +                                        else subformat[subnum] = ""  
> end
> +                                    end end end end end
> +                                end
> +                            end
> +                            subnum = subnum + 1
> +                        end
> +                    end
> +                end
> +            else if b.label == "base" then
> +                baseurl = b[1]
> +            end end
> +        end
> +    end
> +
> +    if subnum <= 0 then
> +        return nil
> +    end
> +
> +    -- Let's return a TAB-separated string containing interesting  
> data
> +    -- Format : Name, Full URL, Language, File number (#CDs)
> +    -- Subtitles File Format: sub, srt... -- unused
> +    ret = subnum .. "\n"
> +
> +    for i = 1,(subnum - 1) do
> +        fullURL = baseurl .. "/" .. suburl[i]
> +        realName = string.gsub( subname[i], "..CDATA.", "" )
> +        realName = string.gsub( realName, "..>", "" )
> +        ret = ret .. realName .. "\t" .. fullURL .. "\t" ..  
> sublang[i] .. "\t" .. subfilenum[i] .. "\n"
> +    end
> +
> +    return ret
> +end
> Index: modules/gui/qt4/dialogs/help.cpp
> ===================================================================
> --- modules/gui/qt4/dialogs/help.cpp	(révision 24316)
> +++ modules/gui/qt4/dialogs/help.cpp	(copie de travail)
> @@ -6,6 +6,7 @@
>  *
>  * Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
>  *          Rémi Duraffort <ivoire (at) via.ecp.fr>
> + *          Jean-Philippe André <jpeg CHEZ via POINT ecp POINT fr>
>  *
>  * 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
> @@ -293,3 +294,207 @@
>
>
> #endif
> +
> +
> +#ifdef DOWNLOAD_SUBTITLES
> +/* callback to get information from the core */
> +static void SubDownloadCallback( void *data )
> +{
> +    SubDownloadDialog* SDDialog = (SubDownloadDialog *)data;
> +    QEvent *event = new QEvent( QEvent::User );
> +    QApplication::postEvent( SDDialog, event );
> +}
> +
> +SubDownloadDialog *SubDownloadDialog::instance = NULL;
> +
> +SubDownloadDialog::SubDownloadDialog( intf_thread_t *_p_intf ) :  
> QVLCFrame( _p_intf )
> +{
> +    setWindowTitle( qtr( "Download Subtitles" ) );
> +    memset( &sublist, 0, sizeof( subtitles_list_t) );
> +
> +    QGridLayout *layout = new QGridLayout( this );
> +
> +    QLabel *movieNameLabel = new QLabel( this );
> +    movieNameLabel->setText( qtr( "Movie's name" ) );
> +    layout->addWidget( movieNameLabel, 0, 0 );
> +
> +    movieNameEdit = new QLineEdit( this );
> +    movieNameEdit->setText( getCurrentTitle() );
> +    layout->addWidget( movieNameEdit, 0, 1, 1, 2 );
> +
> +    QBoxLayout *optionsLayout = new  
> QBoxLayout( QBoxLayout::LeftToRight );
> +
> +    QLabel *languageLabel = new QLabel( this );
> +    languageLabel->setText( qtr( "Language" ) );
> +    optionsLayout->addWidget( languageLabel );
> +
> +    languageCombo = new QComboBox( this );
> +//     languageCombo->setEditable( true );
> +    optionsLayout->addWidget( languageCombo );
> +
> +//     serverCombo = new QComboBox( this );
> +//     serverCombo->setEditable( true );
> +//     optionsLayout->addWidget( serverCombo );
> +
> +    layout->addLayout( optionsLayout, 1, 0, 1, 3 );
> +
> +
> +    subtitlesList = new QTreeWidget( this );
> +    subtitlesList->setRootIsDecorated( false );
> +    subtitlesList->setAlternatingRowColors( true );
> +    subtitlesList- 
> >setSelectionMode( QAbstractItemView::ExtendedSelection );
> +    subtitlesList- 
> >setSelectionBehavior( QAbstractItemView::SelectRows );
> +    subtitlesList- 
> >setEditTriggers( QAbstractItemView::SelectedClicked );
> +    subtitlesList->setColumnCount( 3 );
> +    {
> +        QStringList headerLabels;
> +        headerLabels << qtr( "Language" );
> +        headerLabels << qtr( "Title" );
> +        headerLabels << qtr( "URL" );
> +        subtitlesList->setHeaderLabels( headerLabels );
> +    }
> +    layout->addWidget ( subtitlesList, 2, 0, 1, 3 );
> +
> +    fetchListButton = new QPushButton( qtr( "&Fetch List" ) );
> +    layout->addWidget( fetchListButton, 3, 0 );
> +
> +    downloadButton = new QPushButton( qtr( "&Download File" ) );
> +    downloadButton->setEnabled( false ); /* TODO */
> +    layout->addWidget( downloadButton, 3, 1 );
> +
> +    closeButton = new QPushButton( qtr( "&Close" ) );
> +    layout->addWidget( closeButton, 3, 2 );
> +
> +    BUTTONACT( closeButton, close() );
> +    BUTTONACT( downloadButton, downloadFile() );
> +    BUTTONACT( fetchListButton, fetchList() );
> +
> +    /* FIXME: pushing Enter closes the dialogbox */
> +//     CONNECT( movieNameEdit, SIGNAL(returnPressed()),
> +//              this, SLOT(fetchList()) );
> +
> +    fillComboBoxes();
> +}
> +
> +SubDownloadDialog::~SubDownloadDialog()
> +{
> +    /* TODO: free memory in struct subtitles_list_ */
> +}
> +
> +void SubDownloadDialog::close()
> +{
> +    hide();
> +}
> +
> +void SubDownloadDialog::fillComboBoxes()
> +{
> +    /* Now we have to fill the comboboxes with Ids of languages and  
> servers */
> +    /* TODO */
> +
> +//     serverCombo->addItem( "OpenSubtitles.Org",  
> "opensubtitles.lua" );
> +
> +    languageCombo->addItem( qtr( "French" ), "FR" );
> +    languageCombo->addItem( qtr( "English" ), "EN" );
> +    languageCombo->addItem( qtr( "German" ), "DE" );
> +    /* And so on... not hard-coded would maybe be better ? */
> +}
> +
> +void SubDownloadDialog::fetchList()
> +{
> +//     listURLString += serverCombo->itemData( serverCombo- 
> >currentIndex() ).toString();
> +    /** TODO threader tout ce bordel */
> +
> +    int i_ret;
> +    module_t *p_module;
> +
> +    p_intf->p_private = &sublist;
> +    QString qs_Title = movieNameEdit->text();
> +    char * psz_title;
> +    asprintf( &psz_title, "%s", qPrintable( qs_Title ) );
> +    sublist.psz_title = psz_title;
> +
> +    p_module = module_Need( p_intf, "subtitles finder", 0,  
> VLC_FALSE );
> +
> +//     free( psz_title );
> +
> +    if( p_module )
> +    {
> +        msg_Dbg( p_intf, "found subtitles finder" );
> +        i_ret = 1;
> +    }
> +    else
> +        msg_Dbg( p_intf, "unable to load module" );
> +
> +    if ( sublist.ppsz_subtitles_urls )
> +    {
> +        int i = 0;
> +        for ( ; i < sublist.count; i++ )
> +        {
> +            QStringList row;
> +            row << QString( sublist.ppsz_subtitles_langs[i] );
> +            row << QString( sublist.ppsz_subtitles_names[i] );
> +            row << QString( sublist.ppsz_subtitles_urls[i] );
> +            QTreeWidgetItem *item = new  
> QTreeWidgetItem( subtitlesList, row );
> +            item->setFlags( Qt::ItemIsSelectable |  
> Qt::ItemIsEditable |
> +                            Qt::ItemIsUserCheckable |  
> Qt::ItemIsEnabled);
> +            subtitlesList->insertTopLevelItem( i, item );
> +        }
> +    }
> +
> +    if( p_module )
> +        module_Unneed( p_intf, p_module );
> +}
> +
> +void SubDownloadDialog::downloadFile()
> +{
> +    /* TODO */
> +     QMessageBox::warning( this, qtr( "S.I.D." ), qtr( "This  
> feature is not yet implemented !" ) );
> +}
> +
> +void SubDownloadDialog::customEvent( QEvent * event )
> +{
> +    updateStatus();
> +    event->accept();
> +}
> +
> +int SubDownloadDialog::updateStatus()
> +{
> +     QMessageBox::warning( this, qtr( "Not implemented yet !" ),  
> "caca" );
> +}
> +
> +QString SubDownloadDialog::getCurrentTitle()
> +{
> +    QString qsTitle;
> +    char * psz_name;
> +
> +    playlist_t *p_playlist = pl_Get( p_intf );
> +    playlist_item_t *p_item = ( p_playlist ? p_playlist- 
> >status.p_item : NULL );
> +    input_item_t *p_input = (p_item ? p_item->p_input : NULL);
> +    if( !p_input )
> +    {
> +        if ( p_playlist->items.i_size )
> +        {
> +            p_item = p_playlist->items.p_elems[0];
> +            if ( !p_item )
> +                return QString( "" );
> +            else
> +                p_input = p_item->p_input;
> +        }
> +    }
> +    if ( !p_input )
> +                return QString( "" );
> +
> +    psz_name = input_item_GetName( p_input ) ;
> +    qsTitle = QString( psz_name );
> +    free( psz_name );
> +
> +    return qsTitle;
> +}
> +
> +void SubDownloadDialog::showEvent( QShowEvent * event )
> +{
> +    /* Reactualize movie's name when showing the dialog */
> +    movieNameEdit->setText( getCurrentTitle() );
> +}
> +
> +#endif
> Index: modules/gui/qt4/dialogs/help.hpp
> ===================================================================
> --- modules/gui/qt4/dialogs/help.hpp	(révision 24316)
> +++ modules/gui/qt4/dialogs/help.hpp	(copie de travail)
> @@ -28,6 +28,15 @@
>
> #include "util/qvlcframe.hpp"
>
> +#define DOWNLOAD_SUBTITLES tamere
> +#ifdef DOWNLOAD_SUBTITLES
> +#include <QLineEdit>
> +#include <QTreeWidget>
> +#include <QComboBox>
> +#include <QMessageBox>
> +#include <../modules/misc/lua/vlc.h> /** FIXME */
> +#endif
> +
> class QPushButton;
> class QTextBrowser;
> class QLabel;
> @@ -106,4 +115,39 @@
> };
> #endif
>
> +#ifdef DOWNLOAD_SUBTITLES
> +class SubDownloadDialog : public QVLCFrame
> +{
> +    Q_OBJECT;
> +public:
> +    static SubDownloadDialog * getInstance( intf_thread_t *p_intf )
> +    {
> +        if( !instance )
> +            instance = new SubDownloadDialog( p_intf );
> +        return instance;
> +    }
> +    virtual ~SubDownloadDialog();
> +
> +private:
> +    SubDownloadDialog( intf_thread_t * );
> +    static SubDownloadDialog *instance;
> +    QComboBox *languageCombo; //, *serverCombo;
> +    QTreeWidget *subtitlesList;
> +    QPushButton *downloadButton, *fetchListButton, *closeButton;
> +    QLineEdit *movieNameEdit;
> +    void fillComboBoxes();
> +    int updateStatus();
> +    subtitles_list_t sublist;
> +    QString getCurrentTitle();
> +    /* Event handling */
> +    void customEvent( QEvent * );
> +    void showEvent( QShowEvent * );
> +
> +private slots:
> +    void close();
> +    void fetchList();
> +    void downloadFile();
> +};
> #endif
> +
> +#endif
> Index: modules/gui/qt4/dialogs_provider.hpp
> ===================================================================
> --- modules/gui/qt4/dialogs_provider.hpp	(révision 24316)
> +++ modules/gui/qt4/dialogs_provider.hpp	(copie de travail)
> @@ -154,6 +154,12 @@
> #ifdef UPDATE_CHECK
>     void updateDialog();
> #endif
> +
> +#define DOWNLOAD_SUBTITLES tamere
> +#ifdef DOWNLOAD_SUBTITLES
> +    void subDownloadDialog();
> +#endif
> +
>     void aboutDialog();
>     void gotoTimeDialog();
>     void podcastConfigureDialog();
> Index: modules/gui/qt4/menus.cpp
> ===================================================================
> --- modules/gui/qt4/menus.cpp	(révision 24316)
> +++ modules/gui/qt4/menus.cpp	(copie de travail)
> @@ -464,6 +464,10 @@
> #ifdef UPDATE_CHECK
>     addDPStaticEntry( menu, qtr( "Update" ) , "", "",  
> SLOT( updateDialog() ), "");
> #endif
> +#define DOWNLOAD_SUBTITLES tamere
> +#ifdef DOWNLOAD_SUBTITLES
> +    addDPStaticEntry( menu, qtr( "Download Subtitles" ) , "", "",  
> SLOT( subDownloadDialog() ), "");
> +#endif
>     menu->addSeparator();
>     addDPStaticEntry( menu, qtr( I_MENU_ABOUT ), "", "",  
> SLOT( aboutDialog() ),
>         "Ctrl+F1" );
> Index: modules/gui/qt4/dialogs_provider.cpp
> ===================================================================
> --- modules/gui/qt4/dialogs_provider.cpp	(révision 24316)
> +++ modules/gui/qt4/dialogs_provider.cpp	(copie de travail)
> @@ -188,6 +188,13 @@
> }
> #endif
>
> +#ifdef DOWNLOAD_SUBTITLES
> +void DialogsProvider::subDownloadDialog()
> +{
> +    SubDownloadDialog::getInstance( p_intf )->toggleVisible();
> +}
> +#endif
> +
> void DialogsProvider::aboutDialog()
> {
>     AboutDialog::getInstance( p_intf )->toggleVisible();
> Index: modules/misc/lua/Modules.am
> ===================================================================
> --- modules/misc/lua/Modules.am	(révision 24316)
> +++ modules/misc/lua/Modules.am	(copie de travail)
> @@ -1 +1 @@
> -SOURCES_lua = playlist.c meta.c intf.c vlc.c vlc.h callbacks.c  
> objects.c variables.c configuration.c net.c vlm.c httpd.c acl.c sd.c
> +SOURCES_lua = playlist.c meta.c intf.c vlc.c vlc.h callbacks.c  
> objects.c variables.c configuration.c net.c vlm.c httpd.c acl.c sd.c  
> subtitles.c
> Index: modules/misc/lua/vlc.c
> ===================================================================
> --- modules/misc/lua/vlc.c	(révision 24316)
> +++ modules/misc/lua/vlc.c	(copie de travail)
> @@ -87,6 +87,12 @@
>         add_string( "lua-config", "", NULL,
>                     CONFIG_TEXT, CONFIG_LONGTEXT, VLC_FALSE );
>         set_callbacks( E_(Open_LuaIntf), E_(Close_LuaIntf) );
> +    add_submodule();
> +        add_shortcut( "luasubtitles" );
> +        set_shortname( N_( "Lua Subtitles" ) );
> +        set_description( _("Fetch subtitles list using lua  
> scripts") );
> +        set_capability( "subtitles finder", 10 );
> +        set_callbacks( E_(FindSubtitles), NULL );
> vlc_module_end();
>
> / 
> *****************************************************************************
> Index: modules/misc/lua/vlc.h
> ===================================================================
> --- modules/misc/lua/vlc.h	(révision 24316)
> +++ modules/misc/lua/vlc.h	(copie de travail)
> @@ -48,6 +48,31 @@
> #include <lauxlib.h>    /* Higher level C API */
> #include <lualib.h>     /* Lua libs */
>
> +#ifndef DOWNLOAD_SUBTITLES
> +# define DOWNLOAD_SUBTITLES prout
> +#endif
> +#ifdef DOWNLOAD_SUBTITLES
> +struct subtitles_list_t {
> +//     VLC_COMMON_MEMBERS
> +    /* Input values */
> +//     playlist_t *p_playlist;
> +    char *psz_scriptname;
> +    char *psz_title; /* Unused yet */
> +    /* Output values */
> +    int count; /* Number of subtitles found */
> +    char **ppsz_subtitles_names; /* Subtitles names */
> +    char **ppsz_subtitles_langs; /* language, maybe we could use  
> int ID values ? */
> +    char **ppsz_subtitles_urls; /* URL */
> +    int *pi_filenum; /* CDs number */
> +    /** We could use ARRAYs instead of this shit */
> +    /* TYPEDEF_ARRAY ( char_list, char * ) ... */
> +};
> +typedef struct subtitles_list_t subtitles_list_t;
> +#define SUB_NAME_MAX_LEN 100
> +#define SUB_URL_MAX_LEN 500
> +#define SUB_LANG_MAX_LEN 20
> +#endif
> +
> / 
> *****************************************************************************
>  * Module entry points
>   
> *****************************************************************************/
> @@ -60,6 +85,7 @@
> int E_(Open_LuaIntf)( vlc_object_t * );
> void E_(Close_LuaIntf)( vlc_object_t * );
>
> +int E_(FindSubtitles)( vlc_object_t * );
>
> / 
> *****************************************************************************
>  * Lua debug
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> http://mailman.videolan.org/listinfo/vlc-devel




More information about the vlc-devel mailing list