<commit_before>--[[
 VLSub Extension for VLC media player 1.1 and 2.0
 Copyright 2010 - 2013 <NAME>

 Authors:  <NAME>
 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.
--]]

function descriptor()
	return { title = "VLsub 0.9" ;
		version = "0.9" ;
		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

require "os"

-- Global variables
dlg = nil     -- Dialog
--~ conflocation = 'subdownloader.conf'
url = "http://api.opensubtitles.org/xml-rpc"
progressBarSize = 70

--~ default_language = "fre"
default_language = nil
refresh_toggle = false

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.request("LogIn")
	update_fields()
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()
	update_fields()
end

function input_changed()
	--~ Crash !?
	--~ wait(3)
	--~ update_fields()
end

function update_fields()
	openSub.getFileInfo()
	openSub.getMovieInfo()

	if openSub.movie.name ~= nil then
		widget.get("title").input:set_text(openSub.movie.name)
	end

	if openSub.movie.seasonNumber ~= nil then
		widget.get("season").input:set_text(openSub.movie.seasonNumber)
	end

	if openSub.movie.episodeNumber ~= nil then
		widget.get("episode").input:set_text(openSub.movie.episodeNumber)
	end
end

openSub = {
	itemStore = nil,
	actionLabel = "",
	conf = {
		url = "http://api.opensubtitles.org/xml-rpc",
		userAgentHTTP = "VLSub",
		useragent = "VLSub 0.9",
		username = "",
		password = "",
		language = "",
		downloadSub = true,
		removeTag = false,
		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 = ""
	},
	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)
			--~ vlc.msg.dbg(responseStr)
			if (response and response.status == "200 OK") then
				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
		},
		SearchSubtitles = {
			methodName = "SearchSubtitles",
			params = function()
				openSub.actionLabel = "Searching subtitles"
				setMessage(openSub.actionLabel..": "..progressBarContent(0))

				local member = {
						  { name="sublanguageid", value={ string=openSub.sub.languageid } },
						  { name="query", value={ string=openSub.movie.name } } }


				if openSub.movie.season ~= nil then
					table.insert(member, { name="season", value={ string=openSub.movie.season } })
				end

				if openSub.movie.episode ~= nil then
					table.insert(member, { name="episode", value={ string=openSub.movie.episode } })
				end

				return {
					{ value={ string=openSub.session.token } },
					{ value={
						array={
						  data={
							value={
							  struct={
								member=member
								   }}}}}}
				}
			end,
			callback = function(resp)
				openSub.itemStore = resp.data

				if openSub.itemStore ~= "0" then
					return true
				else
					openSub.itemStore = nil
					return false
				end
			end
		}
	},
	getInputItem = function()
		return vlc.item or vlc.input.item()
	end,
	getFileInfo = function()
		local item = openSub.getInputItem()
		local file = openSub.file
		if not item then
			file.hasInput = false;
			file.cleanName = "";
			return false
		else
			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.hasInput = true;
			file.cleanName = string.gsub(file.name, "[%._]", " ")
			vlc.msg.dbg(file.cleanName)
		end
	end,
	getMovieInfo = function()
		if not openSub.file.name then
			openSub.movie.name = ""
			openSub.movie.seasonNumber = ""
			openSub.movie.episodeNumber = ""
			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 = ""

			vlc.msg.dbg(openSub.movie.name)
		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 lo,hi=0,0
        for i=1,8192 do
                local a,b,c,d = file:read(4):byte(1,4)
                lo = lo + a + b*256 + c*65536 + d*16777216
                a,b,c,d = file:read(4):byte(1,4)
                hi = hi + a + b*256 + c*65536 + d*16777216
                while lo>=4294967296 do
                        lo = lo-4294967296
                        hi = hi+1
                end
                while hi>=4294967296 do
                        hi = hi-4294967296
                end
        end
        local size = file:seek("end", -65536) + 65536
        for i=1,8192 do
                local a,b,c,d = file:read(4):byte(1,4)
                lo = lo + a + b*256 + c*65536 + d*16777216
                a,b,c,d = file:read(4):byte(1,4)
                hi = hi + a + b*256 + c*65536 + d*16777216
                while lo>=4294967296 do
                        lo = lo-4294967296
                        hi = hi+1
                end
                while hi>=4294967296 do
                        hi = hi-4294967296
                end
        end
        lo = lo + size
                while lo>=4294967296 do
                        lo = lo-4294967296
                        hi = hi+1
                end
                while hi>=4294967296 do
                        hi = hi-4294967296
                end

		openSub.file.bytesize = size
		openSub.file.hash = string.format("%08x%08x", hi,lo)

		return true
	end,
    loadSubtitles = function(url, SubFileName, target)
        openSub.actionLabel = "Downloading subtitle"
        setMessage(openSub.actionLabel..": "..progressBarContent(0))
        local subfileURI = nil
        local resp = get(url)
        if resp then
            local tmpFileName = openSub.file.dir..SubFileName..".zip"
            subfileURI = "zip://"..make_uri(tmpFileName, true).."!/"..SubFileName
            local tmpFile = assert(io.open(tmpFileName, "wb"))
            tmpFile:write(resp)
            tmpFile:flush()
            tmpFile:close()

            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).."\n")
                    else
                        subfile:write(data.."\n")
                    end
                    data = stream:readline()
                end

                subfile:flush()
                subfile:close()
                stream = nil
                collectgarbage()
				subfileURI = make_uri(target, true)
            end
			os.remove(tmpFileName)
        end

        if vlc.item or vlc.input.item() then
			vlc.msg.dbg("Adding subtitle :" .. subfileURI)
            vlc.input.add_subtitle(subfileURI)
            setMessage("Success: Subtitles loaded.")
        else
            setError("No current input, unable to add subtitles "..target)
        end
    end
}

function make_uri(str, encode)
    local windowdrive = string.match(str, "^(%a:/).+$")
	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 windowdrive then
        return "file:///"..windowdrive..str
    else
        return "file://"..str
    end
end

function download_selection()
	local selection = widget.getVal("mainlist")
	if #selection > 0 and openSub.itemStore then
		download_subtitles(selection)
	end
end

function searchHash()
	openSub.sub.languageid = languages[widget.getVal("language")][2]

	message = widget.get("message")
	if message.display == "none" then
		message.display = "block"
		widget.set_interface(interface)
	end

	openSub.getMovieHash()

	if openSub.file.hash then
		openSub.request("SearchSubtitlesByHash")
		display_subtitles()
	end
end

function searchIMBD()
	openSub.movie.name = trim(widget.getVal("title"))
	openSub.movie.season = tonumber(widget.getVal("season"))
	openSub.movie.episode = tonumber(widget.getVal("episode"))
	openSub.sub.languageid  = languages[widget.getVal("language")][2]

	message = widget.get("message")
	if message.display == "none" then
		message.display = "block"
		widget.set_interface(interface)
	end

	if openSub.file.name ~= "" then
		openSub.request("SearchSubtitles")
		display_subtitles()
	end
end

function display_subtitles()
	local list = "mainlist"
	widget.setVal(list)	--~ Reset list
	if openSub.itemStore then
		for i, item in ipairs(openSub.itemStore) do
			widget.setVal(list, item.SubFileName.." ["..item.SubLanguageID.."] ("..item.SubSumCD.." CD)")
		end
	else
		widget.setVal(list, "No result")
	end
end

function download_subtitles(selection)
	local list = "mainlist"
	widget.resetSel(list) -- reset selection
	local index = selection[1][1]
	local item = openSub.itemStore[index]
	local subfileTarget = ""

	if openSub.conf.justgetlink
	or not (vlc.item or vlc.input.item())
	or not openSub.file.dir
	or not openSub.file.name then
		setMessage("Link : <a href='"..item.ZipDownloadLink.."'>"..item.ZipDownloadLink.."</a>")
	else
		subfileTarget = openSub.file.dir..openSub.file.name.."."..item.SubLanguageID.."."..item.SubFormat
		vlc.msg.dbg("subfileTarget: "..subfileTarget)
		openSub.loadSubtitles(item.ZipDownloadLink, 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)
		widget.force_refresh()
	end,
	force_refresh = function() --~ Hacky
		if refresh_toggle then
			refresh_toggle = false
			dlg:set_title(openSub.conf.useragent)
		else
			refresh_toggle = true
			dlg:set_title(openSub.conf.useragent.." ")
		end
	end,
	destroy = function(w)
		dlg:del_widget(w.input)
		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(openSub.conf.useragent)
	widget.set_interface(interface)
end


function toggle_help()
	helpMessage = widget.get("helpMessage")
	if helpMessage.display == "block" then
		helpMessage.display = "none"
	elseif helpMessage.display == "none" then
		helpMessage.display = "block"
	end

	widget.set_interface(interface)
end


function set_interface()  --~ old
	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='background-color:#181;color:#181;'>"
	local accomplished = math.ceil(progressBarSize*pct/100)

	local left = progressBarSize - accomplished
	content = content .. string.rep ("-", accomplished)
	content = content .. "</span><span style='background-color:#fff;color:#fff;'>"
	content = content .. string.rep ("-", left)
	content = content .. "</span>"
	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

--~ Misc utils

function file_exists(name)
	local f=io.open(name ,"r")
	if f~=nil then
		io.close(f)
		vlc.msg.dbg("File found!" .. name)
		return true
	else
		vlc.msg.dbg("File not found. "..name)
		return false
	end
end

function wait(seconds)
	local _start = os.time()
	local _end = _start+seconds
	while (_end ~= os.time()) do
	end
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

--~ Network utils

function get(url)
	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 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

--~ XML utils

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
				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

--~ Interface data

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 = "Language:" },
				{ type = "dropdown", value = languages, id = "language" , width = 2 },
				{ type = "button", value = "Search by hash", callback = searchHash },
			}
		}
	},
	{
		id = "imdb",
		type = "div",
		content = {
			{
				{ type = "label", value = "Title:"},
				{ type = "text_input", value = openSub.movie.name or "", id = "title", width = 2 },
				{ type = "button", value = "Search by name", callback = searchIMBD }
			},{
				{ type = "label", value = "Season (series):"},
				{ type = "text_input", value = openSub.movie.seasonNumber or "", id = "season", width = 2 }
			},{
				{ type = "label", value = "Episode (series):"},
				{ type = "text_input", value = openSub.movie.episodeNumber or "", id = "episode", width = 2 }
			},{
				{ type = "list", width = 4, id = "mainlist" }
			}
		}
	},
	{
		type = "div",
		content = {
			{
				{ type = "button", value = "Help", callback = toggle_help },
				{ type = "span", width = 1},
				{ type = "button", value = "Download", callback = download_selection },
				{ type = "button", value = "Close", callback = close }
			}
		}
	},
	{
		id = "progressBar",
		type = "div",
		content = {
			{
				{ type = "html", width = 4,
					display = "none",
					value = ""..
					" Download subtittles from <a href='http://www.opensubtitles.org/'>opensubtitles.org</a> and display them while watching a video.<br>"..
					" <br>"..
					" <b><u>Usage:</u></b><br>"..
					" <br>"..
					" VLSub is meant to be used while your watching the video, so start it first (if nothing is playing you will get a link to download the subtitles in your browser).<br>"..
					" <br>"..
					" Choose the language for your subtitles and click on the button corresponding to one of the two research method provided by VLSub:<br>"..
					" <br>"..
					" <b>Method 1: Search by hash</b><br>"..
					" It is recommended to try this method first, because it performs a research based on the video file print, so you can find subtitles synchronized with your video.<br>"..
					" <br>"..
					" <b>Method 2: Search by name</b><br>"..
					" If you have no luck with the first method, just check the title is correct before clicking. If you search subtitles for a serie, you can also provide a season and episode number.<br>"..
					" <br>"..
					" <b>Downloading Subtitles</b><br>"..
					" Select one subtitle in the list and click on 'Download'.<br>"..
					" It will be put in the same directory that your video, with the same name (different extension)"..
					" so Vlc will load them automatically the next time you'll start the video.<br>"..
					" <br>"..
					" <b>/!\\ Beware :</b> Existing subtitles are overwrited without asking confirmation, so put them elsewhere if thet're important."
					, id = "helpMessage"
				}
			},{
				{
					type = "label",
					width = 4,
					display = "none",
					value = "  ",
					id = "message"
				}
			}
		}
	}
}<commit_msg>Update VLSub.lua<commit_after>--[[
 VLSub Extension for VLC media player 1.1 and 2.0
 Copyright 2013 <NAME>

 Authors:  <NAME>
 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.
--]]


						--[[ Global var ]]--

-- You can set here your default language by replacing nil with your language code (see below)
-- Example:
-- language = "fre",
-- language = "ger",
-- language = "eng",
-- ...

local options = {
	language = nil,
	downloadBehaviour = 'save',
	langExt = false,
	removeTag = false,
	showMediaInformation = true,
	progressBarSize = 80,
	intLang = 'eng',
	translations_avail = {
		eng = 'English',
		cze = 'Czech',
		dan = 'Danish',
		fre = 'Français',
		ell = 'Greek',
		baq = 'Basque',
		pob = 'Brazilian Portuguese',
		slo = 'Slovak',
		spa = 'Spanish',
		swe = 'Swedish',
		ukr = 'Ukrainian'
	},
	translation = {
		int_all = 'All',
		int_descr = 'Download subtitles from OpenSubtitles.org',
		int_research = 'Research',
		int_config = 'Config',
		int_configuration = 'Configuration',
		int_help = 'Help',
		int_search_hash = 'Search by hash',
		int_search_name = 'Search by name',
		int_title = 'Title',
		int_season = 'Season (series)',
		int_episode = 'Episode (series)',
		int_show_help = 'Show help',
		int_show_conf = 'Show config',
		int_dowload_sel = 'Download selection',
		int_close = 'Close',
		int_ok = 'Ok',
		int_save = 'Save',
		int_cancel = 'Cancel',
		int_bool_true = 'Yes',
		int_bool_false = 'No',
		int_search_transl = 'Search translations',
		int_searching_transl = 'Searching translations ...',
		int_int_lang = 'Interface language',
		int_default_lang = 'Subtitles language',
		int_dowload_behav = 'What to do with subtitles',
		int_dowload_save = 'Load and save',
		int_dowload_load = 'Load only',
		int_dowload_manual =  'Manual download',
		int_display_code = 'Display language code in file name',
		int_remove_tag = 'Remove tags',
		int_vlsub_work_dir = 'VLSub working directory',
		int_os_username = 'Username',
		int_os_password = 'Password',
		int_help_mess = " Download subtittles from <a href='http://www.opensubtitles.org/'>opensubtitles.org</a> and display them while watching a video.<br>"..
			" <br>"..
			" <b><u>Usage:</u></b><br>"..
			" <br>"..
			" VLSub is meant to be used while your watching the video, so start it first (if nothing is playing you will get a link to download the subtitles in your browser).<br>"..
			" <br>"..
			" Choose the language for your subtitles and click on the button corresponding to one of the two research method provided by VLSub:<br>"..
			" <br>"..
			" <b>Method 1: Search by hash</b><br>"..
			" It is recommended to try this method first, because it performs a research based on the video file print, so you can find subtitles synchronized with your video.<br>"..
			" <br>"..
			" <b>Method 2: Search by name</b><br>"..
			" If you have no luck with the first method, just check the title is correct before clicking. If you search subtitles for a serie, you can also provide a season and episode number.<br>"..
			" <br>"..
			" <b>Downloading Subtitles</b><br>"..
			" Select one subtitle in the list and click on 'Download'.<br>"..
			" It will be put in the same directory that your video, with the same name (different extension)"..
			" so Vlc will load them automatically the next time you'll start the video.<br>"..
			" <br>"..
			" <b>/!\\ Beware :</b> Existing subtitles are overwrited without asking confirmation, so put them elsewhere if thet're important.<br>"..
			" <br>"..
			" Find more Vlc extensions at <a href='http://addons.videolan.org'>addons.videolan.org</a>.",

		action_login = 'Logging in',
		action_logout = 'Logging out',
		action_noop = 'Checking session',
		action_search = 'Searching subtitles',
		action_hash = 'Calculating movie hash',

		mess_success = 'Success',
		mess_error = 'Error',
		mess_no_response = 'Server not responding',
		mess_unauthorized = 'Request unauthorized',
		mess_expired = 'Session expired, retrying',
		mess_overloaded = 'Server overloaded, please retry later',
		mess_no_input = 'Please use this method during playing',
		mess_not_local = 'This method works with local file only (for now)',
		mess_not_found = 'File not found',
		mess_not_found2 = 'File not found (illegal character?)',
		mess_no_selection = 'No subtitles selected',
		mess_save_fail = 'Unable to save subtitles',
		mess_click_link = 'Click here to open the file',
		mess_complete = 'Research complete',
		mess_no_res = 'No result',
		mess_res = 'result(s)',
		mess_loaded = 'Subtitles loaded',
		mess_downloading = 'Downloading subtitle',
		mess_dowload_link = 'Download link',
		mess_err_conf_access ='Can\'t fount a suitable path to save config, please set it manually',
		mess_err_wrong_path ='the path contains illegal character, please correct it'
	}
}

