]> git.sesse.net Git - vlc/blob - share/lua/extensions/VLSub.lua
Lua: disable net.(poll|read|write) on Windows and document
[vlc] / share / lua / extensions / VLSub.lua
1 --[[
2  VLSub Extension for VLC media player 1.1 and 2.0
3  Copyright 2010 - 2013 Guillaume Le Maout
4
5  Authors:  Guillaume Le Maout
6  Contact: http://addons.videolan.org/messages/?action=newmessage&username=exebetche
7  Bug report: http://addons.videolan.org/content/show.php/?content=148752
8
9  This program is free software; you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13
14  This program is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with this program; if not, write to the Free Software
21  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 --]]
23
24 function descriptor()
25         return { title = "VLsub 0.9" ;
26                 version = "0.9" ;
27                 author = "exebetche" ;
28                 url = 'http://www.opensubtitles.org/';
29                 shortdesc = "VLsub";
30                 description = "<center><b>VLsub</b></center>"
31                                 .. "Dowload subtitles from OpenSubtitles.org" ;
32                 capabilities = { "input-listener", "meta-listener" }
33         }
34 end
35
36 require "os"
37
38 -- Global variables
39 dlg = nil     -- Dialog
40 --~ conflocation = 'subdownloader.conf'
41 url = "http://api.opensubtitles.org/xml-rpc"
42 progressBarSize = 70
43
44 --~ default_language = "fre"
45 default_language = nil
46 refresh_toggle = false
47
48 function set_default_language()
49         if default_language then
50                 for k,v in ipairs(languages) do
51                         if v[2] == default_language then
52                                 table.insert(languages, 1, v)
53                                 return true
54                         end
55                 end
56         end
57 end
58
59 function activate()
60     vlc.msg.dbg("[VLsub] Welcome")
61     set_default_language()
62
63     create_dialog()
64         openSub.request("LogIn")
65         update_fields()
66 end
67
68 function deactivate()
69         if openSub.token then
70                 openSub.LogOut()
71         end
72     vlc.msg.dbg("[VLsub] Bye bye!")
73 end
74
75 function close()
76     vlc.deactivate()
77 end
78
79 function meta_changed()
80         update_fields()
81 end
82
83 function input_changed()
84         --~ Crash !?
85         --~ wait(3)
86         --~ update_fields()
87 end
88
89 function update_fields()
90         openSub.getFileInfo()
91         openSub.getMovieInfo()
92
93         if openSub.movie.name ~= nil then
94                 widget.get("title").input:set_text(openSub.movie.name)
95         end
96
97         if openSub.movie.seasonNumber ~= nil then
98                 widget.get("season").input:set_text(openSub.movie.seasonNumber)
99         end
100
101         if openSub.movie.episodeNumber ~= nil then
102                 widget.get("episode").input:set_text(openSub.movie.episodeNumber)
103         end
104 end
105
106 openSub = {
107         itemStore = nil,
108         actionLabel = "",
109         conf = {
110                 url = "http://api.opensubtitles.org/xml-rpc",
111                 userAgentHTTP = "VLSub",
112                 useragent = "VLSub 0.9",
113                 username = "",
114                 password = "",
115                 language = "",
116                 downloadSub = true,
117                 removeTag = false,
118                 justgetlink = false
119         },
120         session = {
121                 loginTime = 0,
122                 token = ""
123         },
124         file = {
125                 uri = nil,
126                 ext = nil,
127                 name = nil,
128                 path = nil,
129                 dir = nil,
130                 hash = nil,
131                 bytesize = nil,
132                 fps = nil,
133                 timems = nil,
134                 frames = nil
135         },
136         movie = {
137                 name = "",
138                 season = "",
139                 episode = ""
140         },
141         sub = {
142                 id = nil,
143                 authorcomment = nil,
144                 hash = nil,
145                 idfile = nil,
146                 filename = nil,
147                 content = nil,
148                 IDSubMovieFile = nil,
149                 score = nil,
150                 comment = nil,
151                 bad = nil,
152                 languageid = nil
153         },
154         request = function(methodName)
155                 local params = openSub.methods[methodName].params()
156                 local reqTable = openSub.getMethodBase(methodName, params)
157                 local request = "<?xml version='1.0'?>"..dump_xml(reqTable)
158                 local host, path = parse_url(openSub.conf.url)
159                 local header = {
160                         "POST "..path.." HTTP/1.1",
161                         "Host: "..host,
162                         "User-Agent: "..openSub.conf.userAgentHTTP,
163                         "Content-Type: text/xml",
164                         "Content-Length: "..string.len(request),
165                         "",
166                         ""
167                 }
168                 request = table.concat(header, "\r\n")..request
169
170                 local response
171                 local status, responseStr = http_req(host, 80, request)
172
173                 if status == 200 then
174                         response = parse_xmlrpc(responseStr)
175                         --~ vlc.msg.dbg(responseStr)
176                         if (response and response.status == "200 OK") then
177                                 return openSub.methods[methodName].callback(response)
178                         elseif response then
179                                 setError("code "..response.status.."("..status..")")
180                                 return false
181                         else
182                                 setError("Server not responding")
183                                 return false
184                         end
185                 elseif status == 401 then
186                         setError("Request unauthorized")
187
188                         response = parse_xmlrpc(responseStr)
189                         if openSub.session.token ~= response.token then
190                                 setMessage("Session expired, retrying")
191                                 openSub.session.token = response.token
192                                 openSub.request(methodName)
193                         end
194                         return false
195                 elseif status == 503 then
196                         setError("Server overloaded, please retry later")
197                         return false
198                 end
199
200         end,
201         getMethodBase = function(methodName, param)
202                 if openSub.methods[methodName].methodName then
203                         methodName = openSub.methods[methodName].methodName
204                 end
205
206                 local request = {
207                   methodCall={
208                         methodName=methodName,
209                         params={ param=param }}}
210
211                 return request
212         end,
213         methods = {
214                 LogIn = {
215                         params = function()
216                                 openSub.actionLabel = "Logging in"
217                                 return {
218                                         { value={ string=openSub.conf.username } },
219                                         { value={ string=openSub.conf.password } },
220                                         { value={ string=openSub.conf.language } },
221                                         { value={ string=openSub.conf.useragent } }
222                                 }
223                         end,
224                         callback = function(resp)
225                                 openSub.session.token = resp.token
226                                 openSub.session.loginTime = os.time()
227                                 return true
228                         end
229                 },
230                 LogOut = {
231                         params = function()
232                                 openSub.actionLabel = "Logging out"
233                                 return {
234                                         { value={ string=openSub.session.token } }
235                                 }
236                         end,
237                         callback = function()
238                                 return true
239                         end
240                 },
241                 NoOperation = {
242                         params = function()
243                                 return {
244                                         { value={ string=openSub.session.token } }
245                                 }
246                         end,
247                         callback = function()
248                                 return true
249                         end
250                 },
251                 SearchSubtitlesByHash = {
252                         methodName = "SearchSubtitles",
253                         params = function()
254                                 openSub.actionLabel = "Searching subtitles"
255                                 setMessage(openSub.actionLabel..": "..progressBarContent(0))
256
257                                 return {
258                                         { value={ string=openSub.session.token } },
259                                         { value={
260                                                 array={
261                                                   data={
262                                                         value={
263                                                           struct={
264                                                                 member={
265                                                                   { name="sublanguageid", value={ string=openSub.sub.languageid } },
266                                                                   { name="moviehash", value={ string=openSub.file.hash } },
267                                                                   { name="moviebytesize", value={ double=openSub.file.bytesize } } }}}}}}}
268                                 }
269                         end,
270                         callback = function(resp)
271                                 openSub.itemStore = resp.data
272
273                                 if openSub.itemStore ~= "0" then
274                                         return true
275                                 else
276                                         openSub.itemStore = nil
277                                         return false
278                                 end
279                         end
280                 },
281                 SearchSubtitles = {
282                         methodName = "SearchSubtitles",
283                         params = function()
284                                 openSub.actionLabel = "Searching subtitles"
285                                 setMessage(openSub.actionLabel..": "..progressBarContent(0))
286
287                                 local member = {
288                                                   { name="sublanguageid", value={ string=openSub.sub.languageid } },
289                                                   { name="query", value={ string=openSub.movie.name } } }
290
291
292                                 if openSub.movie.season ~= nil then
293                                         table.insert(member, { name="season", value={ string=openSub.movie.season } })
294                                 end
295
296                                 if openSub.movie.episode ~= nil then
297                                         table.insert(member, { name="episode", value={ string=openSub.movie.episode } })
298                                 end
299
300                                 return {
301                                         { value={ string=openSub.session.token } },
302                                         { value={
303                                                 array={
304                                                   data={
305                                                         value={
306                                                           struct={
307                                                                 member=member
308                                                                    }}}}}}
309                                 }
310                         end,
311                         callback = function(resp)
312                                 openSub.itemStore = resp.data
313
314                                 if openSub.itemStore ~= "0" then
315                                         return true
316                                 else
317                                         openSub.itemStore = nil
318                                         return false
319                                 end
320                         end
321                 }
322         },
323         getInputItem = function()
324                 return vlc.item or vlc.input.item()
325         end,
326         getFileInfo = function()
327                 local item = openSub.getInputItem()
328                 local file = openSub.file
329                 if not item then
330                         file.hasInput = false;
331                         file.cleanName = "";
332                         return false
333                 else
334                         local parsed_uri = vlc.net.url_parse(item:uri())
335                         file.uri = item:uri()
336                         file.protocol = parsed_uri["protocol"]
337                         file.path = vlc.strings.decode_uri(parsed_uri["path"])
338                         --correction needed for windows
339                         local windowPath = string.match(file.path, "^/(%a:/.+)$")
340                         if windowPath then
341                                 file.path = windowPath
342                         end
343                         file.dir, file.completeName = string.match(file.path, "^([^\n]-/?)([^/]+)$")
344                         file.name, file.ext = string.match(file.path, "([^/]-)%.?([^%.]*)$")
345
346                         if file.ext == "part" then
347                                 file.name, file.ext = string.match(file.name, "^([^/]+)%.([^%.]+)$")
348                         end
349                         file.hasInput = true;
350                         file.cleanName = string.gsub(file.name, "[%._]", " ")
351                         vlc.msg.dbg(file.cleanName)
352                 end
353         end,
354         getMovieInfo = function()
355                 if not openSub.file.name then
356                         openSub.movie.name = ""
357                         openSub.movie.seasonNumber = ""
358                         openSub.movie.episodeNumber = ""
359                         return false
360                 end
361
362                 local showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)[sS](%d%d)[eE](%d%d).*")
363
364                 if not showName then
365                    showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)(%d)[xX](%d%d).*")
366                 end
367
368                 if showName then
369                         openSub.movie.name = showName
370                         openSub.movie.seasonNumber = seasonNumber
371                         openSub.movie.episodeNumber = episodeNumber
372                 else
373                         openSub.movie.name = openSub.file.cleanName
374                         openSub.movie.seasonNumber = ""
375                         openSub.movie.episodeNumber = ""
376
377                         vlc.msg.dbg(openSub.movie.name)
378                 end
379         end,
380         getMovieHash = function()
381                 openSub.actionLabel = "Calculating movie hash"
382                 setMessage(openSub.actionLabel..": "..progressBarContent(0))
383
384                 local item = openSub.getInputItem()
385
386                 if not item then
387                         setError("Please use this method during playing")
388                         return false
389                 end
390
391                 openSub.getFileInfo()
392                 if openSub.file.protocol ~= "file" then
393                         setError("This method works with local file only (for now)")
394                         return false
395                 end
396
397                 local path = openSub.file.path
398                 if not path then
399                         setError("File not found")
400                         return false
401                 end
402
403                 local file = assert(io.open(path, "rb"))
404                 if not file then
405                         setError("File not found")
406                         return false
407                 end
408
409         local lo,hi=0,0
410         for i=1,8192 do
411                 local a,b,c,d = file:read(4):byte(1,4)
412                 lo = lo + a + b*256 + c*65536 + d*16777216
413                 a,b,c,d = file:read(4):byte(1,4)
414                 hi = hi + a + b*256 + c*65536 + d*16777216
415                 while lo>=4294967296 do
416                         lo = lo-4294967296
417                         hi = hi+1
418                 end
419                 while hi>=4294967296 do
420                         hi = hi-4294967296
421                 end
422         end
423         local size = file:seek("end", -65536) + 65536
424         for i=1,8192 do
425                 local a,b,c,d = file:read(4):byte(1,4)
426                 lo = lo + a + b*256 + c*65536 + d*16777216
427                 a,b,c,d = file:read(4):byte(1,4)
428                 hi = hi + a + b*256 + c*65536 + d*16777216
429                 while lo>=4294967296 do
430                         lo = lo-4294967296
431                         hi = hi+1
432                 end
433                 while hi>=4294967296 do
434                         hi = hi-4294967296
435                 end
436         end
437         lo = lo + size
438                 while lo>=4294967296 do
439                         lo = lo-4294967296
440                         hi = hi+1
441                 end
442                 while hi>=4294967296 do
443                         hi = hi-4294967296
444                 end
445
446                 openSub.file.bytesize = size
447                 openSub.file.hash = string.format("%08x%08x", hi,lo)
448
449                 return true
450         end,
451     loadSubtitles = function(url, SubFileName, target)
452         openSub.actionLabel = "Downloading subtitle"
453         setMessage(openSub.actionLabel..": "..progressBarContent(0))
454         local subfileURI = nil
455         local resp = get(url)
456         if resp then
457             local tmpFileName = openSub.file.dir..SubFileName..".zip"
458             subfileURI = "zip://"..make_uri(tmpFileName, true).."!/"..SubFileName
459             local tmpFile = assert(io.open(tmpFileName, "wb"))
460             tmpFile:write(resp)
461             tmpFile:flush()
462             tmpFile:close()
463
464             if target then
465                 local stream = vlc.stream(subfileURI)
466                 local data = ""
467                 local subfile = assert(io.open(target, "w")) -- FIXME: check for file presence before overwrite (maybe ask what to do)
468
469                 while data do
470                     if openSub.conf.removeTag then
471                         subfile:write(remove_tag(data).."\n")
472                     else
473                         subfile:write(data.."\n")
474                     end
475                     data = stream:readline()
476                 end
477
478                 subfile:flush()
479                 subfile:close()
480                 stream = nil
481                 collectgarbage()
482                                 subfileURI = make_uri(target, true)
483             end
484                         os.remove(tmpFileName)
485         end
486
487         if vlc.item or vlc.input.item() then
488                         vlc.msg.dbg("Adding subtitle :" .. subfileURI)
489             vlc.input.add_subtitle(subfileURI)
490             setMessage("Success: Subtitles loaded.")
491         else
492             setError("No current input, unable to add subtitles "..target)
493         end
494     end
495 }
496
497 function make_uri(str, encode)
498     local windowdrive = string.match(str, "^(%a:/).+$")
499         if encode then
500                 local encodedPath = ""
501                 for w in string.gmatch(str, "/([^/]+)") do
502                         vlc.msg.dbg(w)
503                         encodedPath = encodedPath.."/"..vlc.strings.encode_uri_component(w)
504                 end
505                 str = encodedPath
506         end
507     if windowdrive then
508         return "file:///"..windowdrive..str
509     else
510         return "file://"..str
511     end
512 end
513
514 function download_selection()
515         local selection = widget.getVal("mainlist")
516         if #selection > 0 and openSub.itemStore then
517                 download_subtitles(selection)
518         end
519 end
520
521 function searchHash()
522         openSub.sub.languageid = languages[widget.getVal("language")][2]
523
524         message = widget.get("message")
525         if message.display == "none" then
526                 message.display = "block"
527                 widget.set_interface(interface)
528         end
529
530         openSub.getMovieHash()
531
532         if openSub.file.hash then
533                 openSub.request("SearchSubtitlesByHash")
534                 display_subtitles()
535         end
536 end
537
538 function searchIMBD()
539         openSub.movie.name = trim(widget.getVal("title"))
540         openSub.movie.season = tonumber(widget.getVal("season"))
541         openSub.movie.episode = tonumber(widget.getVal("episode"))
542         openSub.sub.languageid  = languages[widget.getVal("language")][2]
543
544         message = widget.get("message")
545         if message.display == "none" then
546                 message.display = "block"
547                 widget.set_interface(interface)
548         end
549
550         if openSub.file.name ~= "" then
551                 openSub.request("SearchSubtitles")
552                 display_subtitles()
553         end
554 end
555
556 function display_subtitles()
557         local list = "mainlist"
558         widget.setVal(list)     --~ Reset list
559         if openSub.itemStore then
560                 for i, item in ipairs(openSub.itemStore) do
561                         widget.setVal(list, item.SubFileName.." ["..item.SubLanguageID.."] ("..item.SubSumCD.." CD)")
562                 end
563         else
564                 widget.setVal(list, "No result")
565         end
566 end
567
568 function download_subtitles(selection)
569         local list = "mainlist"
570         widget.resetSel(list) -- reset selection
571         local index = selection[1][1]
572         local item = openSub.itemStore[index]
573         local subfileTarget = ""
574
575         if openSub.conf.justgetlink
576         or not (vlc.item or vlc.input.item())
577         or not openSub.file.dir
578         or not openSub.file.name then
579                 setMessage("Link : <a href='"..item.ZipDownloadLink.."'>"..item.ZipDownloadLink.."</a>")
580         else
581                 subfileTarget = openSub.file.dir..openSub.file.name.."."..item.SubLanguageID.."."..item.SubFormat
582                 vlc.msg.dbg("subfileTarget: "..subfileTarget)
583                 openSub.loadSubtitles(item.ZipDownloadLink, item.SubFileName, subfileTarget)
584         end
585 end
586
587 widget = {
588         stack = {},
589     meta = {},
590         registered_table = {},
591         main_table = {},
592         set_node = function(node, parent)
593                 local left = parent.left
594                 for k, l in pairs(node) do --parse items
595                         local tmpTop = parent.height
596                         local tmpLeft = left
597                         local ltmpLeft = l.left
598                         local ltmpTop = l.top
599                         local tmphidden = l.hidden
600
601                         l.top = parent.height + parent.top
602                         l.left = left
603                         l.parent = parent
604
605                         if l.display == "none" or parent.hidden then
606                                 l.hidden = true
607                         else
608                                 l.hidden = false
609                         end
610
611                         if l.type == "div" then --that's a container
612                                 l.display = (l.display or "block")
613                                 l.height = 1
614                                 for _, newNode in ipairs(l.content) do --parse lines
615                                         widget.set_node(newNode, l)
616                                         l.height = l.height+1
617                                 end
618                                 l.height = l.height - 1
619                                 left = left - 1
620                         else --that's an item
621                                 l.display = (l.display or "inline")
622
623                                 if not l.input then
624                                         tmphidden = true
625                                 end
626
627                                 if tmphidden and not l.hidden then --~ create
628                                         widget.create(l)
629                                 elseif not tmphidden and l.hidden then --~ destroy
630                                         widget.destroy(l)
631                                 end
632
633                                 if not l.hidden and (ltmpTop ~= l.top or ltmpLeft ~= l.left) then
634                                         if l.input then --~ destroy
635                                                 widget.destroy(l)
636                                         end
637                                         --~ recreate
638                                         widget.create(l)
639                                 end
640                         end
641
642                         --~  Store reference ID
643                         if l.id and not widget.registered_table[l.id] then
644                                 widget.registered_table[l.id] = l
645                         end
646
647                         if l.display == "block" then
648                                 parent.height = parent.height + (l.height or 1)
649                                 left = parent.left
650                         elseif l.display == "none" then
651                                 parent.height = (tmpTop or parent.height)
652                                 left = (tmpLeft or left)
653                         elseif l.display == "inline" then
654                                 left = left + (l.width or 1)
655                         end
656                 end
657         end,
658         set_interface = function(intf_map)
659                 local root = {left = 1, top = 0, height = 0, hidden = false}
660                 widget.set_node(intf_map, root)
661                 widget.force_refresh()
662         end,
663         force_refresh = function() --~ Hacky
664                 if refresh_toggle then
665                         refresh_toggle = false
666                         dlg:set_title(openSub.conf.useragent)
667                 else
668                         refresh_toggle = true
669                         dlg:set_title(openSub.conf.useragent.." ")
670                 end
671         end,
672         destroy = function(w)
673                 dlg:del_widget(w.input)
674                 if widget.registered_table[w.id] then
675                         widget.registered_table[w.id] = nil
676                 end
677         end,
678         create = function(w)
679                 local cur_widget
680                 if w.type == "button" then
681                         cur_widget = dlg:add_button(w.value or "", w.callback, w.left, w.top, w.width or 1, w.height or 1)
682                 elseif w.type == "label" then
683                         cur_widget = dlg:add_label(w.value or "", w.left, w.top, w.width or 1, w.height or 1)
684                 elseif w.type == "html" then
685                         cur_widget = dlg:add_html(w.value or "", w.left, w.top, w.width or 1, w.height or 1)
686                 elseif w.type == "text_input" then
687                         cur_widget = dlg:add_text_input(w.value or "", w.left, w.top, w.width or 1, w.height or 1)
688                 elseif w.type == "password" then
689                         cur_widget = dlg:add_password(w.value or "", w.left, w.top, w.width or 1, w.height or 1)
690                 elseif w.type == "check_box" then
691                         cur_widget = dlg:add_check_box(w.value or "", w.left, w.top, w.width or 1, w.height or 1)
692                 elseif w.type == "dropdown" then
693                         cur_widget = dlg:add_dropdown(w.left, w.top, w.width or 1, w.height or 1)
694                 elseif w.type == "list" then
695                         cur_widget = dlg:add_list(w.left, w.top, w.width or 1, w.height or 1)
696                 elseif w.type == "image" then
697
698                 end
699
700                 if w.type == "dropdown" or w.type == "list" then
701                         if type(w.value) == "table" then
702                                 for k, l in ipairs(w.value) do
703                                         if type(l) == "table" then
704                                                 cur_widget:add_value(l[1], k)
705                                         else
706                                                 cur_widget:add_value(l, k)
707                                         end
708                                 end
709                         end
710                 end
711
712                 if w.type and w.type ~= "div" then
713                         w.input = cur_widget
714                 end
715         end,
716         get = function(h)
717                  return widget.registered_table[h]
718         end,
719         setVal = function(h, val, index)
720                 widget.set_val(widget.registered_table[h], val, index)
721         end,
722         set_val = function(w, val, index)
723                 local input = w.input
724                 local t = w.type
725                 if t == "button" or
726                 t == "label" or
727                 t == "html" or
728                 t == "text_input" or
729                 t == "password" then
730                         if type(val) == "string" then
731                                 input:set_text(val)
732                                 w.value = val
733                         end
734                 elseif t == "check_box" then
735                         if type(val) == "bool" then
736                                 input:set_checked(val)
737                         else
738                                 input:set_text(val)
739                         end
740                 elseif t == "dropdown" or t == "list" then
741                         if val and index then
742                                 input:add_value(val, index)
743                                 w.value[index] = val
744                         elseif val and not index then
745                                 if type(val) == "table" then
746                                         for k, l in ipairs(val) do
747                                                 input:add_value(l, k)
748                                                 table.insert(w.value, l)
749                                         end
750                                 else
751                                         input:add_value(val, #w.value+1)
752                                         table.insert(w.value, val)
753                                 end
754                         elseif not val and not index then
755                                 input:clear()
756                                 w.value = nil
757                                 w.value = {}
758                         end
759                 end
760         end,
761         getVal = function(h, typeval)
762                 if not widget.registered_table[h] then print(h) return false end
763                 return widget.get_val(widget.registered_table[h], typeval)
764         end,
765         get_val = function(w, typeval)
766                 local input = w.input
767                 local t = w.type
768
769                 if t == "button" or
770                    t == "label" or
771                    t == "html" or
772                    t == "text_input" or
773                    t == "password" then
774                         return input:get_text()
775                 elseif t == "check_box" then
776                         if typeval == "checked" then
777                                 return input:get_checked()
778                         else
779                                 return input:get_text()
780                         end
781                 elseif t == "dropdown" then
782                         return input:get_value()
783                 elseif t == "list" then
784                         local selection = input:get_selection()
785                         local output = {}
786
787                         for index, name in  pairs(selection)do
788                                 table.insert(output, {index, name})
789                         end
790                         return output
791                 end
792         end,
793         resetSel = function(h, typeval)
794                 local w = widget.registered_table[h]
795                 local val = w.value
796                 widget.set_val(w)
797                 widget.set_val(w, val)
798         end
799 }
800
801 function create_dialog()
802         dlg = vlc.dialog(openSub.conf.useragent)
803         widget.set_interface(interface)
804 end
805
806
807 function toggle_help()
808         helpMessage = widget.get("helpMessage")
809         if helpMessage.display == "block" then
810                 helpMessage.display = "none"
811         elseif helpMessage.display == "none" then
812                 helpMessage.display = "block"
813         end
814
815         widget.set_interface(interface)
816 end
817
818
819 function set_interface()  --~ old
820         local method_index = widget.getVal("method")
821         local method_id = methods[method_index][2]
822         if tmp_method_id then
823                 if tmp_method_id == method_id then
824                         return false
825                 end
826                 widget.get(tmp_method_id).display = "none"
827         else
828                 openSub.request("LogIn")
829         end
830         tmp_method_id = method_id
831         widget.get(method_id).display = "block"
832         widget.set_interface(interface)
833         setMessage("")
834
835         if method_id == "hash" then
836                 searchHash()
837         elseif method_id == "imdb" then
838                 if openSub.file.name and not hasAssociatedResult() then
839                         associatedResult()
840                         widget.get("title").input:set_text(openSub.movie.name)
841                         widget.get("season").input:set_text(openSub.movie.seasonNumber)
842                         widget.get("episode").input:set_text(openSub.movie.episodeNumber)
843                 end
844         end
845 end
846
847 function progressBarContent(pct)
848         local content = "<span style='background-color:#181;color:#181;'>"
849         local accomplished = math.ceil(progressBarSize*pct/100)
850
851         local left = progressBarSize - accomplished
852         content = content .. string.rep ("-", accomplished)
853         content = content .. "</span><span style='background-color:#fff;color:#fff;'>"
854         content = content .. string.rep ("-", left)
855         content = content .. "</span>"
856         return content
857 end
858
859 function setError(str)
860         setMessage("<span style='color:#B23'>Error: "..str.."</span>")
861 end
862
863 function setMessage(str)
864         if widget.get("message") then
865                 widget.setVal("message", str)
866                 dlg:update()
867         end
868 end
869
870 --~ Misc utils
871
872 function file_exists(name)
873         local f=io.open(name ,"r")
874         if f~=nil then
875                 io.close(f)
876                 vlc.msg.dbg("File found!" .. name)
877                 return true
878         else
879                 vlc.msg.dbg("File not found. "..name)
880                 return false
881         end
882 end
883
884 function wait(seconds)
885         local _start = os.time()
886         local _end = _start+seconds
887         while (_end ~= os.time()) do
888         end
889 end
890
891 function trim(str)
892     if not str then return "" end
893     return string.gsub(str, "^%s*(.-)%s*$", "%1")
894 end
895
896 function remove_tag(str)
897         return string.gsub(str, "{[^}]+}", "")
898 end
899
900 --~ Network utils
901
902 function get(url)
903         local host, path = parse_url(url)
904         local header = {
905                 "GET "..path.." HTTP/1.1",
906                 "Host: "..host,
907                 "User-Agent: "..openSub.conf.userAgentHTTP,
908                 "",
909                 ""
910         }
911         local request = table.concat(header, "\r\n")
912
913         local response
914         local status, response = http_req(host, 80, request)
915
916         if status == 200 then
917                 return response
918         else
919                 return false
920         end
921 end
922
923 function http_req(host, port, request)
924         local fd = vlc.net.connect_tcp(host, port)
925         if fd >= 0 then
926                 local pollfds = {}
927
928                 pollfds[fd] = vlc.net.POLLIN
929                 vlc.net.send(fd, request)
930                 vlc.net.poll(pollfds)
931
932                 local response = vlc.net.recv(fd, 1024)
933                 local headerStr, body = string.match(response, "(.-\r?\n)\r?\n(.*)")
934                 local header = parse_header(headerStr)
935                 local contentLength = tonumber(header["Content-Length"])
936                 local TransferEncoding = header["Transfer-Encoding"]
937                 local status = tonumber(header["statuscode"])
938                 local bodyLenght = string.len(body)
939                 local pct = 0
940
941                 if status ~= 200 then return status end
942
943                 while contentLength and bodyLenght < contentLength do
944                         vlc.net.poll(pollfds)
945                         response = vlc.net.recv(fd, 1024)
946
947                         if response then
948                                 body = body..response
949                         else
950                                 vlc.net.close(fd)
951                                 return false
952                         end
953                         bodyLenght = string.len(body)
954                         pct = bodyLenght / contentLength * 100
955                         setMessage(openSub.actionLabel..": "..progressBarContent(pct))
956                 end
957                 vlc.net.close(fd)
958
959                 return status, body
960         end
961         return ""
962 end
963
964 function parse_header(data)
965         local header = {}
966
967         for name, s, val in string.gfind(data, "([^%s:]+)(:?)%s([^\n]+)\r?\n") do
968                 if s == "" then header['statuscode'] =  tonumber(string.sub (val, 1 , 3))
969                 else header[name] = val end
970         end
971         return header
972 end
973
974 function parse_url(url)
975         local url_parsed = vlc.net.url_parse(url)
976         return  url_parsed["host"], url_parsed["path"], url_parsed["option"]
977 end
978
979 --~ XML utils
980
981 function parse_xml(data)
982         local tree = {}
983         local stack = {}
984         local tmp = {}
985         local level = 0
986
987         table.insert(stack, tree)
988
989         for op, tag, p, empty, val in string.gmatch(data, "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)") do
990                 if op=="/" then
991                         if level>1 then
992                                 level = level - 1
993                                 table.remove(stack)
994                         end
995                 else
996                         level = level + 1
997                         if val == "" then
998                                 if type(stack[level][tag]) == "nil" then
999                                         stack[level][tag] = {}
1000                                         table.insert(stack, stack[level][tag])
1001                                 else
1002                                         if type(stack[level][tag][1]) == "nil" then
1003                                                 tmp = nil
1004                                                 tmp = stack[level][tag]
1005                                                 stack[level][tag] = nil
1006                                                 stack[level][tag] = {}
1007                                                 table.insert(stack[level][tag], tmp)
1008                                         end
1009                                         tmp = nil
1010                                         tmp = {}
1011                                         table.insert(stack[level][tag], tmp)
1012                                         table.insert(stack, tmp)
1013                                 end
1014                         else
1015                                 if type(stack[level][tag]) == "nil" then
1016                                         stack[level][tag] = {}
1017                                 end
1018                                 stack[level][tag] = vlc.strings.resolve_xml_special_chars(val)
1019                                 table.insert(stack,  {})
1020                         end
1021                         if empty ~= "" then
1022                                 stack[level][tag] = ""
1023                                 level = level - 1
1024                                 table.remove(stack)
1025                         end
1026                 end
1027         end
1028         return tree
1029 end
1030
1031 function parse_xmlrpc(data)
1032         local tree = {}
1033         local stack = {}
1034         local tmp = {}
1035         local tmpTag = ""
1036         local level = 0
1037         table.insert(stack, tree)
1038
1039         for op, tag, p, empty, val in string.gmatch(data, "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)") do
1040                 if op=="/" then
1041                         if tag == "member" or tag == "array" then
1042                                 if level>0  then
1043                                         level = level - 1
1044                                         table.remove(stack)
1045                                 end
1046                         end
1047                 elseif tag == "name" then
1048                         level = level + 1
1049                         if val~=""then tmpTag  = vlc.strings.resolve_xml_special_chars(val) end
1050
1051                         if type(stack[level][tmpTag]) == "nil" then
1052                                 stack[level][tmpTag] = {}
1053                                 table.insert(stack, stack[level][tmpTag])
1054                         else
1055                                 tmp = nil
1056                                 tmp = {}
1057                                 table.insert(stack[level-1], tmp)
1058
1059                                 stack[level] = nil
1060                                 stack[level] = tmp
1061                                 table.insert(stack, tmp)
1062                         end
1063                         if empty ~= "" then
1064                                 level = level - 1
1065                                 stack[level][tmpTag] = ""
1066                                 table.remove(stack)
1067                         end
1068                 elseif tag == "array" then
1069                         level = level + 1
1070                         tmp = nil
1071                         tmp = {}
1072                         table.insert(stack[level], tmp)
1073                         table.insert(stack, tmp)
1074                 elseif val ~= "" then
1075                         stack[level][tmpTag] = vlc.strings.resolve_xml_special_chars(val)
1076                 end
1077         end
1078         return tree
1079 end
1080
1081 function dump_xml(data)
1082         local level = 0
1083         local stack = {}
1084         local dump = ""
1085
1086         local function parse(data, stack)
1087                 for k,v in pairs(data) do
1088                         if type(k)=="string" then
1089                                 dump = dump.."\r\n"..string.rep (" ", level).."<"..k..">"
1090                                 table.insert(stack, k)
1091                                 level = level + 1
1092                         elseif type(k)=="number" and k ~= 1 then
1093                                 dump = dump.."\r\n"..string.rep (" ", level-1).."<"..stack[level]..">"
1094                         end
1095
1096                         if type(v)=="table" then
1097                                 parse(v, stack)
1098                         elseif type(v)=="string" then
1099                                 dump = dump..vlc.strings.convert_xml_special_chars(v)
1100                         elseif type(v)=="number" then
1101                                 dump = dump..v
1102                         end
1103
1104                         if type(k)=="string" then
1105                                 if type(v)=="table" then
1106                                         dump = dump.."\r\n"..string.rep (" ", level-1).."</"..k..">"
1107                                 else
1108                                         dump = dump.."</"..k..">"
1109                                 end
1110                                 table.remove(stack)
1111                                 level = level - 1
1112
1113                         elseif type(k)=="number" and k ~= #data then
1114                                 if type(v)=="table" then
1115                                         dump = dump.."\r\n"..string.rep (" ", level-1).."</"..stack[level]..">"
1116                                 else
1117                                         dump = dump.."</"..stack[level]..">"
1118                                 end
1119                         end
1120                 end
1121         end
1122         parse(data, stack)
1123         return dump
1124 end
1125
1126 --~ Interface data
1127
1128 languages = {
1129         {'All', 'all'},
1130         {'Albanian', 'alb'},
1131         {'Arabic', 'ara'},
1132         {'Armenian', 'arm'},
1133         {'Malay', 'may'},
1134         {'Bosnian', 'bos'},
1135         {'Bulgarian', 'bul'},
1136         {'Catalan', 'cat'},
1137         {'Basque', 'eus'},
1138         {'Chinese (China)', 'chi'},
1139         {'Croatian', 'hrv'},
1140         {'Czech', 'cze'},
1141         {'Danish', 'dan'},
1142         {'Dutch', 'dut'},
1143         {'English (US)', 'eng'},
1144         {'English (UK)', 'bre'},
1145         {'Esperanto', 'epo'},
1146         {'Estonian', 'est'},
1147         {'Finnish', 'fin'},
1148         {'French', 'fre'},
1149         {'Galician', 'glg'},
1150         {'Georgian', 'geo'},
1151         {'German', 'ger'},
1152         {'Greek', 'ell'},
1153         {'Hebrew', 'heb'},
1154         {'Hungarian', 'hun'},
1155         {'Indonesian', 'ind'},
1156         {'Italian', 'ita'},
1157         {'Japanese', 'jpn'},
1158         {'Kazakh', 'kaz'},
1159         {'Korean', 'kor'},
1160         {'Latvian', 'lav'},
1161         {'Lithuanian', 'lit'},
1162         {'Luxembourgish', 'ltz'},
1163         {'Macedonian', 'mac'},
1164         {'Norwegian', 'nor'},
1165         {'Persian', 'per'},
1166         {'Polish', 'pol'},
1167         {'Portuguese (Portugal)', 'por'},
1168         {'Portuguese (Brazil)', 'pob'},
1169         {'Romanian', 'rum'},
1170         {'Russian', 'rus'},
1171         {'Serbian', 'scc'},
1172         {'Slovak', 'slo'},
1173         {'Slovenian', 'slv'},
1174         {'Spanish (Spain)', 'spa'},
1175         {'Swedish', 'swe'},
1176         {'Thai', 'tha'},
1177         {'Turkish', 'tur'},
1178         {'Ukrainian', 'ukr'},
1179         {'Vietnamese', 'vie'}
1180 }
1181
1182 methods = {
1183         {"Video hash", "hash"},
1184         {"IMDB ID", "imdb"}
1185 }
1186
1187 interface = {
1188         {
1189                 id = "header",
1190                 type = "div",
1191                 content = {
1192                         {
1193                                 { type = "label", value = "Language:" },
1194                                 { type = "dropdown", value = languages, id = "language" , width = 2 },
1195                                 { type = "button", value = "Search by hash", callback = searchHash },
1196                         }
1197                 }
1198         },
1199         {
1200                 id = "imdb",
1201                 type = "div",
1202                 content = {
1203                         {
1204                                 { type = "label", value = "Title:"},
1205                                 { type = "text_input", value = openSub.movie.name or "", id = "title", width = 2 },
1206                                 { type = "button", value = "Search by name", callback = searchIMBD }
1207                         },{
1208                                 { type = "label", value = "Season (series):"},
1209                                 { type = "text_input", value = openSub.movie.seasonNumber or "", id = "season", width = 2 }
1210                         },{
1211                                 { type = "label", value = "Episode (series):"},
1212                                 { type = "text_input", value = openSub.movie.episodeNumber or "", id = "episode", width = 2 }
1213                         },{
1214                                 { type = "list", width = 4, id = "mainlist" }
1215                         }
1216                 }
1217         },
1218         {
1219                 type = "div",
1220                 content = {
1221                         {
1222                                 { type = "button", value = "Help", callback = toggle_help },
1223                                 { type = "span", width = 1},
1224                                 { type = "button", value = "Download", callback = download_selection },
1225                                 { type = "button", value = "Close", callback = close }
1226                         }
1227                 }
1228         },
1229         {
1230                 id = "progressBar",
1231                 type = "div",
1232                 content = {
1233                         {
1234                                 { type = "html", width = 4,
1235                                         display = "none",
1236                                         value = ""..
1237                                         " Download subtittles from <a href='http://www.opensubtitles.org/'>opensubtitles.org</a> and display them while watching a video.<br>"..
1238                                         " <br>"..
1239                                         " <b><u>Usage:</u></b><br>"..
1240                                         " <br>"..
1241                                         " 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>"..
1242                                         " <br>"..
1243                                         " Choose the language for your subtitles and click on the button corresponding to one of the two research method provided by VLSub:<br>"..
1244                                         " <br>"..
1245                                         " <b>Method 1: Search by hash</b><br>"..
1246                                         " 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>"..
1247                                         " <br>"..
1248                                         " <b>Method 2: Search by name</b><br>"..
1249                                         " 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>"..
1250                                         " <br>"..
1251                                         " <b>Downloading Subtitles</b><br>"..
1252                                         " Select one subtitle in the list and click on 'Download'.<br>"..
1253                                         " It will be put in the same directory that your video, with the same name (different extension)"..
1254                                         " so Vlc will load them automatically the next time you'll start the video.<br>"..
1255                                         " <br>"..
1256                                         " <b>/!\\ Beware :</b> Existing subtitles are overwrited without asking confirmation, so put them elsewhere if thet're important."
1257                                         , id = "helpMessage"
1258                                 }
1259                         },{
1260                                 {
1261                                         type = "label",
1262                                         width = 4,
1263                                         display = "none",
1264                                         value = "  ",
1265                                         id = "message"
1266                                 }
1267                         }
1268                 }
1269         }
1270 }