[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