local languages = {
	{'alb', 'Albanian'},
	{'ara', 'Arabic'},
	{'arm', 'Armenian'},
	{'baq', 'Basque'},
	{'ben', 'Bengali'},
	{'bos', 'Bosnian'},
	{'bre', 'Breton'},
	{'bul', 'Bulgarian'},
	{'bur', 'Burmese'},
	{'cat', 'Catalan'},
	{'chi', 'Chinese'},
	{'hrv', 'Croatian'},
	{'cze', 'Czech'},
	{'dan', 'Danish'},
	{'dut', 'Dutch'},
	{'eng', 'English'},
	{'epo', 'Esperanto'},
	{'est', 'Estonian'},
	{'fin', 'Finnish'},
	{'fre', 'French'},
	{'glg', 'Galician'},
	{'geo', 'Georgian'},
	{'ger', 'German'},
	{'ell', 'Greek'},
	{'heb', 'Hebrew'},
	{'hin', 'Hindi'},
	{'hun', 'Hungarian'},
	{'ice', 'Icelandic'},
	{'ind', 'Indonesian'},
	{'ita', 'Italian'},
	{'jpn', 'Japanese'},
	{'kaz', 'Kazakh'},
	{'khm', 'Khmer'},
	{'kor', 'Korean'},
	{'lav', 'Latvian'},
	{'lit', 'Lithuanian'},
	{'ltz', 'Luxembourgish'},
	{'mac', 'Macedonian'},
	{'may', 'Malay'},
	{'mal', 'Malayalam'},
	{'mon', 'Mongolian'},
	{'nor', 'Norwegian'},
	{'oci', 'Occitan'},
	{'per', 'Persian'},
	{'pol', 'Polish'},
	{'por', 'Portuguese'},
	{'pob', 'Brazilian Portuguese'},
	{'rum', 'Romanian'},
	{'rus', 'Russian'},
	{'scc', 'Serbian'},
	{'sin', 'Sinhalese'},
	{'slo', 'Slovak'},
	{'slv', 'Slovenian'},
	{'spa', 'Spanish'},
	{'swa', 'Swahili'},
	{'swe', 'Swedish'},
	{'syr', 'Syriac'},
	{'tgl', 'Tagalog'},
	{'tel', 'Telugu'},
	{'tha', 'Thai'},
	{'tur', 'Turkish'},
	{'ukr', 'Ukrainian'},
	{'urd', 'Urdu'},
	{'vie', 'Vietnamese'}
}

-- Languages code conversion table: iso-639-1 to iso-639-3
-- See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
local lang_os_to_iso = {
	sq = "alb",
	ar = "ara",
	hy = "arm",
	eu = "baq",
	bn = "ben",
	bs = "bos",
	br = "bre",
	bg = "bul",
	my = "bur",
	ca = "cat",
	zh = "chi",
	hr = "hrv",
	cs = "cze",
	da = "dan",
	nl = "dut",
	en = "eng",
	eo = "epo",
	et = "est",
	fi = "fin",
	fr = "fre",
	gl = "glg",
	ka = "geo",
	de = "ger",
	el = "ell",
	he = "heb",
	hi = "hin",
	hu = "hun",
	is = "ice",
	id = "ind",
	it = "ita",
	ja = "jpn",
	kk = "kaz",
	km = "khm",
	ko = "kor",
	lv = "lav",
	lt = "lit",
	lb = "ltz",
	mk = "mac",
	ms = "may",
	ml = "mal",
	mn = "mon",
	no = "nor",
	oc = "oci",
	fa = "per",
	pl = "pol",
	pt = "por",
	po = "pob",
	ro = "rum",
	ru = "rus",
	sr = "scc",
	si = "sin",
	sk = "slo",
	sl = "slv",
	es = "spa",
	sw = "swa",
	sv = "swe",
	tl = "tgl",
	te = "tel",
	th = "tha",
	tr = "tur",
	uk = "ukr",
	ur = "urd",
	vi = "vie"
}

local dlg = nil
local input_table = {} -- General widget id reference
local select_conf = {} -- Drop down widget / option table association

						--[[ Vlc extension stuff ]]--

function descriptor()
	return {
		title = "VLsub 0.9.10",
		version = "0.9.10",
		author = "exebetche",
		url = 'http://www.opensubtitles.org/',
		shortdesc = "VLsub";
		description = options.translation.int_descr,
		capabilities = {"menu", "input-listener" }
	}
end

function activate()
	vlc.msg.dbg("[VLsub] Welcome")

    check_config()

    if vlc.input.item() then
		openSub.getFileInfo()
		openSub.getMovieInfo()
	end

    show_main()
end

function close()
	deactivate()
end

function deactivate()
    vlc.msg.dbg("[VLsub] Bye bye!")
    if dlg then
		dlg:hide()
	end

	if openSub.session.token and openSub.session.token ~= "" then
		openSub.request("LogOut")
	end
   vlc.deactivate()
end

function menu()
	return {
		lang.int_research,
		lang.int_config,
		lang.int_help
	}
end

function meta_changed()
	return false
end

function input_changed()
	collectgarbage()
	set_interface_main()
	collectgarbage()
end

						--[[ Interface data ]]--

function interface_main()
	dlg:add_label(lang["int_default_lang"]..':', 1, 1, 1, 1)
	input_table['language'] =  dlg:add_dropdown(2, 1, 2, 1)
	dlg:add_button(lang["int_search_hash"], searchHash, 4, 1, 1, 1)

	dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1)
	input_table['title'] = dlg:add_text_input(openSub.movie.title or "", 2, 2, 2, 1)
	dlg:add_button(lang["int_search_name"], searchIMBD, 4, 2, 1, 1)
	dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1)
	input_table['seasonNumber'] = dlg:add_text_input(openSub.movie.seasonNumber or "", 2, 3, 2, 1)
	dlg:add_label(lang["int_episode"]..':', 1, 4, 1, 1)
	input_table['episodeNumber'] = dlg:add_text_input(openSub.movie.episodeNumber or "", 2, 4, 2, 1)
	input_table['mainlist'] = dlg:add_list(1, 5, 4, 1)
	input_table['message'] = nil
	input_table['message'] = dlg:add_label(' ', 1, 6, 4, 1)
	dlg:add_button(lang["int_show_help"], show_help, 1, 7, 1, 1)
	dlg:add_button('   '..lang["int_show_conf"]..'   ', show_conf, 2, 7, 1, 1)
	dlg:add_button(lang["int_dowload_sel"], download_subtitles, 3, 7, 1, 1)
	dlg:add_button(lang["int_close"], deactivate, 4, 7, 1, 1)

	assoc_select_conf('language', 'language', openSub.conf.languages, 2, lang["int_all"])
	display_subtitles()
