Last active
April 3, 2023 18:55
-
-
Save andersonvom/4950960 to your computer and use it in GitHub Desktop.
Lua script for VLC to download subtitles from OpenSubtitles.org
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
VLSub Extension for VLC media player 1.1 and 2.0 | |
Copyright 2010 Guillaume Le Maout | |
Authors: Guillaume Le Maout | |
Contact: http://addons.videolan.org/messages/?action=newmessage&username=exebetche | |
Bug report: http://addons.videolan.org/content/show.php/?content=148752 | |
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. | |
--]] | |
-- Extension description | |
function descriptor() | |
return { title = "VLsub" ; | |
version = "0.6" ; | |
author = "exebetche" ; | |
url = 'http://www.opensubtitles.org/'; | |
shortdesc = "VLsub"; | |
description = "<center><b>VLsub</b></center>" | |
.. "Dowload subtitles from OpenSubtitles.org" ; | |
capabilities = { "input-listener", "meta-listener" } | |
} | |
end | |
-- Global variables | |
dlg = nil -- Dialog | |
conflocation = 'subdownloader.conf' | |
url = "http://api.opensubtitles.org/xml-rpc" | |
progressBarSize = 40 | |
interface_state = 0 | |
result_state = {} | |
default_language = "pob" | |
function set_default_language() | |
if default_language then | |
for k,v in ipairs(languages) do | |
if v[2] == default_language then | |
table.insert(languages, 1, v) | |
return true | |
end | |
end | |
end | |
end | |
function activate() | |
vlc.msg.dbg("[VLsub] Welcome") | |
set_default_language() | |
create_dialog() | |
openSub.getFileInfo() | |
openSub.getMovieInfo() | |
--~ openSub.request("LogIn") | |
end | |
function deactivate() | |
if openSub.token then | |
openSub.LogOut() | |
end | |
vlc.msg.dbg("[VLsub] Bye bye!") | |
end | |
function close() | |
vlc.deactivate() | |
end | |
function meta_changed() | |
openSub.getFileInfo() | |
openSub.getMovieInfo() | |
if tmp_method_id == "hash" then | |
searchHash() | |
elseif tmp_method_id == "imdb" then | |
widget.get("title").input:set_text(openSub.movie.name) | |
widget.get("season").input:set_text(openSub.movie.seasonNumber) | |
widget.get("episode").input:set_text(openSub.movie.episodeNumber) | |
end | |
end | |
function input_changed() | |
return false | |
end | |
openSub = { | |
itemStore = nil, | |
actionLabel = "", | |
conf = { | |
url = "http://api.opensubtitles.org/xml-rpc", | |
userAgentHTTP = "VLSub", | |
useragent = "VLSub 0.6", | |
username = "", | |
password = "", | |
language = "", | |
downloadSub = true, | |
removeTag = true, | |
justgetlink = false | |
}, | |
session = { | |
loginTime = 0, | |
token = "" | |
}, | |
file = { | |
uri = nil, | |
ext = nil, | |
name = nil, | |
path = nil, | |
dir = nil, | |
hash = nil, | |
bytesize = nil, | |
fps = nil, | |
timems = nil, | |
frames = nil | |
}, | |
movie = { | |
name = "", | |
season = "", | |
episode = "", | |
imdbid = nil, | |
imdbidShow = nil, | |
imdbidEpisode = nil, | |
imdbRequest = nil, | |
year = nil, | |
releasename = nil, | |
aka = nil | |
}, | |
sub = { | |
id = nil, | |
authorcomment = nil, | |
hash = nil, | |
idfile = nil, | |
filename = nil, | |
content = nil, | |
IDSubMovieFile = nil, | |
score = nil, | |
comment = nil, | |
bad = nil, | |
languageid = nil | |
}, | |
request = function(methodName) | |
local params = openSub.methods[methodName].params() | |
local reqTable = openSub.getMethodBase(methodName, params) | |
local request = "<?xml version='1.0'?>"..dump_xml(reqTable) | |
local host, path = parse_url(openSub.conf.url) | |
local header = { | |
"POST "..path.." HTTP/1.1", | |
"Host: "..host, | |
"User-Agent: "..openSub.conf.userAgentHTTP, | |
"Content-Type: text/xml", | |
"Content-Length: "..string.len(request), | |
"", | |
"" | |
} | |
request = table.concat(header, "\r\n")..request | |
local response | |
local status, responseStr = http_req(host, 80, request) | |
if status == 200 then | |
response = parse_xmlrpc(responseStr) | |
if (response and response.status == "200 OK") then | |
vlc.msg.dbg(responseStr) | |
return openSub.methods[methodName].callback(response) | |
elseif response then | |
setError("code "..response.status.."("..status..")") | |
return false | |
else | |
setError("Server not responding") | |
return false | |
end | |
elseif status == 401 then | |
setError("Request unauthorized") | |
response = parse_xmlrpc(responseStr) | |
if openSub.session.token ~= response.token then | |
setMessage("Session expired, retrying") | |
openSub.session.token = response.token | |
openSub.request(methodName) | |
end | |
return false | |
elseif status == 503 then | |
setError("Server overloaded, please retry later") | |
return false | |
end | |
end, | |
getMethodBase = function(methodName, param) | |
if openSub.methods[methodName].methodName then | |
methodName = openSub.methods[methodName].methodName | |
end | |
local request = { | |
methodCall={ | |
methodName=methodName, | |
params={ param=param }}} | |
return request | |
end, | |
methods = { | |
LogIn = { | |
params = function() | |
openSub.actionLabel = "Logging in" | |
return { | |
{ value={ string=openSub.conf.username } }, | |
{ value={ string=openSub.conf.password } }, | |
{ value={ string=openSub.conf.language } }, | |
{ value={ string=openSub.conf.useragent } } | |
} | |
end, | |
callback = function(resp) | |
openSub.session.token = resp.token | |
openSub.session.loginTime = os.time() | |
return true | |
end | |
}, | |
LogOut = { | |
params = function() | |
openSub.actionLabel = "Logging out" | |
return { | |
{ value={ string=openSub.session.token } } | |
} | |
end, | |
callback = function() | |
return true | |
end | |
}, | |
NoOperation = { | |
params = function() | |
return { | |
{ value={ string=openSub.session.token } } | |
} | |
end, | |
callback = function() | |
return true | |
end | |
}, | |
SearchSubtitlesByHash = { | |
methodName = "SearchSubtitles", | |
params = function() | |
openSub.actionLabel = "Searching subtitles" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
return { | |
{ value={ string=openSub.session.token } }, | |
{ value={ | |
array={ | |
data={ | |
value={ | |
struct={ | |
member={ | |
{ name="sublanguageid", value={ string=openSub.sub.languageid } }, | |
{ name="moviehash", value={ string=openSub.file.hash } }, | |
{ name="moviebytesize", value={ double=openSub.file.bytesize } } }}}}}}} | |
} | |
end, | |
callback = function(resp) | |
openSub.itemStore = resp.data | |
if openSub.itemStore ~= "0" then | |
return true | |
else | |
openSub.itemStore = nil | |
return false | |
end | |
end | |
}, | |
SearchMoviesOnIMDB = { | |
params = function() | |
openSub.actionLabel = "Searching movie on IMDB" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
return { | |
{ value={ string=openSub.session.token } }, | |
{ value={ string=openSub.movie.imdbRequest } } | |
} | |
end, | |
callback = function(resp) | |
openSub.itemStore = resp.data | |
if openSub.itemStore ~= "0" then | |
return true | |
else | |
openSub.itemStore = nil | |
return false | |
end | |
end | |
}, | |
SearchSubtitlesByIdIMDB = { | |
methodName = "SearchSubtitles", | |
params = function() | |
openSub.actionLabel = "Searching subtitles" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
return { | |
{ value={ string=openSub.session.token } }, | |
{ value={ | |
array={ | |
data={ | |
value={ | |
struct={ | |
member={ | |
{ name="sublanguageid", value={ string=openSub.sub.languageid } }, | |
{ name="imdbid", value={ string=openSub.movie.imdbid } } }}}}}}} | |
} | |
end, | |
callback = function(resp) | |
openSub.itemStore = resp.data | |
if openSub.itemStore ~= "0" then | |
return true | |
else | |
openSub.itemStore = nil | |
return false | |
end | |
end | |
}, | |
GetIMDBMovieDetails = { | |
params = function() | |
return { | |
{ value={ string=openSub.session.token } }, | |
{ value={ string=openSub.movie.imdbid } } | |
} | |
end, | |
callback = function(resp) | |
print(dump_xml(resp)) | |
end | |
}, | |
IsTVserie = { | |
methodName = "GetIMDBMovieDetails", | |
params = function() | |
return { | |
{ value={ string=openSub.session.token } }, | |
{ value={ string=openSub.movie.imdbid } } | |
} | |
end, | |
callback = function(resp) | |
return (string.lower(resp.data.kind)=="tv series") | |
end | |
} | |
}, | |
getInputItem = function() | |
return vlc.item or vlc.input.item() | |
end, | |
getFileInfo = function() | |
local item = openSub.getInputItem() | |
if not item then | |
return false | |
else | |
local file = openSub.file | |
local parsed_uri = vlc.net.url_parse(item:uri()) | |
file.uri = item:uri() | |
file.protocol = parsed_uri["protocol"] | |
file.path = vlc.strings.decode_uri(parsed_uri["path"]) | |
--correction needed for windows | |
local windowPath = string.match(file.path, "^/(%a:/.+)$") | |
if windowPath then | |
file.path = windowPath | |
end | |
file.dir, file.completeName = string.match(file.path, "^([^\n]-/?)([^/]+)$") | |
file.name, file.ext = string.match(file.path, "([^/]-)%.?([^%.]*)$") | |
if file.ext == "part" then | |
file.name, file.ext = string.match(file.name, "^([^/]+)%.([^%.]+)$") | |
end | |
file.cleanName = string.gsub(file.name, "[%._]", " ") | |
end | |
end, | |
getMovieInfo = function() | |
if not openSub.file.name then | |
return false | |
end | |
local showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)[sS](%d%d)[eE](%d%d).*") | |
if not showName then | |
showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)(%d)[xX](%d%d).*") | |
end | |
if showName then | |
openSub.movie.name = showName | |
openSub.movie.seasonNumber = seasonNumber | |
openSub.movie.episodeNumber = episodeNumber | |
else | |
openSub.movie.name = openSub.file.cleanName | |
openSub.movie.seasonNumber = "" | |
openSub.movie.episodeNumber = "" | |
end | |
end, | |
getMovieHash = function() | |
openSub.actionLabel = "Calculating movie hash" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
local item = openSub.getInputItem() | |
if not item then | |
setError("Please use this method during playing") | |
return false | |
end | |
openSub.getFileInfo() | |
if openSub.file.protocol ~= "file" then | |
setError("This method works with local file only (for now)") | |
return false | |
end | |
local path = openSub.file.path | |
if not path then | |
setError("File not found") | |
return false | |
end | |
local file = assert(io.open(path, "rb")) | |
if not file then | |
setError("File not found") | |
return false | |
end | |
local i = 1 | |
local a = {0, 0, 0, 0, 0, 0, 0, 0} | |
local hash = "" | |
local size = file:seek("end") | |
file:seek("set", 0) | |
local bytes = file:read(65536) | |
file:seek("set", size-65536) | |
bytes = bytes..file:read("*all") | |
file:close () | |
for b in string.gfind(string.format("%16X ", size), "..") do | |
d = tonumber(b, 16) | |
if type(d) ~= "nil" then a[9-i] = d end | |
i=i+1 | |
end | |
i = 1 | |
for b in string.gfind(bytes, ".") do | |
a[i] = a[i] + string.byte(b) | |
d = math.floor(a[i]/255) | |
if d>=1 then | |
a[i] = a[i] - d * 256 | |
if i<8 then a[i+1] = a[i+1] + d end | |
end | |
i=i+1 | |
if i==9 then i=1 end | |
end | |
for i=8, 1, -1 do | |
hash = hash..string.format("%02x",a[i]) | |
end | |
openSub.file.bytesize = size | |
openSub.file.hash = hash | |
return true | |
end, | |
getImdbEpisodeId = function(season, episode) | |
openSub.actionLabel = "Searching episode id on IMDB" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
local IMDBurl = "http://www.imdb.com/title/tt"..openSub.movie.imdbid.."/episodes/_ajax?season="..season | |
local host, path = parse_url(IMDBurl) | |
local stream = vlc.stream(IMDBurl) | |
local data = "" | |
while data do | |
data = stream:read(65536) | |
local id = string.match(data, 'data%-const="tt(%d+)"[^>]+>\r?\n<img[^>]+>\r?\n<div> S'..season..', Ep'..episode) | |
return id | |
end | |
return false | |
end, | |
getImdbEpisodeIdYQL = function(season, episode) | |
openSub.actionLabel = "Searching episode on IMDB" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
local url = "http://pipes.yahoo.com/pipes/pipe.run?_id=5f525406f2b2b376eeb20b97a216bcb1&_render=json&imdbid="..openSub.movie.imdbid.."&season="..season.."&episode="..episode | |
local host, path = parse_url(url) | |
local header = { | |
"GET "..path.." HTTP/1.1", | |
"Host: "..host, | |
"User-Agent: "..openSub.conf.userAgentHTTP, | |
"", | |
"" | |
} | |
local request = table.concat(header, "\r\n") | |
local fd = vlc.net.connect_tcp(host, 80) | |
local data = "" | |
if fd >= 0 then | |
local pollfds = {} | |
pollfds[fd] = vlc.net.POLLIN | |
vlc.net.send(fd, request) | |
vlc.net.poll(pollfds) | |
data = vlc.net.recv(fd, 2048) | |
print(data) | |
end | |
setMessage(openSub.actionLabel..": "..progressBarContent(100)) | |
local id = string.match(data, '"content":"(%d+)"') | |
return id | |
end, | |
getImdbEpisodeIdGoogle = function(season, episode, title) | |
openSub.actionLabel = "Searching episode on IMDB" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
local query = 'site:imdb.com tv episode "'..title..'" (#'..season..'.'..episode..')' | |
local url = "https://www.google.com/uds/GwebSearch?hl=fr&source=gsc&gss=.com&gl=www.google.com&context=1&key=notsupplied&v=1.0&&q="..vlc.strings.encode_uri_component(query) | |
local host, path = parse_url(url) | |
local header = { | |
"GET "..path.." HTTP/1.1", | |
"Host: "..host, | |
"User-Agent: "..openSub.conf.userAgentHTTP, | |
"", | |
"" | |
} | |
local request = table.concat(header, "\r\n") | |
local fd = vlc.net.connect_tcp(host, 80) | |
local data = "" | |
if fd >= 0 then | |
local pollfds = {} | |
pollfds[fd] = vlc.net.POLLIN | |
vlc.net.send(fd, request) | |
vlc.net.poll(pollfds) | |
data = vlc.net.recv(fd, 2048) | |
--print(data) | |
end | |
setMessage(openSub.actionLabel..": "..progressBarContent(100)) | |
local id = string.match(data, '"url":"http://www.imdb.com/title/tt(%d+)/"') | |
return id | |
end, | |
loadSubtitles = function(url, fileDir, SubFileName, target) | |
openSub.actionLabel = "Downloading subtitle" | |
setMessage(openSub.actionLabel..": "..progressBarContent(0)) | |
local resp = get(url) | |
local subfileURI = "" | |
if resp then | |
local tmpFileName = fileDir..SubFileName..".zip" | |
local tmpFile = assert(io.open(tmpFileName, "wb")) | |
tmpFile:write(resp) | |
tmpFile:close() | |
subfileURI = "zip://"..make_uri(tmpFileName.."!/"..SubFileName) | |
if target then | |
local stream = vlc.stream(subfileURI) | |
local data = "" | |
local subfile = assert(io.open(target, "w")) -- FIXME: check for file presence before overwrite (maybe ask what to do) | |
while data do | |
if openSub.conf.removeTag then | |
subfile:write(remove_tag(data).."\r\n") | |
else | |
subfile:write(data.."\r\n") | |
end | |
data = stream:readline() | |
end | |
subfile:close() | |
subfileURI = make_uri(target, true) | |
end | |
os.remove(tmpFileName) | |
end | |
local item = vlc.item or vlc.input.item() | |
if item then | |
vlc.input.add_subtitle(subfileURI) | |
else | |
setError("No current input, unable to add subtitles "..target) | |
end | |
end | |
} | |
function make_uri(str, encode) | |
local iswindowPath = string.match(str, "^%a:/.+$") | |
-- vlc.msg.dbg(iswindowPath) | |
if encode then | |
local encodedPath = "" | |
for w in string.gmatch(str, "/([^/]+)") do | |
vlc.msg.dbg(w) | |
encodedPath = encodedPath.."/"..vlc.strings.encode_uri_component(w) | |
end | |
str = encodedPath | |
end | |
if iswindowPath then | |
return "file:///"..str | |
else | |
return "file://"..str | |
end | |
end | |
function searchHash() | |
if not hasAssociatedResult() then | |
openSub.sub.languageid = languages[widget.getVal("language")][2] | |
openSub.getMovieHash() | |
associatedResult() | |
if openSub.file.hash then | |
openSub.request("SearchSubtitlesByHash") | |
display_subtitles() | |
end | |
else | |
local selection = widget.getVal("hashmainlist") | |
if #selection > 0 then | |
download_subtitles(selection) | |
end | |
end | |
end | |
function searchIMBD() | |
local title = trim(widget.getVal("title")) | |
local old_title = trim(widget.get("title").value) | |
local season = tonumber(widget.getVal("season")) | |
local old_season = tonumber(widget.get("season").value) | |
local episode = tonumber(widget.getVal("episode")) | |
local old_episode = tonumber(widget.get("episode").value) | |
local language = languages[widget.getVal("language")][2] | |
local selection = widget.getVal("imdbmainlist") | |
local sel = (#selection > 0) | |
local newTitle = (title ~= old_title) | |
local newEpisode = (season ~= old_season or episode ~= old_episode) | |
local newLanguage = (language ~= openSub.sub.languageid) | |
local movie = openSub.movie | |
local imdbResults = {} | |
widget.get("title").value = title | |
widget.get("season").value = season | |
widget.get("episode").value = episode | |
openSub.sub.languageid = language | |
if newTitle then | |
movie.imdbRequest = title | |
movie.imdbid = nil | |
movie.imdbidShow = nil | |
if openSub.request("SearchMoviesOnIMDB") then -- search exact match | |
local lowerTitle = string.lower(title) | |
local itemTitle = "" | |
for i, item in ipairs(openSub.itemStore) do | |
-- itemTitle = string.match(item.title, "[%s\"]*([^%(\"]*)[%s\"']*%(?") | |
item.cleanTitle = string.match(item.title, "[%s\"]*([^%(\"]*)[%s\"']*%(?") | |
-- vlc.msg.dbg(itemTitle) | |
--[[if string.lower(itemTitle) == lowerTitle then | |
movie.imdbid = item.id | |
break | |
end]] | |
table.insert(imdbResults, item.title) | |
end | |
if not movie.imdbid then | |
widget.setVal("imdbmainlist") | |
widget.setVal("imdbmainlist", imdbResults) | |
end | |
end | |
end | |
if not movie.imdbid and sel then | |
local index = selection[1][1] | |
local item = openSub.itemStore[index] | |
movie.imdbid = item.id | |
movie.title = item.cleanTitle | |
movie.imdbidShow = movie.imdbid | |
newEpisode = true | |
end | |
if movie.imdbid then | |
if season and episode and (newTitle or newEpisode) then | |
if not newTitle then | |
movie.imdbid = movie.imdbidShow | |
end | |
movie.imdbidEpisode = openSub.getImdbEpisodeIdGoogle(season, episode, movie.title) | |
-- movie.imdbidEpisode = openSub.getImdbEpisodeId(season, episode) | |
if movie.imdbidEpisode then | |
vlc.msg.dbg("Episode imdbid: "..movie.imdbidEpisode) | |
movie.imdbidShow = movie.imdbid | |
movie.imdbid = movie.imdbidEpisode | |
elseif openSub.request("IsTVserie") then | |
movie.imdbidEpisode = openSub.getImdbEpisodeIdYQL(season, episode) | |
if movie.imdbidEpisode then | |
movie.imdbidShow = movie.imdbid | |
movie.imdbid = movie.imdbidEpisode | |
else | |
setError("Season/episode don't match for this title") | |
end | |
else | |
setError("Title not referenced as a TV serie on IMDB") | |
--~ -- , choose an other one and/or empty episode/season field") | |
widget.setVal("imdbmainlist", imdbResults) | |
end | |
end | |
if newTitle or newEpisode or newLanguage then | |
openSub.request("SearchSubtitlesByIdIMDB") | |
display_subtitles() | |
elseif sel and openSub.itemStore then | |
download_subtitles(selection) | |
end | |
end | |
end | |
function associatedResult() | |
local item = openSub.getInputItem() | |
if not item then return false end | |
result_state[tmp_method_id] = item:uri() | |
end | |
function hasAssociatedResult() | |
local item = openSub.getInputItem() | |
if not item then return false end | |
return (result_state[tmp_method_id] == item:uri()) | |
end | |
function display_subtitles() | |
local list = tmp_method_id.."mainlist" | |
widget.setVal(list) | |
if openSub.itemStore then | |
for i, item in ipairs(openSub.itemStore) do | |
widget.setVal(list, item.SubFileName.." ("..item.SubSumCD.." CD)") | |
end | |
else | |
widget.setVal(list, "No result") | |
end | |
end | |
function download_subtitles(selection) | |
local list = tmp_method_id.."mainlist" | |
widget.resetSel(list) -- reset selection | |
local index = selection[1][1] | |
local item = openSub.itemStore[index] | |
local subfileTarget = "" | |
if openSub.file.dir and openSub.file.name then | |
subfileTarget = openSub.file.dir..openSub.file.name.."."..openSub.sub.languageid.."."..item.SubFormat | |
else | |
subfileTarget = os.tmpname() --FIXME: ask the user where to put it instaed | |
end | |
if openSub.conf.justgetlink then | |
setMessage("Link: <a href='"..item.ZipDownloadLink.."'>"..item.ZipDownloadLink.."</a>") | |
else | |
openSub.loadSubtitles(item.ZipDownloadLink, openSub.file.dir, item.SubFileName, subfileTarget) | |
close() -- Close dialog right after downloading subtitle | |
end | |
end | |
widget = { | |
stack = {}, | |
meta = {}, | |
registered_table = {}, | |
main_table = {}, | |
set_node = function(node, parent) | |
local left = parent.left | |
for k, l in pairs(node) do --parse items | |
local tmpTop = parent.height | |
local tmpLeft = left | |
local ltmpLeft = l.left | |
local ltmpTop = l.top | |
local tmphidden = l.hidden | |
l.top = parent.height + parent.top | |
l.left = left | |
l.parent = parent | |
if l.display == "none" or parent.hidden then | |
l.hidden = true | |
else | |
l.hidden = false | |
end | |
if l.type == "div" then --that's a container | |
l.display = (l.display or "block") | |
l.height = 1 | |
for _, newNode in ipairs(l.content) do --parse lines | |
widget.set_node(newNode, l) | |
l.height = l.height+1 | |
end | |
l.height = l.height - 1 | |
left = left - 1 | |
else --that's an item | |
l.display = (l.display or "inline") | |
if not l.input then | |
tmphidden = true | |
end | |
if tmphidden and not l.hidden then --~ create | |
widget.create(l) | |
elseif not tmphidden and l.hidden then --~ destroy | |
widget.destroy(l) | |
end | |
if not l.hidden and (ltmpTop ~= l.top or ltmpLeft ~= l.left) then | |
if l.input then --~ destroy | |
widget.destroy(l) | |
end | |
--~ recreate | |
widget.create(l) | |
end | |
end | |
--~ Store reference ID | |
if l.id and not widget.registered_table[l.id] then | |
widget.registered_table[l.id] = l | |
end | |
if l.display == "block" then | |
parent.height = parent.height + (l.height or 1) | |
left = parent.left | |
elseif l.display == "none" then | |
parent.height = (tmpTop or parent.height) | |
left = (tmpLeft or left) | |
elseif l.display == "inline" then | |
left = left + (l.width or 1) | |
end | |
end | |
end, | |
set_interface = function(intf_map) | |
local root = {left = 1, top = 0, height = 0, hidden = false} | |
widget.set_node(intf_map, root) | |
end, | |
destroy = function(w) | |
dlg:del_widget(w.input) | |
--~ w.input = nil | |
--~ w.value = nil | |
if widget.registered_table[w.id] then | |
widget.registered_table[w.id] = nil | |
end | |
end, | |
create = function(w) | |
local cur_widget | |
if w.type == "button" then | |
cur_widget = dlg:add_button(w.value or "", w.callback, w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "label" then | |
cur_widget = dlg:add_label(w.value or "", w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "html" then | |
cur_widget = dlg:add_html(w.value or "", w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "text_input" then | |
cur_widget = dlg:add_text_input(w.value or "", w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "password" then | |
cur_widget = dlg:add_password(w.value or "", w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "check_box" then | |
cur_widget = dlg:add_check_box(w.value or "", w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "dropdown" then | |
cur_widget = dlg:add_dropdown(w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "list" then | |
cur_widget = dlg:add_list(w.left, w.top, w.width or 1, w.height or 1) | |
elseif w.type == "image" then | |
end | |
if w.type == "dropdown" or w.type == "list" then | |
if type(w.value) == "table" then | |
for k, l in ipairs(w.value) do | |
if type(l) == "table" then | |
cur_widget:add_value(l[1], k) | |
else | |
cur_widget:add_value(l, k) | |
end | |
end | |
end | |
end | |
if w.type and w.type ~= "div" then | |
w.input = cur_widget | |
end | |
end, | |
get = function(h) | |
return widget.registered_table[h] | |
end, | |
setVal = function(h, val, index) | |
widget.set_val(widget.registered_table[h], val, index) | |
end, | |
set_val = function(w, val, index) | |
local input = w.input | |
local t = w.type | |
if t == "button" or | |
t == "label" or | |
t == "html" or | |
t == "text_input" or | |
t == "password" then | |
if type(val) == "string" then | |
input:set_text(val) | |
w.value = val | |
end | |
elseif t == "check_box" then | |
if type(val) == "bool" then | |
input:set_checked(val) | |
else | |
input:set_text(val) | |
end | |
elseif t == "dropdown" or t == "list" then | |
if val and index then | |
input:add_value(val, index) | |
w.value[index] = val | |
elseif val and not index then | |
if type(val) == "table" then | |
for k, l in ipairs(val) do | |
input:add_value(l, k) | |
table.insert(w.value, l) | |
end | |
else | |
input:add_value(val, #w.value+1) | |
table.insert(w.value, val) | |
end | |
elseif not val and not index then | |
input:clear() | |
w.value = nil | |
w.value = {} | |
end | |
end | |
end, | |
getVal = function(h, typeval) | |
if not widget.registered_table[h] then print(h) return false end | |
return widget.get_val(widget.registered_table[h], typeval) | |
end, | |
get_val = function(w, typeval) | |
local input = w.input | |
local t = w.type | |
if t == "button" or | |
t == "label" or | |
t == "html" or | |
t == "text_input" or | |
t == "password" then | |
return input:get_text() | |
elseif t == "check_box" then | |
if typeval == "checked" then | |
return input:get_checked() | |
else | |
return input:get_text() | |
end | |
elseif t == "dropdown" then | |
return input:get_value() | |
elseif t == "list" then | |
local selection = input:get_selection() | |
local output = {} | |
for index, name in pairs(selection)do | |
table.insert(output, {index, name}) | |
end | |
return output | |
end | |
end, | |
resetSel = function(h, typeval) | |
local w = widget.registered_table[h] | |
local val = w.value | |
widget.set_val(w) | |
widget.set_val(w, val) | |
end | |
} | |
function create_dialog() | |
dlg = vlc.dialog("VLSub") | |
widget.set_interface(interface) | |
end | |
function set_interface() | |
local method_index = widget.getVal("method") | |
local method_id = methods[method_index][2] | |
if tmp_method_id then | |
if tmp_method_id == method_id then | |
return false | |
end | |
widget.get(tmp_method_id).display = "none" | |
else | |
openSub.request("LogIn") | |
end | |
tmp_method_id = method_id | |
widget.get(method_id).display = "block" | |
widget.set_interface(interface) | |
setMessage("") | |
if method_id == "hash" then | |
searchHash() | |
elseif method_id == "imdb" then | |
if openSub.file.name and not hasAssociatedResult() then | |
associatedResult() | |
widget.get("title").input:set_text(openSub.movie.name) | |
widget.get("season").input:set_text(openSub.movie.seasonNumber) | |
widget.get("episode").input:set_text(openSub.movie.episodeNumber) | |
end | |
end | |
end | |
function progressBarContent(pct) | |
local content = "<span style='color:#181; font-family: monospace;'>" | |
local accomplished = math.ceil(progressBarSize*pct/100) | |
local left = progressBarSize - accomplished | |
content = content .. string.rep ("░", accomplished) | |
content = content .. "</span>" | |
content = content .. string.rep ("|", left) | |
return content | |
end | |
function setError(str) | |
setMessage("<span style='color:#B23'>Error: "..str.."</span>") | |
end | |
function setMessage(str) | |
if widget.get("message") then | |
widget.setVal("message", str) | |
dlg:update() | |
end | |
end | |
function get(url) | |
local host, path = parse_url(url) | |
local header = { | |
"GET "..path.." HTTP/1.1", | |
"Host: "..host, | |
"User-Agent: "..openSub.conf.userAgentHTTP, | |
--~ "TE: identity", -- useless, and that's a shame | |
"", | |
"" | |
} | |
local request = table.concat(header, "\r\n") | |
local response | |
local status, response = http_req(host, 80, request) | |
if status == 200 then | |
return response | |
else | |
return false | |
end | |
end | |
function http_req(host, port, request) | |
local fd = vlc.net.connect_tcp(host, port) | |
if fd >= 0 then | |
local pollfds = {} | |
pollfds[fd] = vlc.net.POLLIN | |
vlc.net.send(fd, request) | |
vlc.net.poll(pollfds) | |
local response = vlc.net.recv(fd, 1024) | |
local headerStr, body = string.match(response, "(.-\r?\n)\r?\n(.*)") | |
local header = parse_header(headerStr) | |
local contentLength = tonumber(header["Content-Length"]) | |
local TransferEncoding = header["Transfer-Encoding"] | |
local status = tonumber(header["statuscode"]) | |
local bodyLenght = string.len(body) | |
local pct = 0 | |
if status ~= 200 then return status end | |
while contentLength and bodyLenght < contentLength do | |
vlc.net.poll(pollfds) | |
response = vlc.net.recv(fd, 1024) | |
if response then | |
body = body..response | |
else | |
vlc.net.close(fd) | |
return false | |
end | |
bodyLenght = string.len(body) | |
pct = bodyLenght / contentLength * 100 | |
setMessage(openSub.actionLabel..": "..progressBarContent(pct)) | |
end | |
vlc.net.close(fd) | |
return status, body | |
end | |
return "" | |
end | |
function parse_header(data) | |
local header = {} | |
for name, s, val in string.gfind(data, "([^%s:]+)(:?)%s([^\n]+)\r?\n") do | |
if s == "" then header['statuscode'] = tonumber(string.sub (val, 1 , 3)) | |
else header[name] = val end | |
end | |
return header | |
end | |
function parse_url(url) | |
local url_parsed = vlc.net.url_parse(url) | |
return url_parsed["host"], url_parsed["path"], url_parsed["option"] | |
end | |
function parse_xml(data) | |
local tree = {} | |
local stack = {} | |
local tmp = {} | |
local level = 0 | |
table.insert(stack, tree) | |
for op, tag, p, empty, val in string.gmatch(data, "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)") do | |
if op=="/" then | |
if level>1 then | |
level = level - 1 | |
table.remove(stack) | |
end | |
else | |
level = level + 1 | |
if val == "" then | |
if type(stack[level][tag]) == "nil" then | |
stack[level][tag] = {} | |
table.insert(stack, stack[level][tag]) | |
else | |
if type(stack[level][tag][1]) == "nil" then | |
tmp = nil | |
tmp = stack[level][tag] | |
stack[level][tag] = nil | |
stack[level][tag] = {} | |
table.insert(stack[level][tag], tmp) | |
end | |
tmp = nil | |
tmp = {} | |
table.insert(stack[level][tag], tmp) | |
table.insert(stack, tmp) | |
end | |
else | |
if type(stack[level][tag]) == "nil" then | |
stack[level][tag] = {} | |
end | |
stack[level][tag] = vlc.strings.resolve_xml_special_chars(val) | |
table.insert(stack, {}) | |
end | |
if empty ~= "" then | |
stack[level][tag] = "" | |
level = level - 1 | |
table.remove(stack) | |
end | |
end | |
end | |
return tree | |
end | |
function parse_xmlrpc(data) | |
local tree = {} | |
local stack = {} | |
local tmp = {} | |
local tmpTag = "" | |
local level = 0 | |
table.insert(stack, tree) | |
for op, tag, p, empty, val in string.gmatch(data, "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)") do | |
if op=="/" then | |
if tag == "member" or tag == "array" then | |
if level>0 then | |
level = level - 1 | |
table.remove(stack) | |
end | |
end | |
elseif tag == "name" then | |
level = level + 1 | |
if val~=""then tmpTag = vlc.strings.resolve_xml_special_chars(val) end | |
if type(stack[level][tmpTag]) == "nil" then | |
stack[level][tmpTag] = {} | |
table.insert(stack, stack[level][tmpTag]) | |
else | |
tmp = nil | |
tmp = {} | |
table.insert(stack[level-1], tmp) | |
stack[level] = nil | |
stack[level] = tmp | |
table.insert(stack, tmp) | |
end | |
if empty ~= "" then | |
level = level - 1 | |
stack[level][tmpTag] = "" | |
table.remove(stack) | |
end | |
elseif tag == "array" then | |
level = level + 1 | |
tmp = nil | |
tmp = {} | |
table.insert(stack[level], tmp) | |
table.insert(stack, tmp) | |
elseif val ~= "" then | |
stack[level][tmpTag] = vlc.strings.resolve_xml_special_chars(val) | |
end | |
end | |
return tree | |
end | |
function dump_xml(data) | |
local level = 0 | |
local stack = {} | |
local dump = "" | |
local function parse(data, stack) | |
for k,v in pairs(data) do | |
if type(k)=="string" then | |
--~ print(k) | |
dump = dump.."\r\n"..string.rep (" ", level).."<"..k..">" | |
table.insert(stack, k) | |
level = level + 1 | |
elseif type(k)=="number" and k ~= 1 then | |
dump = dump.."\r\n"..string.rep (" ", level-1).."<"..stack[level]..">" | |
end | |
if type(v)=="table" then | |
parse(v, stack) | |
elseif type(v)=="string" then | |
dump = dump..vlc.strings.convert_xml_special_chars(v) | |
elseif type(v)=="number" then | |
dump = dump..v | |
end | |
if type(k)=="string" then | |
if type(v)=="table" then | |
dump = dump.."\r\n"..string.rep (" ", level-1).."</"..k..">" | |
else | |
dump = dump.."</"..k..">" | |
end | |
table.remove(stack) | |
level = level - 1 | |
elseif type(k)=="number" and k ~= #data then | |
if type(v)=="table" then | |
dump = dump.."\r\n"..string.rep (" ", level-1).."</"..stack[level]..">" | |
else | |
dump = dump.."</"..stack[level]..">" | |
end | |
end | |
end | |
end | |
parse(data, stack) | |
return dump | |
end | |
function trim(str) | |
if not str then return "" end | |
return string.gsub(str, "^%s*(.-)%s*$", "%1") | |
end | |
function remove_tag(str) | |
return string.gsub(str, "{[^}]+}", "") | |
end | |
languages = { | |
{'All', 'all'}, | |
{'Albanian', 'alb'}, | |
{'Arabic', 'ara'}, | |
{'Armenian', 'arm'}, | |
{'Malay', 'may'}, | |
{'Bosnian', 'bos'}, | |
{'Bulgarian', 'bul'}, | |
{'Catalan', 'cat'}, | |
{'Basque', 'eus'}, | |
{'Chinese (China)', 'chi'}, | |
{'Croatian', 'hrv'}, | |
{'Czech', 'cze'}, | |
{'Danish', 'dan'}, | |
{'Dutch', 'dut'}, | |
{'English (US)', 'eng'}, | |
{'English (UK)', 'bre'}, | |
{'Esperanto', 'epo'}, | |
{'Estonian', 'est'}, | |
{'Finnish', 'fin'}, | |
{'French', 'fre'}, | |
{'Galician', 'glg'}, | |
{'Georgian', 'geo'}, | |
{'German', 'ger'}, | |
{'Greek', 'ell'}, | |
{'Hebrew', 'heb'}, | |
{'Hungarian', 'hun'}, | |
{'Indonesian', 'ind'}, | |
{'Italian', 'ita'}, | |
{'Japanese', 'jpn'}, | |
{'Kazakh', 'kaz'}, | |
{'Korean', 'kor'}, | |
{'Latvian', 'lav'}, | |
{'Lithuanian', 'lit'}, | |
{'Luxembourgish', 'ltz'}, | |
{'Macedonian', 'mac'}, | |
{'Norwegian', 'nor'}, | |
{'Persian', 'per'}, | |
{'Polish', 'pol'}, | |
{'Portuguese (Portugal)', 'por'}, | |
{'Portuguese (Brazil)', 'pob'}, | |
{'Romanian', 'rum'}, | |
{'Russian', 'rus'}, | |
{'Serbian', 'scc'}, | |
{'Slovak', 'slo'}, | |
{'Slovenian', 'slv'}, | |
{'Spanish (Spain)', 'spa'}, | |
{'Swedish', 'swe'}, | |
{'Thai', 'tha'}, | |
{'Turkish', 'tur'}, | |
{'Ukrainian', 'ukr'}, | |
{'Vietnamese', 'vie'} | |
} | |
methods = { | |
{"Video hash", "hash"}, | |
{"IMDB ID", "imdb"} | |
} | |
interface = { | |
{ | |
id = "header", | |
type = "div", | |
content = { | |
{ | |
{ type = "label", value = "Search method:" }, | |
{ | |
type = "dropdown", | |
value = methods, | |
id = "method", | |
width = 2 | |
}, | |
{ type = "button", value = "Go", callback = set_interface } | |
}, | |
{ | |
{ type = "label", value = "Language:" }, | |
{ type = "dropdown", value = languages, id = "language" , width = 2 } | |
} | |
} | |
}, | |
{ | |
id = "hash", | |
type = "div", | |
display = "none", | |
content = { | |
{ | |
{ type = "list", width = 4, id = "hashmainlist" } | |
},{ | |
{ type = "span", width = 2}, | |
{ type = "button", value = "Ok", callback = searchHash }, | |
{ type = "button", value = "Close", callback = close } | |
} | |
} | |
}, | |
{ | |
id = "imdb", | |
type = "div", | |
display = "none", | |
content = { | |
{ | |
{ type = "label", value = "Title:"}, | |
{ type = "text_input", value = openSub.movie.name or "", id = "title" } | |
},{ | |
{ type = "label", value = "Season (series):"}, | |
{ type = "text_input", value = openSub.movie.seasonNumber or "", id = "season" } | |
},{ | |
{ type = "label", value = "Episode (series):"}, | |
{ type = "text_input", value = openSub.movie.episodeNumber or "", id = "episode" }, | |
{ type = "button", value = "Ok", callback = searchIMBD }, | |
{ type = "button", value = "Close", callback = close } | |
},{ | |
{ type = "list", width = 4, id = "imdbmainlist" } | |
} | |
} | |
}, | |
{ | |
id = "progressBar", | |
type = "div", | |
content = { | |
{ | |
{ type = "label", width = 4, value = "Powered by <a href='http://www.opensubtitles.org/'>opensubtitles.org</a>", id = "message" } | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment