--[[ Subtitles Copyright © 2009-2011 VideoLAN and AUTHORS Authors: ale5000 (Based on the script made by Jean-Philippe André) 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. --]] --[[ Notes - Because of internal changes in VLC, the minimum version of VLC required is now: 1.1.0 git - 2010-03-21 - VLC support only uncompressed .rar files Know issues - The search fail if you click the button two times consecutively Changelog 1.04 - Updated for the latest GIT build (now almost all lua bugs are fixed) - Bumped the minimum version required - Minor changes 1.03 - Fixed the duplicated names that were appearing in the search - Fixed the empty names that were appearing in the search - Now there is a message when it doesn't find anything - Minor optimizations - Minor fixes 1.02 - Fix for the latest GIT build 1.01 - Changed version schema - Now there isn't any dialog showed by default, you can still open them from the menu - Now it is more clear when the script is working - Added some dlg:flush() that were missing (this fix the elements in the dialog that weren't updated) - Now it catch the fail of vlc.stream - Minor fixes 0.1.8 - Now you can open a new dialog while the previous is still open - Fixed getting title in the new version of VLC - Now using dlg:flush() where appropriate - Workarounded some bugs in VLC IMPORTANT: Now it no longer work on old versions of VLC 0.1.7 - Removed the button "+"; it is useless since the title is updated automatically - The download dialog can now be hidden/showed - Added the "Load from url..." dialog - The code of parse_archive() is now a separate function called by both "Load from url..." and the normal subtitles download dialog. - Minor changes 0.1.6 - Some variables made local - Minor fixes - Added .rar archive support ("stream_filter_rar" is incomplete and can only use one file in the archive, need a fix in VLC) Note: VLC support only uncompressed .rar files ]] -- Global variables dlg = nil dialog_is_opened = false dialog_is_hidden = false update_title_needed = false -- -Common website = nil language = nil main_text_input = nil search_button = nil load_button = nil -- -Dialog "Download subtitles" subtitles_list = nil subtitles_result = nil -- -Dialog "Load subtitles from url..." type_text_input = nil function descriptor() return { title = "Subtitles"; version = "1.04"; author = "ale5000"; url = 'http://ale5000.altervista.org/vlc/extensions/subtitles-mod.lua'; description = "
Subtitles
" .. "Get the subtitles of movies from the internet, currently only from OpenSubtitles.org

" .. " Subtitles service allowed by www.OpenSubtitles.org
" .. "
(Based on the script made by Jean-Philippe André)"; shortdesc = "Get the subtitles of movies from the internet, currently only from OpenSubtitles.org"; capabilities = { "menu"; "input-listener"--[[; "meta-listener"]] } } end function menu() if not dialog_is_hidden then return { "Download", "Upload", "Load from url...", "About" } else return { "Show" } end end -- Function triggered when the extension is activated function activate() vlc.msg.dbg(_VERSION) vlc.msg.dbg("[Subtitles] Activating") trigger_menu(1) return true end -- Function triggered when the extension is deactivated function deactivate() if dialog_is_opened then close() else reset_variables() dlg = nil end vlc.msg.dbg("[Subtitles] Deactivated") return true end function reset_variables() update_title_needed = false -- Common website = nil language = nil main_text_input = nil search_button = nil load_button = nil -- Dialog "Download subtitles" subtitles_list = nil subtitles_result = nil -- Dialog "Load subtitles from url..." type_text_input = nil end -- Function triggered when the dialog is closed function close() dialog_is_opened = false dialog_is_hidden = false vlc.msg.dbg("[Subtitles] Closing dialog") reset_variables() if dlg ~= nil then dlg:delete() end dlg = nil return true end -- Current input changed function input_changed() vlc.msg.dbg("[Subtitles] Input is changed") update_title() end -- Current input item meta changed --[[function meta_changed() end]] function update_title() if dialog_is_hidden or not update_title_needed then return true end vlc.msg.dbg("[Subtitles] Updating title") local item = vlc.input.item() if item == nil then return false end local title = item:name() -- It return the internal title or the filename if the first is missing if title ~= nil then title = string.gsub(title, "(.*)%.%w+$", "%1") -- Removes file extension if title ~= "" then main_text_input:set_text(title) dlg:update() return true end end return false end function show_dialog_download() -- column, row, colspan, rowspan dlg:add_label("Database: ", 1, 1, 1, 1) website = dlg:add_dropdown(2, 1, 3, 1) dlg:add_label("Language: ", 1, 2, 1, 1) language = dlg:add_dropdown(2, 2, 3, 1) dlg:add_label("Search: ", 1, 3, 1, 1) main_text_input = dlg:add_text_input("", 2, 3, 1, 1) search_button = dlg:add_button("Search", click_search, 3, 3, 1, 1) dlg:add_button("Hide", hide_dialog, 4, 3, 1, 1) for idx, ws in ipairs(websites) do website:add_value(ws.title, idx) end for idx, ws in ipairs(languages) do language:add_value(ws.title, idx) end update_title_needed = true update_title() dlg:update() return true end function show_dialog_upload() -- column, row, colspan, rowspan dlg:add_label("Database: ", 1, 1, 1, 1) website = dlg:add_dropdown(2, 1, 4, 1) -- FIXME: Link is NOT yet working dlg:add_label("
Upload to OpenSubtitles.org
", 1, 2, 2, 1) for idx, ws in ipairs(websites) do website:add_value(ws.title, idx) end dlg:update() return true end function show_dialog_load_url() -- column, row, colspan, rowspan dlg:add_label("URL: ", 1, 1, 1, 1) main_text_input = dlg:add_text_input("", 2, 1, 3, 1) dlg:add_label("Type: ", 1, 2, 1, 1) type_text_input = dlg:add_text_input("", 2, 2, 1, 1) dlg:add_label("        ", 3, 2, 1, 1) -- Spacer load_button = dlg:add_button("Load", click_load_from_url_button, 4, 2, 1, 1) dlg:update() return true end function show_dialog_about() local data = descriptor() -- column, row, colspan, rowspan dlg:add_label("
" .. data.title .. " " .. data.version .. "
", 1, 1, 1, 1) dlg:add_html(data.description, 1, 2, 1, 1) dlg:update() return true end function sleep(sec) local t = vlc.misc.mdate() vlc.misc.mwait(t + sec*1000*1000) end function new_dialog(title) dlg = vlc.dialog(title) end function hide_dialog() dialog_is_hidden = true dlg:hide() end -- Function triggered when a element from the menu is selected function trigger_menu(id) if dialog_is_hidden then if dlg == nil then vlc.msg.err("[Subtitles] Dialog pointer lost") close() return false; end dialog_is_hidden = false dlg:show() return true end if(dialog_is_opened) then close() end dialog_is_opened = true if id == 1 then new_dialog("Download subtitles") return show_dialog_download() elseif id == 2 then new_dialog("Upload subtitles") return show_dialog_upload() elseif id == 3 then new_dialog("Load subtitles from url...") return show_dialog_load_url() elseif id == 4 then new_dialog("About") return show_dialog_about() end vlc.msg.err("[Subtitles] Invalid menu id: "..id) return false end function click_search() vlc.msg.dbg("[Subtitles] Clicked search button from \"Download subtitles\" dialog") local search_term = main_text_input:get_text() if(search_term == "") then return false end local old_button_name = search_button:get_text() search_button:set_text("Wait...") if subtitles_list ~= nil then subtitles_list:clear() end dlg:update() subtitles_result = nil local idx = website:get_value() local idx2 = language:get_value() if idx < 1 or idx2 < 1 then vlc.msg.err("[Subtitles] Invalid index in dropdown") search_button:set_text(old_button_name) return false end local ws = websites[idx] local lang = languages[idx2] local url = ws.urlfunc(search_term) -- vlc.msg.info("[Subtitles] Url: '" .. url .. "'") local stream = vlc.stream(url) if stream == nil then vlc.msg.err("[Subtitles] The site of subtitles isn't reachable") search_button:set_text(old_button_name) return false end local reading = "blah" local xmlpage = "" while(reading ~= nil and reading ~= "") do reading = stream:read(65653) if(reading) then xmlpage = xmlpage .. reading end end if xmlpage == "" then search_button:set_text(old_button_name) return false end subtitles_result = ws.parsefunc(xmlpage) if subtitles_list == nil then subtitles_list = dlg:add_list(1, 4, 4, 1) load_button = dlg:add_button("Load selected subtitles", click_load_from_search_button, 1, 5, 4, 1) end if not subtitles_result then subtitles_result = {} subtitles_result[1]= { url = "-1" } subtitles_list:add_value("Nothing found", 1) search_button:set_text(old_button_name) dlg:update() return false end for idx, res in ipairs(subtitles_result) do if(not res.language or lang.tag == "all" or lang.tag == res.language) then subtitles_list:add_value("["..res.language.."] "..res.name, idx) end end search_button:set_text(old_button_name) dlg:update() return true end function load_unknown_subtitles(url, language) vlc.msg.dbg("[Subtitles] Loading "..language.." subtitle: "..url) vlc.input.add_subtitle(url) end function load_subtitles_in_the_archive(dataBuffer, language) local buffer_length = dataBuffer:len() local files_found_in_the_compressed_file = 0 local subtitles_found_in_the_compressed_file = 0 local endIdx = 1 local srturl, extension -- Find subtitles while(endIdx < buffer_length) do _, endIdx, srturl, extension = dataBuffer:find("([^<]+)%.(%a%a%a?)", endIdx) if(srturl == nil ) then break end --vlc.msg.dbg("[Subtitles] File found in the archive: " .. srturl .. extension) files_found_in_the_compressed_file = files_found_in_the_compressed_file + 1 srturl = string.gsub(srturl, "^(%a%a%a)://", "%1://http://") if(extension == "ass" or extension == "ssa" or extension == "srt" or extension == "smi" or extension == "sub" or extension == "rt" or extension == "txt" or extension == "mpl") then subtitles_found_in_the_compressed_file = subtitles_found_in_the_compressed_file + 1 vlc.msg.dbg("[Subtitles] Loading "..language.." subtitle: "..srturl) vlc.input.add_subtitle(srturl.."."..extension) end end vlc.msg.info("[Subtitles] Files found in the compressed file: "..files_found_in_the_compressed_file) vlc.msg.info("[Subtitles] Subtitles found in the compressed file: "..subtitles_found_in_the_compressed_file) if(subtitles_found_in_the_compressed_file > 0) then return true end vlc.msg.warn("[Subtitles] No subtitles found in the compressed file") return false end function parse_archive(url, language) if url == "-1" then vlc.msg.dbg("[Subtitles] Dummy result") return true end local stream = vlc.stream(url) if stream == nil then vlc.msg.err("[Subtitles] The site of subtitles isn't reachable") return false end stream:addfilter("zip,stream_filter_rar") local data = stream:read(2048) if(data == nil or data:find("", i) 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 table.insert(top, {label=label, xarg=parseargs(xarg), empty=1}) elseif c == "" then -- start tag top = {label=label, xarg=parseargs(xarg)} 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 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], text) end if #stack > 1 then error("unclosed "..stack[stack.n].label) end return stack[1] end --[[ Websites configurations ]]-- -- OpenSubtitles.org -- This function uses first version of OS API. It will probably fail in -- the future. We'll need a XML-RPC key btw function urlOpenSub(search_term) -- base = "http://api.opensubtitles.org/xml-rpc" -- lang = "eng" base = "http://api.opensubtitles.org/en/search/" -- if(lang) then base = base .. "sublanguageid-" .. lang .. "/" search_term = string.gsub(search_term, "%%", "%%37") search_term = string.gsub(search_term, " ", "%%20") return base .. "moviename-" .. search_term .. "/simplexml" -- http://api.opensubtitles.org/en/search/moviename- .. search_term .. /simplexml end function parseOpenSub(xmltext) vlc.msg.dbg("[Subtitles] Parsing XML data...") local xmltext = string.gsub(xmltext, "<%?xml version=\"1%.0\" encoding=\"utf-8\"%?>", "") local xmldata = collect(xmltext) for a,b in pairs(xmldata) do if type(b) == "table" then if b.label == "search" then xmldata = b break end end end if xmldata == nil then return nil end -- Subtitles information data local subname = {} local sub_movie = {} local suburl = {} local sublang = {} local sub_language = {} 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 elseif f.label == "movie" then if f[1] ~= nil then sub_movie[subnum] = f[1] else sub_movie[subnum] = "" end elseif f.label == "download" then if f[1] ~= nil then suburl[subnum] = f[1] else suburl[subnum] = "" end elseif f.label == "iso639" then -- language if f[1] ~= nil then sublang[subnum] = f[1] else sublang[subnum] = "" end elseif f.label == "language" then -- language -- not use yet if f[1] ~= nil then sub_language[subnum] = f[1] else sub_language[subnum] = "" end elseif f.label == "format" then if f[1] ~= nil then subformat[subnum] = f[1] else subformat[subnum] = "" end end end end subnum = subnum + 1 end end end elseif b.label == "base" then baseurl = b[1] end end end if subnum <= 1 then return nil end ret = {} for i = 1,(subnum - 1) do fullURL = baseurl .. "/" .. suburl[i] realName = string.gsub( subname[i], "<..CDATA.", "" ) realName = string.gsub( realName, "..>", "" ) if realName == "" then realName = string.gsub( sub_movie[i], "<..CDATA.", "" ) realName = string.gsub( realName, "..>", "" ) end ret[i] = { name = realName, url = fullURL, language = sublang[i], extension = ".zip" } vlc.msg.dbg("[Subtitles] Found subtitle " .. i .. ": ") vlc.msg.dbg(realName) vlc.msg.dbg(fullURL) end return ret end -- These tables must be after all function definitions websites = { { title = "OpenSubtitles.org", urlfunc = urlOpenSub, parsefunc = parseOpenSub } --[[; { title = "Fake (OS)", urlfunc = url2, parsefunc = parse2 }]] } languages = { { title = "All", tag = "all" }, { title = "English", tag = "en" }, { title = "Chinese", tag = "zh" }, { title = "Finnish", tag = "fi" }, { title = "French", tag = "fr" }, { title = "German", tag = "de" }, { title = "Italian", tag = "it" }, { title = "Japanese", tag = "ja" }, { title = "Polish", tag = "pl" }, { title = "Portuguese", tag = "pt" }, { title = "Russian", tag = "ru" }, { title = "Spanish", tag = "es" } }