--[[ 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 = "
VLsub
" .. "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 = "fre" 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 = ""..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]+>\r?\n
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.."."..item.SubFormat else subfileTarget = os.tmpname() --FIXME: ask the user where to put it instaed end if openSub.conf.justgetlink then setMessage("Link: "..item.ZipDownloadLink.."") else openSub.loadSubtitles(item.ZipDownloadLink, openSub.file.dir, item.SubFileName, subfileTarget) 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 = "" local accomplished = math.ceil(progressBarSize*pct/100) local left = progressBarSize - accomplished content = content .. string.rep ("|", accomplished) content = content .. "" content = content .. string.rep ("|", left) return content end function setError(str) setMessage("Error: "..str.."") 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).."" else dump = dump.."" 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).."" else dump = dump.."" 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 opensubtitles.org", id = "message" } } } } }