end

function set_interface_main()
	-- Update movie title and co. if video input change
	if not type(input_table['title']) == 'userdata' then return false end

	openSub.getFileInfo()
	openSub.getMovieInfo()

	input_table['title']:set_text(openSub.movie.title or "")
	input_table['episodeNumber']:set_text(openSub.movie.episodeNumber or "")
	input_table['seasonNumber']:set_text(openSub.movie.seasonNumber or "")
end

function interface_config()
	input_table['intLangLab'] = dlg:add_label(lang["int_int_lang"]..':', 1, 1, 1, 1)
	input_table['intLangBut'] = dlg:add_button(lang["int_search_transl"], get_available_translations, 2, 1, 1, 1)
	input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1)
	dlg:add_label(lang["int_default_lang"]..':', 1, 2, 2, 1)
	input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1)
	dlg:add_label(lang["int_dowload_behav"]..':', 1, 3, 2, 1)
	input_table['downloadBehaviour'] = dlg:add_dropdown(3, 3, 1, 1)

	dlg:add_label(lang["int_display_code"]..':', 1, 4, 0, 1)
	input_table['langExt'] = dlg:add_dropdown(3, 4, 1, 1)
	dlg:add_label(lang["int_remove_tag"]..':', 1, 5, 0, 1)
	input_table['removeTag'] = dlg:add_dropdown(3, 5, 1, 1)

	if openSub.conf.dirPath then
		if openSub.conf.os == "win" then
			dlg	:add_label("<a href='file:///"..openSub.conf.dirPath.."'>"..lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
		else
			dlg	:add_label("<a href='"..openSub.conf.dirPath.."'>"..lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
		end
	else
		dlg	:add_label(lang["int_vlsub_work_dir"], 1, 6, 2, 1)
	end

	input_table['dir_path'] = dlg:add_text_input(openSub.conf.dirPath, 2, 6, 2, 1)

	dlg:add_label(lang["int_os_username"]..':', 1, 7, 0, 1)
	input_table['os_username'] = dlg:add_text_input(openSub.option.os_username or "", 2, 7, 2, 1)
	dlg:add_label(lang["int_os_password"]..':', 1, 8, 0, 1)
	input_table['os_password'] = dlg:add_text_input(openSub.option.os_password or "", 2, 8, 2, 1)

	input_table['message'] = nil
	input_table['message'] = dlg:add_label(' ', 1, 9, 3, 1)

	dlg:add_button(lang["int_cancel"], show_main, 2, 10, 1, 1)
	dlg:add_button(lang["int_save"], apply_config, 3, 10, 1, 1)

	input_table['langExt']:add_value(lang["int_bool_"..tostring(openSub.option.langExt)], 1)
	input_table['langExt']:add_value(lang["int_bool_"..tostring(not openSub.option.langExt)], 2)
	input_table['removeTag']:add_value(lang["int_bool_"..tostring(openSub.option.removeTag)], 1)
	input_table['removeTag']:add_value(lang["int_bool_"..tostring(not openSub.option.removeTag)], 2)

	assoc_select_conf('intLang', 'intLang', openSub.conf.translations_avail, 2)
	assoc_select_conf('default_language', 'language', openSub.conf.languages, 2, lang["int_all"])
	assoc_select_conf('downloadBehaviour', 'downloadBehaviour', openSub.conf.downloadBehaviours, 1)
end

function interface_help()
	local help_html = lang["int_help_mess"]

	input_table['help'] = dlg:add_html(help_html, 1, 1, 4, 1)
	dlg:add_label(string.rep ("&nbsp;", 100), 1, 2, 3, 1)
	dlg:add_button(lang["int_ok"], show_main, 4, 2, 1, 1)
end

function trigger_menu(dlg_id)
	if dlg_id == 1 then
		close_dlg()
		dlg = vlc.dialog(openSub.conf.useragent)
		interface_main()
	elseif dlg_id == 2 then
		close_dlg()
		dlg = vlc.dialog(openSub.conf.useragent..': '..lang["int_configuration"])
		interface_config()
	elseif dlg_id == 3 then
		close_dlg()
		dlg = vlc.dialog(openSub.conf.useragent..': '..lang["int_help"])
		interface_help()
	end
	collectgarbage() --~ !important
end

function show_main()
	trigger_menu(1)
end

function show_conf()
	trigger_menu(2)
end

function show_help()
	trigger_menu(3)
end

function close_dlg()
	vlc.msg.dbg("[VLSub] Closing dialog")

	if dlg ~= nil then
		--~ dlg:delete() -- Throw an error
		dlg:hide()
	end

	dlg = nil
	input_table = nil
	input_table = {}
	collectgarbage() --~ !important
end

						--[[ Drop down / config association]]--

function assoc_select_conf(select_id, option, conf, ind, default)
-- Helper for i/o interaction betwenn drop down and option list (lang...)
	select_conf[select_id] = {cf = conf, opt  = option, dflt = default, ind = ind}
	set_default_option(select_id)
	display_select(select_id)
end

function set_default_option(select_id)
-- Put the selected option of a list in first place of the associated table
	local opt = select_conf[select_id].opt
	local cfg = select_conf[select_id].cf
	local ind = select_conf[select_id].ind
	if openSub.option[opt] then
		table.sort(cfg, function(a, b)
			if a[1] == openSub.option[opt] then
				return true
			elseif b[1] == openSub.option[opt] then
				return false
			else
				return a[ind] < b[ind]
			end
		end)
	end
end

function display_select(select_id)
-- Display the drop down values with an optionnal default value at the top
	local conf = select_conf[select_id].cf
	local opt = select_conf[select_id].opt
	local option = openSub.option[opt]
	local default = select_conf[select_id].dflt
	local default_isset = false

	if not default then
		default_isset = true
	end

	for k, l in ipairs(conf) do
		if default_isset then
			input_table[select_id]:add_value(l[2], k)
		else
			if option then
				input_table[select_id]:add_value(l[2], k)
				input_table[select_id]:add_value(default, 0)
			else
				input_table[select_id]:add_value(default, 0)
				input_table[select_id]:add_value(l[2], k)
			end
			default_isset = true
		end
	end
end

						--[[ Config & interface localization]]--

function check_config()
	-- Make a copy of english translation to use it as default
	-- in case some element aren't translated in other translations
	eng_translation = {}
	for k, v in pairs(openSub.option.translation) do
		eng_translation[k] = v
	end

	-- Get available translation full name from code
	trsl_names = {}
	for i, lg in ipairs(languages) do
		trsl_names[lg[1]] = lg[2]
	end

	if is_window_path(vlc.config.datadir()) then
		openSub.conf.os = "win"
		slash = "\\"
	else
		openSub.conf.os = "lin"
		slash = "/"
	end

	local path_generic = {"lua", "extensions", "userdata", "vlsub"}
	local dirPath = slash..table.concat(path_generic, slash)
	local filePath	= slash.."vlsub_conf.xml"
	local config_saved = false
	sub_dir = slash.."vlsub_subtitles"

	-- Check if config file path is stored in vlc config
	local other_dirs = {}

	for path in vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
		if path:match(".*"..sub_dir.."$") then
			openSub.conf.dirPath = path:gsub("%s*(.*)"..sub_dir.."%s*$", "%1")
			config_saved = true
		end
		table.insert(other_dirs, path)
	end

	-- if not stored in vlc config
	-- try to find a suitable config file path

    if openSub.conf.dirPath then
		if not is_dir(openSub.conf.dirPath) and
		(openSub.conf.os == "lin"  or
		is_win_safe(openSub.conf.dirPath)) then
			mkdir_p(openSub.conf.dirPath)
		end
    else
		local userdatadir = vlc.config.userdatadir()
		local datadir = vlc.config.datadir()

		-- check if the config already exist
		if file_exist(userdatadir..dirPath..filePath) then
			openSub.conf.dirPath = userdatadir..dirPath
			config_saved = true
		elseif file_exist(datadir..dirPath..filePath) then
			openSub.conf.dirPath = datadir..dirPath
			config_saved = true
		else
			local extension_path = slash..path_generic[1]
				..slash..path_generic[2]

			-- use the same folder as the extension if accessible
			if is_dir(userdatadir..extension_path)
			and file_touch(userdatadir..dirPath..filePath) then
					openSub.conf.dirPath = userdatadir..dirPath
			elseif file_touch(datadir..dirPath..filePath) then
				openSub.conf.dirPath = datadir..dirPath
			end

			-- try to create working dir in user folder
			if not openSub.conf.dirPath
			and is_dir(userdatadir) then
				if not is_dir(userdatadir..dirPath) then
					mkdir_p(userdatadir..dirPath)
				end
				if is_dir(userdatadir..dirPath) and
				file_touch(userdatadir..dirPath..filePath) then
					openSub.conf.dirPath = userdatadir..dirPath
				end
			end

			-- try to create working dir in vlc folder
			if not openSub.conf.dirPath and
			is_dir(datadir) then
				if not is_dir(datadir..dirPath) then
					mkdir_p(datadir..dirPath)
				end
				if file_touch(datadir..dirPath..filePath) then
					openSub.conf.dirPath = datadir..dirPath
				end
			end
		end
	end

	if openSub.conf.dirPath then
		vlc.msg.dbg("[VLSub] Working directory: " ..
			(openSub.conf.dirPath or "not found"))

		openSub.conf.filePath = openSub.conf.dirPath..filePath
		openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"

		if config_saved
		and file_exist(openSub.conf.filePath) then
			vlc.msg.dbg("[VLSub] Loading config file: "..openSub.conf.filePath)
			load_config()
		else
			vlc.msg.dbg("[VLSub] No config file")
			getenv_lang()
			config_saved = save_config()
			if not config_saved then
				vlc.msg.dbg("[VLSub] Unable to save config")
			end
		end

		-- Check presence of a translation file in "%vlsub_directory%/locale"
		-- Add translation files to available translation list

		local file_list = list_dir(openSub.conf.localePath)
		local translations_avail = openSub.conf.translations_avail
		if file_list then
			for i, file_name in ipairs(file_list) do
				local lg =  string.gsub(file_name, "^(%w%w%w).xml$", "%1")
				if lg and not translations_avail[lg] then
					table.insert(translations_avail, {lg, trsl_names[lg]})
				end
			end
		end

		-- Load selected translation from file
		if openSub.option.intLang ~= "eng"
		and not openSub.conf.translated
		then
			local transl_file_path = openSub.conf.localePath..slash..openSub.option.intLang..".xml"
			if file_exist(transl_file_path) then
				vlc.msg.dbg("[VLSub] Loadin translation from file: " .. transl_file_path)
				load_transl(transl_file_path)
			end
		end
	else
		vlc.msg.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
	end

	lang = nil
	lang = options.translation -- just a shortcut

	SetDownloadBehaviours()
	if not openSub.conf.dirPath then
		setError(lang["mess_err_conf_access"])
	end

	-- Set table list of available traduction from assoc. array
	-- so it is sortable

	for k, l in pairs(openSub.option.translations_avail) do
		if k == openSub.option.int_research then
			table.insert(openSub.conf.translations_avail, 1, {k, l})
		else
			table.insert(openSub.conf.translations_avail, {k, l})
		end
	end
	collectgarbage()
end

function load_config()
-- Overwrite default conf with loaded conf
	local tmpFile = io.open(openSub.conf.filePath, "rb")
	if not tmpFile then return false end
	local resp = tmpFile:read("*all")
	tmpFile:flush()
	tmpFile:close()
	local option = parse_xml(resp)

	for key, value in pairs(option) do
		if type(value) == "table" then
			if key == "translation" then
				openSub.conf.translated = true
				for k, v in pairs(value) do
					openSub.option.translation[k] = v
				end
			else
				openSub.option[key] = value
			end
		else
			if value == "true" then
				openSub.option[key] = true
			elseif value == "false" then
				openSub.option[key] = false
			else
				openSub.option[key] = value
			end
		end
	end
	collectgarbage()
end

function load_transl(path)
-- Overwrite default conf with loaded conf
	local tmpFile = assert(io.open(path, "rb"))
	local resp = tmpFile:read("*all")
	tmpFile:flush()
	tmpFile:close()
	openSub.option.translation = nil

	openSub.option.translation = parse_xml(resp)
	collectgarbage()
end

function apply_translation()
-- Overwrite default conf with loaded conf
	for k, v in pairs(eng_translation) do
		if not openSub.option.translation[k] then
			openSub.option.translation[k] = eng_translation[k]
		end
	end
end

function getenv_lang()
-- Retrieve the user OS language
	local os_lang = os.getenv("LANG")

	if os_lang then -- unix, mac
		os_lang = string.sub(os_lang, 0, 2)
		if type(lang_os_to_iso[os_lang]) then
			openSub.option.language = lang_os_to_iso[os_lang]
		end
	else -- Windows
		local lang_w = string.match(os.setlocale("", "collate"), "^[^_]+")
		for i, v in ipairs(openSub.conf.languages) do
		  if v[2] == lang_w then
			openSub.option.language = v[1]
		  end
		end
	end
end

function apply_config()
-- Apply user config selection to local config
	local lg_sel = input_table['intLang']:get_value()
	local sel_val
	local opt

	if lg_sel and lg_sel ~= 1
	and openSub.conf.translations_avail[lg_sel] then
		local lg = openSub.conf.translations_avail[lg_sel][1]
		set_translation(lg)
		SetDownloadBehaviours()
	end

	for select_id, v in pairs(select_conf) do
		if input_table[select_id] and select_conf[select_id] then
			sel_val = input_table[select_id]:get_value()
			opt = select_conf[select_id].opt

			if sel_val == 0 then
				openSub.option[opt] = nil
			else
				openSub.option[opt] = select_conf[select_id].cf[sel_val][1]
			end

			set_default_option(select_id)
		end
	end


	openSub.option.os_username = input_table['os_username']:get_text()
	openSub.option.os_password = input_table['os_password']:get_text()

	if input_table["langExt"]:get_value() == 2 then
		openSub.option.langExt = not openSub.option.langExt
	end

	if input_table["removeTag"]:get_value() == 2 then
		openSub.option.removeTag = not openSub.option.removeTag
	end

	-- Set a custom working directory
	local dir_path = input_table['dir_path']:get_text()
	local dir_path_err = false
	if trim(dir_path) == "" then dir_path = nil end

	if dir_path ~= openSub.conf.dirPath then
		if openSub.conf.os == "lin"
		or is_win_safe(dir_path)
		or not dir_path then
			local other_dirs = {}

			for path in vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
				path = trim(path)
				if path ~= (openSub.conf.dirPath or "")..sub_dir then
					table.insert(other_dirs, path)
				end
			end
			openSub.conf.dirPath = dir_path
			if dir_path then
				table.insert(other_dirs,
				string.gsub(dir_path, "^(.-)[\\/]?$", "%1")..sub_dir)

				if not is_dir(dir_path) then
					mkdir_p(dir_path)
				end

				openSub.conf.filePath = openSub.conf.dirPath..slash.."vlsub_conf.xml"
				openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"
			else
				openSub.conf.filePath = nil
				openSub.conf.localePath = nil
			end
			vlc.config.set("sub-autodetect-path", table.concat(other_dirs, ", "))
		else
			dir_path_err = true
			setError(lang["mess_err_wrong_path"].."<br><b>"..string.gsub(dir_path, "[^%:%w%p%s§¤]+", "<span style='color:#B23'>%1</span>").."</b>")
		end
	end

	if openSub.conf.dirPath and
	not dir_path_err then
		local config_saved = save_config()
		trigger_menu(1)
		if not config_saved then
			setError(lang["mess_err_conf_access"])
		end
	else
		setError(lang["mess_err_conf_access"])
	end
end

function save_config()
-- Dump local config into config file
	if openSub.conf.dirPath
	and openSub.conf.filePath then
		vlc.msg.dbg("[VLSub] Saving config file:  " .. openSub.conf.filePath)

		if file_touch(openSub.conf.filePath) then
			local tmpFile = assert(io.open(openSub.conf.filePath, "wb"))
			local resp = dump_xml(openSub.option)
			tmpFile:write(resp)
			tmpFile:flush()
			tmpFile:close()
			tmpFile = nil
		else
			return false
		end
		collectgarbage()
		return true
	else
		vlc.msg.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
		setError(lang["mess_err_conf_access"])
		return false
	end
end

function SetDownloadBehaviours()
	openSub.conf.downloadBehaviours = nil
	openSub.conf.downloadBehaviours = {
		{'save', lang["int_dowload_save"]},
		{'load', lang["int_dowload_load"]},
		{'manual', lang["int_dowload_manual"]}
	}
end

function get_available_translations()
-- Get all available translation files from the internet
-- (drop previous direct download from github repo because of problem with github https CA certficate on OS X an XP)
-- https://github.com/exebetche/vlsub/tree/master/locale

	local translations_url = "http://addons.videolan.org/CONTENT/content-files/148752-vlsub_translations.xml"

	if input_table['intLangBut']:get_text() == lang["int_search_transl"] then
		openSub.actionLabel = lang["int_searching_transl"]

		local translations_content, lol = get(translations_url)

		all_trsl = parse_xml(translations_content)
		local lg, trsl

		for lg, trsl in pairs(all_trsl) do
			if lg ~= options.intLang[1] and not openSub.option.translations_avail[lg] then
				openSub.option.translations_avail[lg] = trsl_names[lg] or ""
				table.insert(openSub.conf.translations_avail, {lg, trsl_names[lg]})
				input_table['intLang']:add_value(trsl_names[lg], #openSub.conf.translations_avail)
			end
		end

		setMessage(success_tag(lang["mess_complete"]))
		collectgarbage()
	end
end

function set_translation(lg)
	openSub.option.translation = nil
	openSub.option.translation = {}

	if lg == 'eng' then
		for k, v in pairs(eng_translation) do
			openSub.option.translation[k] = v
		end
	else
		-- If translation file exists in /locale directory load it
		if openSub.conf.localePath
		and file_exist(openSub.conf.localePath..slash..lg..".xml") then
			local transl_file_path = openSub.conf.localePath..slash..lg..".xml"
			vlc.msg.dbg("[VLSub] Loading translation from file: " .. transl_file_path)
			load_transl(transl_file_path)
			apply_translation()
		else
		-- Load translation file from internet
			if not all_trsl then
				get_available_translations()
			end

			if not all_trsl or not all_trsl[lg] then
				vlc.msg.dbg("[VLSub] Error, translation not found")
				return false
			end
			openSub.option.translation = all_trsl[lg]
			apply_translation()
			all_trsl = nil
		end
	end

	lang = nil
	lang = openSub.option.translation
	collectgarbage()
end

						--[[ Core ]]--

openSub = {
	itemStore = nil,
	actionLabel = "",
	conf = {
		url = "http://api.opensubtitles.org/xml-rpc",
		path = nil,
		userAgentHTTP = "VLSub",
		useragent = "VLSub 0.9",
		translations_avail = {},
		downloadBehaviours = nil,
		languages = languages
	},
	option = options,
	session = {
		loginTime = 0,
		token = ""
	},
	file = {
		hasInput = false,
		uri = nil,
		ext = nil,
		name = nil,
		path = nil,
		protocol = nil,
		cleanName = nil,
		dir = nil,
		hash = nil,
		bytesize = nil,
		fps = nil,
		timems = nil,
		frames = nil
	},
	movie = {
		title = "",
		seasonNumber = "",
		episodeNumber = "",
		sublanguageid = ""
	},
	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 then
				if response.status == "200 OK" then
					return openSub.methods[methodName].callback(response)
				elseif response.status == "406 No session" then
					openSub.request("LogIn")
				elseif response then
					setError("code '"..response.status.."' ("..status..")")
					return false
				end
			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 = lang["action_login"]
				return {
					{ value={ string=openSub.option.os_username } },
					{ value={ string=openSub.option.os_password } },
					{ value={ string=openSub.movie.sublanguageid } },
					{ 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 = lang["action_logout"]
				return {
					{ value={ string=openSub.session.token } }
				}
			end,
			callback = function()
				return true
			end
		},
		NoOperation = {
			params = function()
				openSub.actionLabel = lang["action_noop"]
				return {
					{ value={ string=openSub.session.token } }
				}
			end,
			callback = function(resp)
				return true
			end
		},
		SearchSubtitlesByHash = {
			methodName = "SearchSubtitles",
			params = function()
				openSub.actionLabel = lang["action_search"]
				setMessage(openSub.actionLabel..": "..progressBarContent(0))

				return {
					{ value={ string=openSub.session.token } },
					{ value={
						array={
						  data={
							value={
							  struct={
								member={
								  { name="sublanguageid", value={
									string=openSub.movie.sublanguageid } },
								  { name="moviehash", value={
									string=openSub.file.hash } },
								  { name="moviebytesize", value={
									double=openSub.file.bytesize } } }}}}}}}
				}
			end,
			callback = function(resp)
				openSub.itemStore = resp.data
			end
		},
		SearchSubtitles = {
			methodName = "SearchSubtitles",
			params = function()
				openSub.actionLabel = lang["action_search"]
				setMessage(openSub.actionLabel..": "..progressBarContent(0))

				local member = {
						  { name="sublanguageid", value={
							string=openSub.movie.sublanguageid } },
						  { name="query", value={
							string=openSub.movie.title } } }


				if openSub.movie.seasonNumber ~= nil then
					table.insert(member, { name="season", value={
						string=openSub.movie.seasonNumber } })
				end

				if openSub.movie.episodeNumber ~= nil then
					table.insert(member, { name="episode", value={
						string=openSub.movie.episodeNumber } })
				end

				return {
					{ value={ string=openSub.session.token } },
					{ value={
						array={
						  data={
							value={
							  struct={
								member=member
								   }}}}}}
				}
			end,
			callback = function(resp)
				openSub.itemStore = resp.data
			end
		}
	},
	getInputItem = function()
		return vlc.item or vlc.input.item()
	end,
	getFileInfo = function()
	-- Get video file path, name, extension from input uri
		local item = openSub.getInputItem()
		local file = openSub.file
		if not item then
			file.hasInput = false;
			file.cleanName = nil;
			file.protocol = nil;
			file.path = nil;
			file.ext = nil;
			file.uri = nil;
		else
			vlc.msg.dbg("[VLSub] Video URI: "..item:uri())
			local parsed_uri = vlc.net.url_parse(item:uri())
			file.uri = item:uri()
			file.protocol = parsed_uri["protocol"]
			file.path = parsed_uri["path"]

		-- Corrections

			-- For windows
			file.path = string.match(file.path, "^/(%a:/.+)$") or file.path

			-- For file in archive
			local archive_path, name_in_archive = string.match(file.path, '^([^!]+)!/([^!/]*)$')
			if archive_path and archive_path ~= "" then
				file.path = string.gsub(archive_path, '\063', '%%')
				file.path = vlc.strings.decode_uri(file.path)
				file.completeName = string.gsub(name_in_archive, '\063', '%%')
				file.completeName = vlc.strings.decode_uri(file.completeName)
				file.is_archive = true
			else -- "classic" input
				file.path = vlc.strings.decode_uri(file.path)
				file.dir, file.completeName = string.match(file.path, '^(.+/)([^/]*)$')

				local file_stat = vlc.net.stat(file.path)
				if file_stat
				then
					file.stat = file_stat
				end

				file.is_archive = false
			end

			file.name, file.ext = string.match(file.completeName, '^([^/]-)%.?([^%.]*)$')

			if file.ext == "part" then
				file.name, file.ext = string.match(file.name, '^([^/]+)%.([^%.]+)$')
			end

			file.hasInput = true;
			file.cleanName = string.gsub(file.name, "[%._]", " ")
			vlc.msg.dbg("[VLSub] file info "..(dump_xml(file)))
		end
		collectgarbage()
	end,
	getMovieInfo = function()
	-- Clean video file name and check for season/episode pattern in title
		if not openSub.file.name then
			openSub.movie.title = ""
			openSub.movie.seasonNumber = ""
			openSub.movie.episodeNumber = ""
			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.title = showName
			openSub.movie.seasonNumber = seasonNumber
			openSub.movie.episodeNumber = episodeNumber
		else
			openSub.movie.title = openSub.file.cleanName
			openSub.movie.seasonNumber = ""
			openSub.movie.episodeNumber = ""
		end
		collectgarbage()
	end,
	getMovieHash = function()
	-- Calculate movie hash
		openSub.actionLabel = lang["action_hash"]
		setMessage(openSub.actionLabel..": "..progressBarContent(0))

		local item = openSub.getInputItem()

		if not item then
			setError(lang["mess_no_input"])
			return false
		end

		openSub.getFileInfo()

		if not openSub.file.path then
			setError(lang["mess_not_found"])
			return false
		end

		local data_start = ""
		local data_end = ""
        local size
        local chunk_size = 65536

		-- Get data for hash calculation
		if openSub.file.is_archive then
			vlc.msg.dbg("[VLSub] Read hash data from stream")

			local file = vlc.stream(openSub.file.uri)
			local dataTmp1 = ""
			local dataTmp2 = ""
			size = chunk_size

			data_start = file:read(chunk_size)

			while data_end do
				size = size + string.len(data_end)
				dataTmp1 = dataTmp2
				dataTmp2 = data_end
				data_end = file:read(chunk_size)
				collectgarbage()
			end
			data_end = string.sub((dataTmp1..dataTmp2), -chunk_size)
		elseif not file_exist(openSub.file.path)
		and openSub.file.stat then
			vlc.msg.dbg("[VLSub] Read hash data from stream")

			local file = vlc.stream(openSub.file.uri)

			if not file then
				vlc.msg.dbg("[VLSub] No stream")
				return false
			end

			size = openSub.file.stat.size
			local decal = size%chunk_size

			data_start = file:read(chunk_size)

			-- "Seek" to the end
			file:read(decal)

			for i = 1, math.floor(((size-decal)/chunk_size))-2 do
				file:read(chunk_size)
			end

			data_end = file:read(chunk_size)

			file = nil
		else
			vlc.msg.dbg("[VLSub] Read hash data from file")
			local file = io.open( openSub.file.path, "rb")
			if not file then
				vlc.msg.dbg("[VLSub] No stream")
				return false
			end

			data_start = file:read(chunk_size)
			size = file:seek("end", -chunk_size) + chunk_size
			data_end = file:read(chunk_size)
			file = nil
		end

	-- Hash calculation
        local lo = size
        local hi = 0
        local o,a,b,c,d,e,f,g,h
        local hash_data = data_start..data_end
        local max_size = 4294967296
        local overflow

		for i = 1,  #hash_data, 8 do
			a,b,c,d,e,f,g,h = hash_data:byte(i,i+7)
			lo = lo + a + b*256 + c*65536 + d*16777216
			hi = hi + e + f*256 + g*65536 + h*16777216

			if lo > max_size then
				overflow = math.floor(lo/max_size)
				lo = lo-(overflow*max_size)
				hi = hi+overflow
			end

			if hi > max_size then
				overflow = math.floor(hi/max_size)
				hi = hi-(overflow*max_size)
			end
        end

		openSub.file.bytesize = size
		openSub.file.hash = string.format("%08x%08x", hi,lo)
		vlc.msg.dbg("[VLSub] Video hash: "..openSub.file.hash)
		vlc.msg.dbg("[VLSub] Video bytesize: "..size)
		collectgarbage()
		return true
	end,
	checkSession = function()

		if openSub.session.token == "" then
			openSub.request("LogIn")
		else
			openSub.request("NoOperation")
		end
	end
}

function searchHash()
	local sel = input_table["language"]:get_value()
	if sel == 0 then
		openSub.movie.sublanguageid = 'all'
	else
		openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
	end

	openSub.getMovieHash()

	if openSub.file.hash then
		openSub.checkSession()
		openSub.request("SearchSubtitlesByHash")
		display_subtitles()
	end
end

function searchIMBD()
	openSub.movie.title = trim(input_table["title"]:get_text())
	openSub.movie.seasonNumber = tonumber(input_table["seasonNumber"]:get_text())
	openSub.movie.episodeNumber = tonumber(input_table["episodeNumber"]:get_text())

	local sel = input_table["language"]:get_value()
	if sel == 0 then
		openSub.movie.sublanguageid = 'all'
	else
		openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
	end

	if openSub.movie.title ~= "" then
		openSub.checkSession()
		openSub.request("SearchSubtitles")
		display_subtitles()
	end
end

function display_subtitles()
	local mainlist = input_table["mainlist"]
	mainlist:clear()

	if openSub.itemStore == "0" then
		mainlist:add_value(lang["mess_no_res"], 1)
		setMessage("<b>"..lang["mess_complete"]..":</b> "..lang["mess_no_res"])
	elseif openSub.itemStore then
		for i, item in ipairs(openSub.itemStore) do
			mainlist:add_value(
			item.SubFileName..
			" ["..item.SubLanguageID.."]"..
			" ("..item.SubSumCD.." CD)", i)
		end
		setMessage("<b>"..lang["mess_complete"]..":</b> "..#(openSub.itemStore).."  "..lang["mess_res"])
	end
end

function get_first_sel(list)
	local selection = list:get_selection()
	for index, name in pairs(selection) do
		return index
	end
	return 0
end

function download_subtitles()
	local index = get_first_sel(input_table["mainlist"])

	if index == 0 then
		setMessage(lang["mess_no_selection"])
		return false
	end

	openSub.actionLabel = lang["mess_downloading"]

	display_subtitles() -- reset selection

	local item = openSub.itemStore[index]

	if openSub.option.downloadBehaviour == 'manual' then

		local link = "<span style='color:#181'>"
		link = link.."<b>"..lang["mess_dowload_link"]..":</b>"
		link = link.."</span> &nbsp;"
		link = link.."</span> &nbsp;<a href='"..item.ZipDownloadLink.."'>"
		link = link..item.MovieReleaseName.."</a>"

		setMessage(link)
		return false
	elseif openSub.option.downloadBehaviour == 'load' then
		if add_sub("zip://"..item.ZipDownloadLink.."!/"..item.SubFileName) then
			setMessage(success_tag(lang["mess_loaded"]))
		end
		return false
	end

	local message = ""
	local subfileName = openSub.file.name

	if openSub.option.langExt then
		subfileName = subfileName.."."..item.SubLanguageID
	end

	subfileName = subfileName.."."..item.SubFormat
	local tmp_dir
	local file_target_access = true

	if is_dir(openSub.file.dir) then
		tmp_dir = openSub.file.dir
	elseif openSub.conf.dirPath then
		tmp_dir = openSub.conf.dirPath

		message = "<br> "..error_tag(lang["mess_save_fail"].." &nbsp;"..
		"<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
		lang["mess_click_link"].."</a>")
	else
		setError(lang["mess_save_fail"].." &nbsp;"..
		"<a href='"..item.ZipDownloadLink.."'>"..
		lang["mess_click_link"].."</a>")
		return false
	end

	local tmpFileURI, tmpFileName = dump_zip(
		item.ZipDownloadLink,
		tmp_dir,
		item.SubFileName)

	vlc.msg.dbg("[VLsub] tmpFileName: "..tmpFileName)

	-- Determine if the path to the video file is accessible for writing

	local target = openSub.file.dir..subfileName

	if not file_touch(target) then
		if openSub.conf.dirPath then
			target =  openSub.conf.dirPath..slash..subfileName
			message = "<br> "..error_tag(lang["mess_save_fail"].." &nbsp;"..
			"<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
			lang["mess_click_link"].."</a>")
		else
			setError(lang["mess_save_fail"].." &nbsp;"..
			"<a href='"..item.ZipDownloadLink.."'>"..
			lang["mess_click_link"].."</a>")
			return false
		end
	end

	vlc.msg.dbg("[VLsub] Subtitles files: "..target)

	-- Unzipped data into file target

	local stream = vlc.stream(tmpFileURI)
	local data = ""
	local subfile = io.open(target, "wb")

	while data do
		subfile:write(data)
		data = stream:read(65536)
	end

	subfile:flush()
	subfile:close()

	stream = nil
	collectgarbage()

	if not os.remove(tmpFileName) then
		vlc.msg.err("[VLsub] Unable to remove temp: "..tmpFileName)
	end

	subfileURI = vlc.strings.make_uri(target)

	if not subfileURI then
		subfileURI = make_uri(target, true)
	end

	-- load subtitles
	if add_sub(subfileURI) then
		message = success_tag(lang["mess_loaded"]) .. message
	end


	setMessage(message)
end

function dump_zip(url, dir, subfileName)
	-- Dump zipped data in a temporary file
	setMessage(openSub.actionLabel..": "..progressBarContent(0))
	local resp = get(url)

	if not resp then
		setError(lang["mess_no_response"])
		return false
	end

	local tmpFileName = dir..slash..subfileName..".gz"
	if not file_touch(tmpFileName) then
		return false
	end
	local tmpFile = assert(io.open(tmpFileName, "wb"))

	tmpFile:write(resp)
	tmpFile:flush()
	tmpFile:close()
	tmpFile = nil
	collectgarbage()
	return "zip://"..make_uri(tmpFileName, true).."!/"..subfileName, tmpFileName
end

function add_sub(subfileURI)
	if vlc.item or vlc.input.item() then
		vlc.msg.dbg("[VLsub] Adding subtitle :" .. subfileURI)
		return vlc.input.add_subtitle(subfileURI)
	end
	return false
end

						--[[ Interface helpers]]--

function progressBarContent(pct)
	local accomplished = math.ceil(openSub.option.progressBarSize*pct/100)
	local left = openSub.option.progressBarSize - accomplished
	local content = "<span style='background-color:#181;color:#181;'>"..
		string.rep ("-", accomplished).."</span>"..
		"<span style='background-color:#fff;color:#fff;'>"..
		string.rep ("-", left)..
		"</span>"
	return content
end

function setMessage(str)
	if input_table["message"] then
		input_table["message"]:set_text(str)
		dlg:update()
	end
end

function setError(mess)
	setMessage(error_tag(mess))
end

function success_tag(str)
	return "<span style='color:#181'><b>"..
	lang["mess_success"]..":</b></span> "..str..""
end

function error_tag(str)
	return "<span style='color:#B23'><b>"..
	lang["mess_error"]..":</b></span> "..str..""
end

						--[[ Network utils]]--

function get(url)
	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 response
	local status, response = http_req(host, 80, request)

	if status == 200 then
		return response
	else
		return false, status, response
	end
end

function http_req(host, port, request)
	local fd = vlc.net.connect_tcp(host, port)
	if not fd then return false end
	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

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

						--[[ XML utils]]--

function parse_xml(data)
	local tree = {}
	local stack = {}
	local tmp = {}
	local level = 0
	local op, tag, p, empty, val
	table.insert(stack, tree)

	for op, tag, p, empty, val in string.gmatch(
		data,
		"[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)[%s\r\n\t]*"
	) do
		if op=="/" then
			if level>0 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

	collectgarbage()
	return tree
end

function parse_xmlrpc(data)
	local tree = {}
	local stack = {}
	local tmp = {}
	local tmpTag = ""
	local level = 0
	local op, tag, p, empty, val
	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
	collectgarbage()
	return tree
end

function dump_xml(data)
	local level = 0
	local stack = {}
	local dump = ""

	local function parse(data, stack)
		local data_index = {}
		local k
		local v
		local i
		local tb

		for k,v in pairs(data) do
			table.insert(data_index, {k, v})
			table.sort(data_index, function(a, b)
				return a[1] < b[1]
			end)
		end

		for i,tb in pairs(data_index) do
			k = tb[1]
			v = tb[2]
			if type(k)=="string" then
				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) or v)
			elseif type(v)=="number" then
				dump = dump..v
			else
				dump = dump..tostring(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)
	collectgarbage()
	return dump
end

						--[[ Misc utils]]--

function make_uri(str, encode)
    local windowdrive = string.match(str, "^(%a:%).+$")
	if encode then
		local encodedPath = ""
		for w in string.gmatch(str, "/([^/]+)") do
			encodedPath = encodedPath.."/"..vlc.strings.encode_uri_component(w)
		end
		str = encodedPath
	end
    if windowdrive then
        return "file:///"..windowdrive..str
    else
        return "file://"..str
    end
end

function file_touch(name) -- test writetability
	if not name or trim(name) == ""
	then return false end

	local f=io.open(name ,"w")
	if f~=nil then
		io.close(f)
		return true
	else
		return false
	end
end

function file_exist(name) -- test readability
	if not name or trim(name) == ""
	then return false end
	local f=io.open(name ,"r")
	if f~=nil then
		io.close(f)
		return true
	else
		return false
	end
end

function is_dir(path)
	if not path or trim(path) == ""
	then return false end
	-- Remove slash at the or it won't work on Windows
	path = string.gsub(path, "^(.-)[\\/]?$", "%1")
	local f, _, code = io.open(path, "rb")

	if f then
		_, _, code = f:read("*a")
		f:close()
		if code == 21 then
			return true
		end
	elseif code == 13 then
		return true
	end

	return false
end

function list_dir(path)
	if not path or trim(path) == ""
	then return false end
	local dir_list_cmd
	local list = {}
	if not is_dir(path) then return false end

	if openSub.conf.os == "win" then
		dir_list_cmd = io.popen('dir /b "'..path..'"')
	elseif openSub.conf.os == "lin" then
		dir_list_cmd = io.popen('ls -1 "'..path..'"')
	end

	if dir_list_cmd then
		for filename in dir_list_cmd:lines() do
			if string.match(filename, "^[^%s]+.+$") then
				table.insert(list, filename)
			end
		end
		return list
	else
		return false
	end
end

function mkdir_p(path)
	if not path or trim(path) == ""
	then return false end
	if openSub.conf.os == "win" then
		os.execute('mkdir "' .. path..'"')
	elseif openSub.conf.os == "lin" then
		os.execute("mkdir -p '" .. path.."'")
	end
end

function is_window_path(path)
	return string.match(path, "^(%a:%).+$")
end

function is_win_safe(path)
	if not path or trim(path) == ""
	or not is_window_path(path)
	then return false end
	return string.match(path, "^%a?%:?[\\%w%p%s§¤]+$")
end

function trim(str)
    if not str then return "" end
    return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1")
end

function remove_tag(str)
	return string.gsub(str, "{[^}]+}", "")
end

function sleep(sec)
   local t = vlc.misc.mdate()
   vlc.misc.mwait(t + sec*1000*1000)
end

{'fraction_non_alphanumeric': 0.15110505991854364, 'fraction_numerical': 0.010000117372269628, 'mean_word_length': 3.2945712989566007, 'pattern_counts': {'":': 15, '<': 155, '<?xml version=': 2, '>': 161, 'https://': 2, 'lorem ipsum': 0, 'www.': 4, 'xml': 39}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 49, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2951090', 'n_tokens_mistral': 34957, 'n_tokens_neox': 30214, 'n_words': 7429}