· 9 years ago · Mar 05, 2017, 07:32 PM
1--[[
2VLSub Extension for VLC media player 1.1 and 2.0
3Copyright 2013 Guillaume Le Maout
4
5Authors: Guillaume Le Maout
6Contact:
7http://addons.videolan.org/messages/?action=newmessage&username=exebetche
8Bug report: http://addons.videolan.org/content/show.php/?content=148752
9
10This program is free software; you can redistribute it and/or modify
11it under the terms of the GNU General Public License as published by
12the Free Software Foundation; either version 2 of the License, or
13(at your option) any later version.
14
15This program is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23--]]
24
25
26 --[[ Global var ]]--
27
28-- You can set here your default language by replacing nil with
29-- your language code (see below).Example:
30-- language = "fre",
31-- language = "ger",
32-- language = "eng",
33-- ...
34
35local options = {
36 language = nil,
37 downloadBehaviour = 'save',
38 langExt = false,
39 removeTag = false,
40 showMediaInformation = true,
41 progressBarSize = 80,
42 intLang = 'eng',
43 translations_avail = {
44 eng = 'English',
45 cze = 'Czech',
46 dan = 'Danish',
47 dut = 'Nederlands',
48 fre = 'Français',
49 ell = 'Greek',
50 baq = 'Basque',
51 pob = 'Brazilian Portuguese',
52 por = 'Portuguese (Portugal)',
53 rum = 'Romanian',
54 slo = 'Slovak',
55 spa = 'Spanish',
56 swe = 'Swedish',
57 ukr = 'Ukrainian',
58 hun = 'Hungarian'
59 },
60 translation = {
61 int_all = 'All',
62 int_descr = 'Download subtitles from OpenSubtitles.org',
63 int_research = 'Research',
64 int_config = 'Config',
65 int_configuration = 'Configuration',
66 int_help = 'Help',
67 int_search_hash = 'Search by hash',
68 int_search_name = 'Search by name',
69 int_title = 'Title',
70 int_season = 'Season (series)',
71 int_episode = 'Episode (series)',
72 int_show_help = 'Show help',
73 int_show_conf = 'Show config',
74 int_dowload_sel = 'Download selection',
75 int_close = 'Close',
76 int_ok = 'Ok',
77 int_save = 'Save',
78 int_cancel = 'Cancel',
79 int_bool_true = 'Yes',
80 int_bool_false = 'No',
81 int_search_transl = 'Search translations',
82 int_searching_transl = 'Searching translations ...',
83 int_int_lang = 'Interface language',
84 int_default_lang = 'Subtitles language',
85 int_dowload_behav = 'What to do with subtitles',
86 int_dowload_save = 'Load and save',
87 int_dowload_load = 'Load only',
88 int_dowload_manual = 'Manual download',
89 int_display_code = 'Display language code in file name',
90 int_remove_tag = 'Remove tags',
91 int_vlsub_work_dir = 'VLSub working directory',
92 int_os_username = 'Username',
93 int_os_password = 'Password',
94 int_help_mess =[[
95 Download subtitles from
96 <a href='http://www.opensubtitles.org/'>
97 opensubtitles.org
98 </a> and display them while watching a video.<br>
99 <br>
100 <b><u>Usage:</u></b><br>
101 <br>
102 Start your video. If you use Vlsub witout playing a video
103 you will get a link to download the subtitles in your browser
104 but the subtitles won't be saved and loaded automatically.<br>
105 <br>
106 Choose the language for your subtitles and click on the
107 button corresponding to one of the two research methods
108 provided by VLSub:<br>
109 <br>
110 <b>Method 1: Search by hash</b><br>
111 It is recommended to try this method first, because it
112 performs a research based on the video file print, so you
113 can find subtitles synchronized with your video.<br>
114 <br>
115 <b>Method 2: Search by name</b><br>
116 If you have no luck with the first method, just check the
117 title is correct before clicking. If you search subtitles
118 for a series, you can also provide a season and episode
119 number.<br>
120 <br>
121 <b>Downloading Subtitles</b><br>
122 Select one subtitle in the list and click on 'Download'.<br>
123 It will be put in the same directory that your video, with
124 the same name (different extension)
125 so VLC will load them automatically the next time you'll
126 start the video.<br>
127 <br>
128 <b>/!\\ Beware :</b> Existing subtitles are overwritten
129 without asking confirmation, so put them elsewhere if
130 they're important.<br>
131 <br>
132 Find more VLC extensions at
133 <a href='http://addons.videolan.org'>addons.videolan.org</a>.
134 ]],
135 int_no_support_mess = [[
136 <strong>VLSub is not working with Vlc 2.1.x on
137 any platform</strong>
138 because the lua "net" module needed to interact
139 with opensubtitles has been
140 removed in this release for the extensions.
141 <br>
142 <strong>Works with Vlc 2.2 on mac and linux.</strong>
143 <br>
144 <strong>On windows you have to install an older version
145 of Vlc (2.0.8 for example)</strong>
146 to use Vlsub:
147 <br>
148 <a target="_blank" rel="nofollow"
149 href="http://download.videolan.org/pub/videolan/vlc/2.0.8/">
150 http://download.videolan.org/pub/videolan/vlc/2.0.8/</a><br>
151 ]],
152
153 action_login = 'Logging in',
154 action_logout = 'Logging out',
155 action_noop = 'Checking session',
156 action_search = 'Searching subtitles',
157 action_hash = 'Calculating movie hash',
158
159 mess_success = 'Success',
160 mess_error = 'Error',
161 mess_no_response = 'Server not responding',
162 mess_unauthorized = 'Request unauthorized',
163 mess_expired = 'Session expired, retrying',
164 mess_overloaded = 'Server overloaded, please retry later',
165 mess_no_input = 'Please use this method during playing',
166 mess_not_local = 'This method works with local file only (for now)',
167 mess_not_found = 'File not found',
168 mess_not_found2 = 'File not found (illegal character?)',
169 mess_no_selection = 'No subtitles selected',
170 mess_save_fail = 'Unable to save subtitles',
171 mess_click_link = 'Click here to open the file',
172 mess_complete = 'Research complete',
173 mess_no_res = 'No result',
174 mess_res = 'result(s)',
175 mess_loaded = 'Subtitles loaded',
176 mess_not_load = 'Unable to load subtitles',
177 mess_downloading = 'Downloading subtitle',
178 mess_dowload_link = 'Download link',
179 mess_err_conf_access ='Can\'t find a suitable path to save'..
180 'config, please set it manually',
181 mess_err_wrong_path ='the path contains illegal character, '..
182 'please correct it',
183 mess_err_cant_download_interface_translation='could not download interface translation'
184 }
185}
186
187local languages = {
188 {'alb', 'Albanian'},
189 {'ara', 'Arabic'},
190 {'arm', 'Armenian'},
191 {'baq', 'Basque'},
192 {'ben', 'Bengali'},
193 {'bos', 'Bosnian'},
194 {'bre', 'Breton'},
195 {'bul', 'Bulgarian'},
196 {'bur', 'Burmese'},
197 {'cat', 'Catalan'},
198 {'chi', 'Chinese'},
199 {'hrv', 'Croatian'},
200 {'cze', 'Czech'},
201 {'dan', 'Danish'},
202 {'dut', 'Dutch'},
203 {'eng', 'English'},
204 {'epo', 'Esperanto'},
205 {'est', 'Estonian'},
206 {'fin', 'Finnish'},
207 {'fre', 'French'},
208 {'glg', 'Galician'},
209 {'geo', 'Georgian'},
210 {'ger', 'German'},
211 {'ell', 'Greek'},
212 {'heb', 'Hebrew'},
213 {'hin', 'Hindi'},
214 {'hun', 'Hungarian'},
215 {'ice', 'Icelandic'},
216 {'ind', 'Indonesian'},
217 {'ita', 'Italian'},
218 {'jpn', 'Japanese'},
219 {'kaz', 'Kazakh'},
220 {'khm', 'Khmer'},
221 {'kor', 'Korean'},
222 {'lav', 'Latvian'},
223 {'lit', 'Lithuanian'},
224 {'ltz', 'Luxembourgish'},
225 {'mac', 'Macedonian'},
226 {'may', 'Malay'},
227 {'mal', 'Malayalam'},
228 {'mon', 'Mongolian'},
229 {'nor', 'Norwegian'},
230 {'oci', 'Occitan'},
231 {'per', 'Persian'},
232 {'pol', 'Polish'},
233 {'por', 'Portuguese'},
234 {'pob', 'Brazilian Portuguese'},
235 {'rum', 'Romanian'},
236 {'rus', 'Russian'},
237 {'scc', 'Serbian'},
238 {'sin', 'Sinhalese'},
239 {'slo', 'Slovak'},
240 {'slv', 'Slovenian'},
241 {'spa', 'Spanish'},
242 {'swa', 'Swahili'},
243 {'swe', 'Swedish'},
244 {'syr', 'Syriac'},
245 {'tgl', 'Tagalog'},
246 {'tel', 'Telugu'},
247 {'tha', 'Thai'},
248 {'tur', 'Turkish'},
249 {'ukr', 'Ukrainian'},
250 {'urd', 'Urdu'},
251 {'vie', 'Vietnamese'}
252}
253
254-- Languages code conversion table: iso-639-1 to iso-639-3
255-- See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
256local lang_os_to_iso = {
257 sq = "alb",
258 ar = "ara",
259 hy = "arm",
260 eu = "baq",
261 bn = "ben",
262 bs = "bos",
263 br = "bre",
264 bg = "bul",
265 my = "bur",
266 ca = "cat",
267 zh = "chi",
268 hr = "hrv",
269 cs = "cze",
270 da = "dan",
271 nl = "dut",
272 en = "eng",
273 eo = "epo",
274 et = "est",
275 fi = "fin",
276 fr = "fre",
277 gl = "glg",
278 ka = "geo",
279 de = "ger",
280 el = "ell",
281 he = "heb",
282 hi = "hin",
283 hu = "hun",
284 is = "ice",
285 id = "ind",
286 it = "ita",
287 ja = "jpn",
288 kk = "kaz",
289 km = "khm",
290 ko = "kor",
291 lv = "lav",
292 lt = "lit",
293 lb = "ltz",
294 mk = "mac",
295 ms = "may",
296 ml = "mal",
297 mn = "mon",
298 no = "nor",
299 oc = "oci",
300 fa = "per",
301 pl = "pol",
302 pt = "por",
303 po = "pob",
304 ro = "rum",
305 ru = "rus",
306 sr = "scc",
307 si = "sin",
308 sk = "slo",
309 sl = "slv",
310 es = "spa",
311 sw = "swa",
312 sv = "swe",
313 tl = "tgl",
314 te = "tel",
315 th = "tha",
316 tr = "tur",
317 uk = "ukr",
318 ur = "urd",
319 vi = "vie"
320}
321
322local dlg = nil
323local input_table = {} -- General widget id reference
324local select_conf = {} -- Drop down widget / option table association
325
326local app_name = "VLsub";
327local app_version = "0.10.0";
328local app_useragent = app_name.." "..app_version;
329
330 --[[ VLC extension stuff ]]--
331
332function descriptor()
333 return {
334 title = app_useragent,
335 version = app_version,
336 author = "exebetche",
337 url = 'http://www.opensubtitles.org/',
338 shortdesc = app_name;
339 description = options.translation.int_descr,
340 capabilities = {"menu", "input-listener" }
341 }
342end
343
344function activate()
345 vlc.msg.dbg("[VLsub] Welcome")
346
347 if not check_config() then
348 vlc.msg.err("[VLsub] Unsupported VLC version")
349 return false
350 end
351
352 if vlc.input.item() then
353 openSub.getFileInfo()
354 openSub.getMovieInfo()
355 end
356
357 show_main()
358end
359
360function close()
361 vlc.deactivate()
362end
363
364function deactivate()
365 vlc.msg.dbg("[VLsub] Bye bye!")
366 if dlg then
367 dlg:hide()
368 end
369
370 if openSub.session.token and openSub.session.token ~= "" then
371 openSub.request("LogOut")
372 end
373end
374
375function menu()
376 return {
377 lang.int_research,
378 lang.int_config,
379 lang.int_help
380 }
381end
382
383function meta_changed()
384 return false
385end
386
387function input_changed()
388 collectgarbage()
389 set_interface_main()
390 collectgarbage()
391end
392
393 --[[ Interface data ]]--
394
395function interface_main()
396 dlg:add_label(lang["int_default_lang"]..':', 1, 1, 1, 1)
397 input_table['language'] = dlg:add_dropdown(2, 1, 2, 1)
398 dlg:add_button(lang["int_search_hash"],
399 searchHash, 4, 1, 1, 1)
400
401 dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1)
402 input_table['title'] = dlg:add_text_input(
403 openSub.movie.title or "", 2, 2, 2, 1)
404 dlg:add_button(lang["int_search_name"],
405 searchIMBD, 4, 2, 1, 1)
406 dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1)
407 input_table['seasonNumber'] = dlg:add_text_input(
408 openSub.movie.seasonNumber or "", 2, 3, 2, 1)
409 dlg:add_label(lang["int_episode"]..':', 1, 4, 1, 1)
410 input_table['episodeNumber'] = dlg:add_text_input(
411 openSub.movie.episodeNumber or "", 2, 4, 2, 1)
412 input_table['mainlist'] = dlg:add_list(1, 5, 4, 1)
413 input_table['message'] = nil
414 input_table['message'] = dlg:add_label(' ', 1, 6, 4, 1)
415 dlg:add_button(
416 lang["int_show_help"], show_help, 1, 7, 1, 1)
417 dlg:add_button(
418 ' '..lang["int_show_conf"]..' ', show_conf, 2, 7, 1, 1)
419 dlg:add_button(
420 lang["int_dowload_sel"], download_subtitles, 3, 7, 1, 1)
421 dlg:add_button(
422 lang["int_close"], close, 4, 7, 1, 1)
423
424 assoc_select_conf(
425 'language',
426 'language',
427 openSub.conf.languages,
428 2,
429 lang["int_all"])
430
431 display_subtitles()
432end
433
434function set_interface_main()
435 -- Update movie title and co. if video input change
436 if not type(input_table['title']) == 'userdata' then return false end
437
438 openSub.getFileInfo()
439 openSub.getMovieInfo()
440
441 input_table['title']:set_text(
442 openSub.movie.title or "")
443 input_table['episodeNumber']:set_text(
444 openSub.movie.episodeNumber or "")
445 input_table['seasonNumber']:set_text(
446 openSub.movie.seasonNumber or "")
447end
448
449function interface_config()
450 input_table['intLangLab'] = dlg:add_label(
451 lang["int_int_lang"]..':', 1, 1, 1, 1)
452 input_table['intLangBut'] = dlg:add_button(
453 lang["int_search_transl"],
454 get_available_translations, 2, 1, 1, 1)
455 input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1)
456 dlg:add_label(
457 lang["int_default_lang"]..':', 1, 2, 2, 1)
458 input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1)
459 dlg:add_label(
460 lang["int_dowload_behav"]..':', 1, 3, 2, 1)
461 input_table['downloadBehaviour'] = dlg:add_dropdown(3, 3, 1, 1)
462 dlg:add_label(
463 lang["int_display_code"]..':', 1, 4, 0, 1)
464 input_table['langExt'] = dlg:add_dropdown(3, 4, 1, 1)
465 dlg:add_label(
466 lang["int_remove_tag"]..':', 1, 5, 0, 1)
467 input_table['removeTag'] = dlg:add_dropdown(3, 5, 1, 1)
468
469 if openSub.conf.dirPath then
470 if openSub.conf.os == "win" then
471 dlg:add_label(
472 "<a href='file:///"..openSub.conf.dirPath.."'>"..
473 lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
474 else
475 dlg:add_label(
476 "<a href='"..openSub.conf.dirPath.."'>"..
477 lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
478 end
479 else
480 dlg :add_label(
481 lang["int_vlsub_work_dir"], 1, 6, 2, 1)
482 end
483
484 input_table['dir_path'] = dlg:add_text_input(
485 openSub.conf.dirPath, 2, 6, 2, 1)
486
487 dlg:add_label(
488 lang["int_os_username"]..':', 1, 7, 0, 1)
489 input_table['os_username'] = dlg:add_text_input(
490 type(openSub.option.os_username) == "string"
491 and openSub.option.os_username or "", 2, 7, 2, 1)
492 dlg:add_label(
493 lang["int_os_password"]..':', 1, 8, 0, 1)
494 input_table['os_password'] = dlg:add_text_input(
495 type(openSub.option.os_password) == "string"
496 and openSub.option.os_password or "", 2, 8, 2, 1)
497
498 input_table['message'] = nil
499 input_table['message'] = dlg:add_label(' ', 1, 9, 3, 1)
500
501 dlg:add_button(
502 lang["int_cancel"],
503 show_main, 2, 10, 1, 1)
504 dlg:add_button(
505 lang["int_save"],
506 apply_config, 3, 10, 1, 1)
507
508 input_table['langExt']:add_value(
509 lang["int_bool_"..tostring(openSub.option.langExt)], 1)
510 input_table['langExt']:add_value(
511 lang["int_bool_"..tostring(not openSub.option.langExt)], 2)
512 input_table['removeTag']:add_value(
513 lang["int_bool_"..tostring(openSub.option.removeTag)], 1)
514 input_table['removeTag']:add_value(
515 lang["int_bool_"..tostring(not openSub.option.removeTag)], 2)
516
517 assoc_select_conf(
518 'intLang',
519 'intLang',
520 openSub.conf.translations_avail,
521 2)
522 assoc_select_conf(
523 'default_language',
524 'language',
525 openSub.conf.languages,
526 2,
527 lang["int_all"])
528 assoc_select_conf(
529 'downloadBehaviour',
530 'downloadBehaviour',
531 openSub.conf.downloadBehaviours,
532 1)
533end
534
535function interface_help()
536 local help_html = lang["int_help_mess"]
537
538 input_table['help'] = dlg:add_html(
539 help_html, 1, 1, 4, 1)
540 dlg:add_label(
541 string.rep (" ", 100), 1, 2, 3, 1)
542 dlg:add_button(
543 lang["int_ok"], show_main, 4, 2, 1, 1)
544end
545
546function interface_no_support()
547 local no_support_html = lang["int_no_support_mess"]
548
549 input_table['no_support'] = dlg:add_html(
550 no_support_html, 1, 1, 4, 1)
551 dlg:add_label(
552 string.rep (" ", 100), 1, 2, 3, 1)
553end
554
555function trigger_menu(dlg_id)
556 if dlg_id == 1 then
557 close_dlg()
558 dlg = vlc.dialog(
559 openSub.conf.useragent)
560 interface_main()
561 elseif dlg_id == 2 then
562 close_dlg()
563 dlg = vlc.dialog(
564 openSub.conf.useragent..': '..lang["int_configuration"])
565 interface_config()
566 elseif dlg_id == 3 then
567 close_dlg()
568 dlg = vlc.dialog(
569 openSub.conf.useragent..': '..lang["int_help"])
570 interface_help()
571 end
572 collectgarbage() --~ !important
573end
574
575function show_main()
576 trigger_menu(1)
577end
578
579function show_conf()
580 trigger_menu(2)
581end
582
583function show_help()
584 trigger_menu(3)
585end
586
587function close_dlg()
588 vlc.msg.dbg("[VLSub] Closing dialog")
589
590 if dlg ~= nil then
591 --~ dlg:delete() -- Throw an error
592 dlg:hide()
593 end
594
595 dlg = nil
596 input_table = nil
597 input_table = {}
598 collectgarbage() --~ !important
599end
600
601 --[[ Drop down / config association]]--
602
603function assoc_select_conf(select_id, option, conf, ind, default)
604-- Helper for i/o interaction between drop down and option list
605 select_conf[select_id] = {
606 cf = conf,
607 opt = option,
608 dflt = default,
609 ind = ind
610 }
611 set_default_option(select_id)
612 display_select(select_id)
613end
614
615function set_default_option(select_id)
616-- Put the selected option of a list in first place of the associated table
617 local opt = select_conf[select_id].opt
618 local cfg = select_conf[select_id].cf
619 local ind = select_conf[select_id].ind
620 if openSub.option[opt] then
621 table.sort(cfg, function(a, b)
622 if a[1] == openSub.option[opt] then
623 return true
624 elseif b[1] == openSub.option[opt] then
625 return false
626 else
627 return a[ind] < b[ind]
628 end
629 end)
630 end
631end
632
633function display_select(select_id)
634-- Display the drop down values with an optional default value at the top
635 local conf = select_conf[select_id].cf
636 local opt = select_conf[select_id].opt
637 local option = openSub.option[opt]
638 local default = select_conf[select_id].dflt
639 local default_isset = false
640
641 if not default then
642 default_isset = true
643 end
644
645 for k, l in ipairs(conf) do
646 if default_isset then
647 input_table[select_id]:add_value(l[2], k)
648 else
649 if option then
650 input_table[select_id]:add_value(l[2], k)
651 input_table[select_id]:add_value(default, 0)
652 else
653 input_table[select_id]:add_value(default, 0)
654 input_table[select_id]:add_value(l[2], k)
655 end
656 default_isset = true
657 end
658 end
659end
660
661 --[[ Config & interface localization]]--
662
663function check_config()
664 -- Make a copy of english translation to use it as default
665 -- in case some element aren't translated in other translations
666 eng_translation = {}
667 for k, v in pairs(openSub.option.translation) do
668 eng_translation[k] = v
669 end
670
671 -- Get available translation full name from code
672 trsl_names = {}
673 for i, lg in ipairs(languages) do
674 trsl_names[lg[1]] = lg[2]
675 end
676
677 if is_window_path(vlc.config.datadir()) then
678 openSub.conf.os = "win"
679 slash = "\\"
680 else
681 openSub.conf.os = "lin"
682 slash = "/"
683 end
684
685 local path_generic = {"lua", "extensions", "userdata", "vlsub"}
686 local dirPath = slash..table.concat(path_generic, slash)
687 local filePath = slash.."vlsub_conf.xml"
688 local config_saved = false
689 sub_dir = slash.."vlsub_subtitles"
690
691 -- Check if config file path is stored in vlc config
692 local other_dirs = {}
693
694 for path in
695 vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
696 if path:match(".*"..sub_dir.."$") then
697 openSub.conf.dirPath = path:gsub(
698 "%s*(.*)"..sub_dir.."%s*$", "%1")
699 config_saved = true
700 end
701 table.insert(other_dirs, path)
702 end
703
704 -- if not stored in vlc config
705 -- try to find a suitable config file path
706
707 if openSub.conf.dirPath then
708 if not is_dir(openSub.conf.dirPath) and
709 (openSub.conf.os == "lin" or
710 is_win_safe(openSub.conf.dirPath)) then
711 mkdir_p(openSub.conf.dirPath)
712 end
713 else
714 local userdatadir = vlc.config.userdatadir()
715 local datadir = vlc.config.datadir()
716
717 -- check if the config already exist
718 if file_exist(userdatadir..dirPath..filePath) then
719 -- in vlc.config.userdatadir()
720 openSub.conf.dirPath = userdatadir..dirPath
721 config_saved = true
722 elseif file_exist(datadir..dirPath..filePath) then
723 -- in vlc.config.datadir()
724 openSub.conf.dirPath = datadir..dirPath
725 config_saved = true
726 else
727 -- if not found determine an accessible path
728 local extension_path = slash..path_generic[1]
729 ..slash..path_generic[2]
730
731 -- use the same folder as the extension if accessible
732 if is_dir(userdatadir..extension_path)
733 and file_touch(userdatadir..dirPath..filePath) then
734 openSub.conf.dirPath = userdatadir..dirPath
735 elseif file_touch(datadir..dirPath..filePath) then
736 openSub.conf.dirPath = datadir..dirPath
737 end
738
739 -- try to create working dir in user folder
740 if not openSub.conf.dirPath
741 and is_dir(userdatadir) then
742 if not is_dir(userdatadir..dirPath) then
743 mkdir_p(userdatadir..dirPath)
744 end
745 if is_dir(userdatadir..dirPath) and
746 file_touch(userdatadir..dirPath..filePath) then
747 openSub.conf.dirPath = userdatadir..dirPath
748 end
749 end
750
751 -- try to create working dir in vlc folder
752 if not openSub.conf.dirPath and
753 is_dir(datadir) then
754 if not is_dir(datadir..dirPath) then
755 mkdir_p(datadir..dirPath)
756 end
757 if file_touch(datadir..dirPath..filePath) then
758 openSub.conf.dirPath = datadir..dirPath
759 end
760 end
761 end
762 end
763
764 if openSub.conf.dirPath then
765 vlc.msg.dbg("[VLSub] Working directory: " ..
766 (openSub.conf.dirPath or "not found"))
767
768 openSub.conf.filePath = openSub.conf.dirPath..filePath
769 openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"
770
771 if config_saved
772 and file_exist(openSub.conf.filePath) then
773 vlc.msg.dbg(
774 "[VLSub] Loading config file: "..openSub.conf.filePath)
775 load_config()
776 else
777 vlc.msg.dbg("[VLSub] No config file")
778 getenv_lang()
779 config_saved = save_config()
780 if not config_saved then
781 vlc.msg.dbg("[VLSub] Unable to save config")
782 end
783 end
784
785 -- Check presence of a translation file
786 -- in "%vlsub_directory%/locale"
787 -- Add translation files to available translation list
788 local file_list = list_dir(openSub.conf.localePath)
789 local translations_avail = openSub.conf.translations_avail
790
791 if file_list then
792 for i, file_name in ipairs(file_list) do
793 local lg = string.gsub(
794 file_name,
795 "^(%w%w%w).xml$",
796 "%1")
797 if lg
798 and not openSub.option.translations_avail[lg] then
799 table.insert(translations_avail, {
800 lg,
801 trsl_names[lg]
802 })
803 end
804 end
805 end
806
807 -- Load selected translation from file
808 if openSub.option.intLang ~= "eng"
809 and not openSub.conf.translated
810 then
811 local transl_file_path = openSub.conf.localePath..
812 slash..openSub.option.intLang..".xml"
813 if file_exist(transl_file_path) then
814 vlc.msg.dbg(
815 "[VLSub] Loading translation from file: "..
816 transl_file_path)
817 load_transl(transl_file_path)
818 end
819 end
820 else
821 vlc.msg.dbg("[VLSub] Unable to find a suitable path"..
822 "to save config, please set it manually")
823 end
824
825 lang = nil
826 lang = options.translation -- just a short cut
827
828 if not vlc.net or not vlc.net.poll then
829 dlg = vlc.dialog(
830 openSub.conf.useragent..': '..lang["mess_error"])
831 interface_no_support()
832 dlg:show()
833 return false
834 end
835
836 SetDownloadBehaviours()
837 if not openSub.conf.dirPath then
838 setError(lang["mess_err_conf_access"])
839 end
840
841 -- Set table list of available translations from assoc. array
842 -- so it is sortable
843
844 for k, l in pairs(openSub.option.translations_avail) do
845 if k == openSub.option.int_research then
846 table.insert(openSub.conf.translations_avail, 1, {k, l})
847 else
848 table.insert(openSub.conf.translations_avail, {k, l})
849 end
850 end
851 collectgarbage()
852 return true
853end
854
855function load_config()
856-- Overwrite default conf with loaded conf
857 local tmpFile = io.open(openSub.conf.filePath, "rb")
858 if not tmpFile then return false end
859 local resp = tmpFile:read("*all")
860 tmpFile:flush()
861 tmpFile:close()
862 local option = parse_xml(resp)
863
864 for key, value in pairs(option) do
865 if type(value) == "table" then
866 if key == "translation" then
867 openSub.conf.translated = true
868 for k, v in pairs(value) do
869 openSub.option.translation[k] = v
870 end
871 else
872 openSub.option[key] = value
873 end
874 else
875 if value == "true" then
876 openSub.option[key] = true
877 elseif value == "false" then
878 openSub.option[key] = false
879 else
880 openSub.option[key] = value
881 end
882 end
883 end
884 collectgarbage()
885end
886
887function load_transl(path)
888-- Overwrite default conf with loaded conf
889 local tmpFile = assert(io.open(path, "rb"))
890 local resp = tmpFile:read("*all")
891 tmpFile:flush()
892 tmpFile:close()
893 openSub.option.translation = nil
894
895 openSub.option.translation = parse_xml(resp)
896 collectgarbage()
897end
898
899function apply_translation()
900-- Overwrite default conf with loaded conf
901 for k, v in pairs(eng_translation) do
902 if not openSub.option.translation[k] then
903 openSub.option.translation[k] = eng_translation[k]
904 end
905 end
906end
907
908function getenv_lang()
909-- Retrieve the user OS language
910 local os_lang = os.getenv("LANG")
911
912 if os_lang then -- unix, mac
913 os_lang = string.sub(os_lang, 0, 2)
914 if type(lang_os_to_iso[os_lang]) then
915 openSub.option.language = lang_os_to_iso[os_lang]
916 end
917 else -- Windows
918 local lang_w = string.match(
919 os.setlocale("", "collate"),
920 "^[^_]+")
921 for i, v in ipairs(openSub.conf.languages) do
922 if v[2] == lang_w then
923 openSub.option.language = v[1]
924 end
925 end
926 end
927end
928
929function apply_config()
930-- Apply user config selection to local config
931 local lg_sel = input_table['intLang']:get_value()
932 local sel_val
933 local opt
934 local sel_cf
935
936 if lg_sel and lg_sel ~= 1
937 and openSub.conf.translations_avail[lg_sel] then
938 local lg = openSub.conf.translations_avail[lg_sel][1]
939 if not set_translation(lg) then
940 vlc.msg.err("[VLSub] Couldn't not set translation")
941 return false
942 end
943 SetDownloadBehaviours()
944 end
945
946 for select_id, v in pairs(select_conf) do
947 if input_table[select_id]
948 and select_conf[select_id] then
949 sel_val = input_table[select_id]:get_value()
950 sel_cf = select_conf[select_id]
951 opt = sel_cf.opt
952
953 if sel_val == 0 then
954 openSub.option[opt] = nil
955 else
956 openSub.option[opt] = sel_cf.cf[sel_val][1]
957 end
958
959 set_default_option(select_id)
960 end
961 end
962
963
964 openSub.option.os_username = input_table['os_username']:get_text()
965 openSub.option.os_password = input_table['os_password']:get_text()
966
967 if input_table["langExt"]:get_value() == 2 then
968 openSub.option.langExt = not openSub.option.langExt
969 end
970
971 if input_table["removeTag"]:get_value() == 2 then
972 openSub.option.removeTag = not openSub.option.removeTag
973 end
974
975 -- Set a custom working directory
976 local dir_path = input_table['dir_path']:get_text()
977 local dir_path_err = false
978 if trim(dir_path) == "" then dir_path = nil end
979
980 if dir_path ~= openSub.conf.dirPath then
981 if openSub.conf.os == "lin"
982 or is_win_safe(dir_path)
983 or not dir_path then
984 local other_dirs = {}
985
986 for path in
987 vlc.config.get(
988 "sub-autodetect-path"):gmatch("[^,]+"
989 ) do
990 path = trim(path)
991 if path ~= (openSub.conf.dirPath or "")..sub_dir then
992 table.insert(other_dirs, path)
993 end
994 end
995 openSub.conf.dirPath = dir_path
996 if dir_path then
997 table.insert(other_dirs,
998 string.gsub(dir_path, "^(.-)[\\/]?$", "%1")..sub_dir)
999
1000 if not is_dir(dir_path) then
1001 mkdir_p(dir_path)
1002 end
1003
1004 openSub.conf.filePath = openSub.conf.dirPath..
1005 slash.."vlsub_conf.xml"
1006 openSub.conf.localePath = openSub.conf.dirPath..
1007 slash.."locale"
1008 else
1009 openSub.conf.filePath = nil
1010 openSub.conf.localePath = nil
1011 end
1012 vlc.config.set(
1013 "sub-autodetect-path",
1014 table.concat(other_dirs, ", "))
1015 else
1016 dir_path_err = true
1017 setError(lang["mess_err_wrong_path"]..
1018 "<br><b>"..
1019 string.gsub(
1020 dir_path,
1021 "[^%:%w%p%s§¤]+",
1022 "<span style='color:#B23'>%1</span>"
1023 )..
1024 "</b>")
1025 end
1026 end
1027
1028 if openSub.conf.dirPath and
1029 not dir_path_err then
1030 local config_saved = save_config()
1031 trigger_menu(1)
1032 if not config_saved then
1033 setError(lang["mess_err_conf_access"])
1034 end
1035 else
1036 setError(lang["mess_err_conf_access"])
1037 end
1038end
1039
1040function save_config()
1041-- Dump local config into config file
1042 if openSub.conf.dirPath
1043 and openSub.conf.filePath then
1044 vlc.msg.dbg(
1045 "[VLSub] Saving config file: "..
1046 openSub.conf.filePath)
1047
1048 if file_touch(openSub.conf.filePath) then
1049 local tmpFile = assert(
1050 io.open(openSub.conf.filePath, "wb"))
1051 local resp = dump_xml(openSub.option)
1052 tmpFile:write(resp)
1053 tmpFile:flush()
1054 tmpFile:close()
1055 tmpFile = nil
1056 else
1057 return false
1058 end
1059 collectgarbage()
1060 return true
1061 else
1062 vlc.msg.dbg("[VLSub] Unable fount a suitable path "..
1063 "to save config, please set it manually")
1064 setError(lang["mess_err_conf_access"])
1065 return false
1066 end
1067end
1068
1069function SetDownloadBehaviours()
1070 openSub.conf.downloadBehaviours = nil
1071 openSub.conf.downloadBehaviours = {
1072 {'save', lang["int_dowload_save"]},
1073 {'manual', lang["int_dowload_manual"]}
1074 }
1075end
1076
1077function get_available_translations()
1078-- Get all available translation files from the internet
1079-- (drop previous direct download from github repo
1080-- causing error with github https CA certficate on OS X an XP)
1081-- https://github.com/exebetche/vlsub/tree/master/locale
1082
1083 local translations_url = "http://addons.videolan.org/CONTENT/"..
1084 "content-files/148752-vlsub_translations.xml"
1085
1086 if input_table['intLangBut']:get_text() == lang["int_search_transl"]
1087 then
1088 openSub.actionLabel = lang["int_searching_transl"]
1089
1090 local translations_content = get(translations_url)
1091 if not translations_content then
1092 collectgarbage()
1093 return false
1094 end
1095 local translations_avail = openSub.option.translations_avail
1096 all_trsl = parse_xml(translations_content)
1097 local lg, trsl
1098
1099 for lg, trsl in pairs(all_trsl) do
1100 if lg ~= options.intLang[1]
1101 and not translations_avail[lg] then
1102 translations_avail[lg] = trsl_names[lg] or ""
1103 table.insert(openSub.conf.translations_avail, {
1104 lg,
1105 trsl_names[lg]
1106 })
1107 input_table['intLang']:add_value(
1108 trsl_names[lg],
1109 #openSub.conf.translations_avail)
1110 end
1111 end
1112
1113 setMessage(success_tag(lang["mess_complete"]))
1114 collectgarbage()
1115 end
1116 return true
1117end
1118
1119function set_translation(lg)
1120 openSub.option.translation = nil
1121 openSub.option.translation = {}
1122
1123 if lg == 'eng' then
1124 for k, v in pairs(eng_translation) do
1125 openSub.option.translation[k] = v
1126 end
1127 else
1128 -- If translation file exists in /locale directory load it
1129 if openSub.conf.localePath
1130 and file_exist(openSub.conf.localePath..
1131 slash..lg..".xml") then
1132 local transl_file_path = openSub.conf.localePath..
1133 slash..lg..".xml"
1134 vlc.msg.dbg("[VLSub] Loading translation from file: "..
1135 transl_file_path)
1136 load_transl(transl_file_path)
1137 apply_translation()
1138 else
1139 -- Load translation file from internet
1140 if not all_trsl and not get_available_translations() then
1141 setMessage(error_tag(lang["mess_err_cant_download_interface_translation"]))
1142 return false
1143 end
1144
1145 if not all_trsl or not all_trsl[lg] then
1146 vlc.msg.dbg("[VLSub] Error, translation not found")
1147 return false
1148 end
1149 openSub.option.translation = all_trsl[lg]
1150 apply_translation()
1151 all_trsl = nil
1152 end
1153 end
1154
1155 lang = nil
1156 lang = openSub.option.translation
1157 collectgarbage()
1158 return true
1159end
1160
1161 --[[ Core ]]--
1162
1163openSub = {
1164 itemStore = nil,
1165 actionLabel = "",
1166 conf = {
1167 url = "http://api.opensubtitles.org/xml-rpc",
1168 path = nil,
1169 HTTPVersion = "1.1",
1170 userAgentHTTP = app_useragent,
1171 useragent = app_useragent,
1172 translations_avail = {},
1173 downloadBehaviours = nil,
1174 languages = languages
1175 },
1176 option = options,
1177 session = {
1178 loginTime = 0,
1179 token = ""
1180 },
1181 file = {
1182 hasInput = false,
1183 uri = nil,
1184 ext = nil,
1185 name = nil,
1186 path = nil,
1187 protocol = nil,
1188 cleanName = nil,
1189 dir = nil,
1190 hash = nil,
1191 bytesize = nil,
1192 fps = nil,
1193 timems = nil,
1194 frames = nil
1195 },
1196 movie = {
1197 title = "",
1198 seasonNumber = "",
1199 episodeNumber = "",
1200 sublanguageid = ""
1201 },
1202 request = function(methodName)
1203 local params = openSub.methods[methodName].params()
1204 local reqTable = openSub.getMethodBase(methodName, params)
1205 local request = "<?xml version='1.0'?>"..dump_xml(reqTable)
1206 local host, path = parse_url(openSub.conf.url)
1207 local header = {
1208 "POST "..path.." HTTP/"..openSub.conf.HTTPVersion,
1209 "Host: "..host,
1210 "User-Agent: "..openSub.conf.userAgentHTTP,
1211 "Content-Type: text/xml",
1212 "Content-Length: "..string.len(request),
1213 "",
1214 ""
1215 }
1216 request = table.concat(header, "\r\n")..request
1217
1218 local response
1219 local status, responseStr = http_req(host, 80, request)
1220
1221 if status == 200 then
1222 response = parse_xmlrpc(responseStr)
1223
1224 if response then
1225 if response.status == "200 OK" then
1226 return openSub.methods[methodName]
1227 .callback(response)
1228 elseif response.status == "406 No session" then
1229 openSub.request("LogIn")
1230 elseif response then
1231 setError("code '"..
1232 response.status..
1233 "' ("..status..")")
1234 return false
1235 end
1236 else
1237 setError("Server not responding")
1238 return false
1239 end
1240 elseif status == 401 then
1241 setError("Request unauthorized")
1242 response = parse_xmlrpc(responseStr)
1243 if openSub.session.token ~= response.token then
1244 setMessage("Session expired, retrying")
1245 openSub.session.token = response.token
1246 openSub.request(methodName)
1247 end
1248 return false
1249 elseif status == 503 then
1250 setError("Server overloaded, please retry later")
1251 return false
1252 end
1253
1254 end,
1255 getMethodBase = function(methodName, param)
1256 if openSub.methods[methodName].methodName then
1257 methodName = openSub.methods[methodName].methodName
1258 end
1259
1260 local request = {
1261 methodCall={
1262 methodName=methodName,
1263 params={ param=param }}}
1264
1265 return request
1266 end,
1267 methods = {
1268 LogIn = {
1269 params = function()
1270 openSub.actionLabel = lang["action_login"]
1271 return {
1272 { value={ string=openSub.option.os_username } },
1273 { value={ string=openSub.option.os_password } },
1274 { value={ string=openSub.movie.sublanguageid } },
1275 { value={ string=openSub.conf.useragent } }
1276 }
1277 end,
1278 callback = function(resp)
1279 openSub.session.token = resp.token
1280 openSub.session.loginTime = os.time()
1281 return true
1282 end
1283 },
1284 LogOut = {
1285 params = function()
1286 openSub.actionLabel = lang["action_logout"]
1287 return {
1288 { value={ string=openSub.session.token } }
1289 }
1290 end,
1291 callback = function()
1292 return true
1293 end
1294 },
1295 NoOperation = {
1296 params = function()
1297 openSub.actionLabel = lang["action_noop"]
1298 return {
1299 { value={ string=openSub.session.token } }
1300 }
1301 end,
1302 callback = function(resp)
1303 return true
1304 end
1305 },
1306 SearchSubtitlesByHash = {
1307 methodName = "SearchSubtitles",
1308 params = function()
1309 openSub.actionLabel = lang["action_search"]
1310 setMessage(openSub.actionLabel..": "..
1311 progressBarContent(0))
1312
1313 return {
1314 { value={ string=openSub.session.token } },
1315 { value={
1316 array={
1317 data={
1318 value={
1319 struct={
1320 member={
1321 { name="sublanguageid", value={
1322 string=openSub.movie.sublanguageid }
1323 },
1324 { name="moviehash", value={
1325 string=openSub.file.hash } },
1326 { name="moviebytesize", value={
1327 double=openSub.file.bytesize } }
1328 }}}}}}}
1329 }
1330 end,
1331 callback = function(resp)
1332 openSub.itemStore = resp.data
1333 end
1334 },
1335 SearchSubtitles = {
1336 methodName = "SearchSubtitles",
1337 params = function()
1338 openSub.actionLabel = lang["action_search"]
1339 setMessage(openSub.actionLabel..": "..
1340 progressBarContent(0))
1341
1342 local member = {
1343 { name="sublanguageid", value={
1344 string=openSub.movie.sublanguageid } },
1345 { name="query", value={
1346 string=openSub.movie.title } } }
1347
1348
1349 if openSub.movie.seasonNumber ~= nil then
1350 table.insert(member, { name="season", value={
1351 string=openSub.movie.seasonNumber } })
1352 end
1353
1354 if openSub.movie.episodeNumber ~= nil then
1355 table.insert(member, { name="episode", value={
1356 string=openSub.movie.episodeNumber } })
1357 end
1358
1359 return {
1360 { value={ string=openSub.session.token } },
1361 { value={
1362 array={
1363 data={
1364 value={
1365 struct={
1366 member=member
1367 }}}}}}
1368 }
1369 end,
1370 callback = function(resp)
1371 openSub.itemStore = resp.data
1372 end
1373 },
1374 SearchSubtitles2 = {
1375 methodName = "SearchSubtitles",
1376 params = function()
1377 openSub.actionLabel = lang["action_search"]
1378 setMessage(openSub.actionLabel..": "..
1379 progressBarContent(0))
1380
1381 local member = {
1382 { name="sublanguageid", value={
1383 string=openSub.movie.sublanguageid } },
1384 { name="tag", value={
1385 string=openSub.file.completeName } } }
1386
1387 return {
1388 { value={ string=openSub.session.token } },
1389 { value={
1390 array={
1391 data={
1392 value={
1393 struct={
1394 member=member
1395 }}}}}}
1396 }
1397 end,
1398 callback = function(resp)
1399 openSub.itemStore = resp.data
1400 end
1401 }
1402 },
1403 getInputItem = function()
1404 return vlc.item or vlc.input.item()
1405 end,
1406 getFileInfo = function()
1407 -- Get video file path, name, extension from input uri
1408 local item = openSub.getInputItem()
1409 local file = openSub.file
1410 if not item then
1411 file.hasInput = false;
1412 file.cleanName = nil;
1413 file.protocol = nil;
1414 file.path = nil;
1415 file.ext = nil;
1416 file.uri = nil;
1417 else
1418 vlc.msg.dbg("[VLSub] Video URI: "..item:uri())
1419 local parsed_uri = vlc.net.url_parse(item:uri())
1420 file.uri = item:uri()
1421 file.protocol = parsed_uri["protocol"]
1422 file.path = parsed_uri["path"]
1423
1424 -- Corrections
1425
1426 -- For windows
1427 file.path = string.match(file.path, "^/(%a:/.+)$") or file.path
1428
1429 -- For file in archive
1430 local archive_path, name_in_archive = string.match(
1431 file.path, '^([^!]+)!/([^!/]*)$')
1432 if archive_path and archive_path ~= "" then
1433 file.path = string.gsub(
1434 archive_path,
1435 '\063',
1436 '%%')
1437 file.path = vlc.strings.decode_uri(file.path)
1438 file.completeName = string.gsub(
1439 name_in_archive,
1440 '\063',
1441 '%%')
1442 file.completeName = vlc.strings.decode_uri(
1443 file.completeName)
1444 file.is_archive = true
1445 else -- "classic" input
1446 file.path = vlc.strings.decode_uri(file.path)
1447 file.dir, file.completeName = string.match(
1448 file.path,
1449 '^(.+/)([^/]*)$')
1450
1451 local file_stat = vlc.net.stat(file.path)
1452 if file_stat
1453 then
1454 file.stat = file_stat
1455 end
1456
1457 file.is_archive = false
1458 end
1459
1460 file.name, file.ext = string.match(
1461 file.completeName,
1462 '^([^/]-)%.?([^%.]*)$')
1463
1464 if file.ext == "part" then
1465 file.name, file.ext = string.match(
1466 file.name,
1467 '^([^/]+)%.([^%.]+)$')
1468 end
1469
1470 file.hasInput = true;
1471 file.cleanName = string.gsub(
1472 file.name,
1473 "[%._]", " ")
1474 vlc.msg.dbg("[VLSub] file info "..(dump_xml(file)))
1475 end
1476 collectgarbage()
1477 end,
1478 getMovieInfo = function()
1479 -- Clean video file name and check for season/episode pattern in title
1480 if not openSub.file.name then
1481 openSub.movie.title = ""
1482 openSub.movie.seasonNumber = ""
1483 openSub.movie.episodeNumber = ""
1484 return false
1485 end
1486
1487 local showName, seasonNumber, episodeNumber = string.match(
1488 openSub.file.cleanName,
1489 "(.+)[sS](%d?%d)[eE](%d%d).*")
1490
1491 if not showName then
1492 showName, seasonNumber, episodeNumber = string.match(
1493 openSub.file.cleanName,
1494 "(.-)(%d?%d)[xX](%d%d).*")
1495 end
1496
1497 if showName then
1498 openSub.movie.title = showName
1499 openSub.movie.seasonNumber = seasonNumber
1500 openSub.movie.episodeNumber = episodeNumber
1501 else
1502 openSub.movie.title = openSub.file.cleanName
1503 openSub.movie.seasonNumber = ""
1504 openSub.movie.episodeNumber = ""
1505 end
1506 collectgarbage()
1507 end,
1508 getMovieHash = function()
1509 -- Calculate movie hash
1510 openSub.actionLabel = lang["action_hash"]
1511 setMessage(openSub.actionLabel..": "..
1512 progressBarContent(0))
1513
1514 local item = openSub.getInputItem()
1515
1516 if not item then
1517 setError(lang["mess_no_input"])
1518 return false
1519 end
1520
1521 openSub.getFileInfo()
1522
1523 if not openSub.file.path then
1524 setError(lang["mess_not_found"])
1525 return false
1526 end
1527
1528 local data_start = ""
1529 local data_end = ""
1530 local size
1531 local chunk_size = 65536
1532
1533 -- Get data for hash calculation
1534 if openSub.file.is_archive then
1535 vlc.msg.dbg("[VLSub] Read hash data from stream")
1536
1537 local file = vlc.stream(openSub.file.uri)
1538 local dataTmp1 = ""
1539 local dataTmp2 = ""
1540 size = chunk_size
1541
1542 data_start = file:read(chunk_size)
1543
1544 while data_end do
1545 size = size + string.len(data_end)
1546 dataTmp1 = dataTmp2
1547 dataTmp2 = data_end
1548 data_end = file:read(chunk_size)
1549 collectgarbage()
1550 end
1551 data_end = string.sub((dataTmp1..dataTmp2), -chunk_size)
1552 elseif not file_exist(openSub.file.path)
1553 and openSub.file.stat then
1554 vlc.msg.dbg("[VLSub] Read hash data from stream")
1555
1556 local file = vlc.stream(openSub.file.uri)
1557
1558 if not file then
1559 vlc.msg.dbg("[VLSub] No stream")
1560 return false
1561 end
1562
1563 size = openSub.file.stat.size
1564 local decal = size%chunk_size
1565
1566 data_start = file:read(chunk_size)
1567
1568 -- "Seek" to the end
1569 file:read(decal)
1570
1571 for i = 1, math.floor(((size-decal)/chunk_size))-2 do
1572 file:read(chunk_size)
1573 end
1574
1575 data_end = file:read(chunk_size)
1576
1577 file = nil
1578 else
1579 vlc.msg.dbg("[VLSub] Read hash data from file")
1580 local file = io.open( openSub.file.path, "rb")
1581 if not file then
1582 vlc.msg.dbg("[VLSub] No stream")
1583 return false
1584 end
1585
1586 data_start = file:read(chunk_size)
1587 size = file:seek("end", -chunk_size) + chunk_size
1588 data_end = file:read(chunk_size)
1589 file = nil
1590 end
1591
1592 -- Hash calculation
1593 local lo = size
1594 local hi = 0
1595 local o,a,b,c,d,e,f,g,h
1596 local hash_data = data_start..data_end
1597 local max_size = 4294967296
1598 local overflow
1599
1600 for i = 1, #hash_data, 8 do
1601 a,b,c,d,e,f,g,h = hash_data:byte(i,i+7)
1602 lo = lo + a + b*256 + c*65536 + d*16777216
1603 hi = hi + e + f*256 + g*65536 + h*16777216
1604
1605 if lo > max_size then
1606 overflow = math.floor(lo/max_size)
1607 lo = lo-(overflow*max_size)
1608 hi = hi+overflow
1609 end
1610
1611 if hi > max_size then
1612 overflow = math.floor(hi/max_size)
1613 hi = hi-(overflow*max_size)
1614 end
1615 end
1616
1617 openSub.file.bytesize = size
1618 openSub.file.hash = string.format("%08x%08x", hi,lo)
1619 vlc.msg.dbg("[VLSub] Video hash: "..openSub.file.hash)
1620 vlc.msg.dbg("[VLSub] Video bytesize: "..size)
1621 collectgarbage()
1622 return true
1623 end,
1624 checkSession = function()
1625
1626 if openSub.session.token == "" then
1627 openSub.request("LogIn")
1628 else
1629 openSub.request("NoOperation")
1630 end
1631 end
1632}
1633
1634function searchHash()
1635 local sel = input_table["language"]:get_value()
1636 if sel == 0 then
1637 openSub.movie.sublanguageid = 'all'
1638 else
1639 openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
1640 end
1641
1642 openSub.getMovieHash()
1643
1644 if openSub.file.hash then
1645 openSub.checkSession()
1646 openSub.request("SearchSubtitlesByHash")
1647 display_subtitles()
1648 end
1649end
1650
1651function searchIMBD()
1652 openSub.movie.title = trim(input_table["title"]:get_text())
1653 openSub.movie.seasonNumber = tonumber(
1654 input_table["seasonNumber"]:get_text())
1655 openSub.movie.episodeNumber = tonumber(
1656 input_table["episodeNumber"]:get_text())
1657
1658 local sel = input_table["language"]:get_value()
1659 if sel == 0 then
1660 openSub.movie.sublanguageid = 'all'
1661 else
1662 openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
1663 end
1664
1665 if openSub.movie.title ~= "" then
1666 openSub.checkSession()
1667 openSub.request("SearchSubtitles")
1668 display_subtitles()
1669 end
1670end
1671
1672function display_subtitles()
1673 local mainlist = input_table["mainlist"]
1674 mainlist:clear()
1675
1676 if openSub.itemStore == "0" then
1677 mainlist:add_value(lang["mess_no_res"], 1)
1678 setMessage("<b>"..lang["mess_complete"]..":</b> "..
1679 lang["mess_no_res"])
1680 elseif openSub.itemStore then
1681 for i, item in ipairs(openSub.itemStore) do
1682 mainlist:add_value(
1683 (item.SubFileName or "???")..
1684 " ["..(item.SubLanguageID or "?").."]"..
1685 " ("..(item.SubSumCD or "?").." CD)", i)
1686 end
1687 setMessage("<b>"..lang["mess_complete"]..":</b> "..
1688 #(openSub.itemStore).." "..lang["mess_res"])
1689 end
1690end
1691
1692function get_first_sel(list)
1693 local selection = list:get_selection()
1694 for index, name in pairs(selection) do
1695 return index
1696 end
1697 return 0
1698end
1699
1700function download_subtitles()
1701 local index = get_first_sel(input_table["mainlist"])
1702
1703 if index == 0 then
1704 setMessage(lang["mess_no_selection"])
1705 return false
1706 end
1707
1708 openSub.actionLabel = lang["mess_downloading"]
1709
1710 local item = openSub.itemStore[index]
1711
1712 if openSub.option.downloadBehaviour == 'manual'
1713 or not openSub.file.hasInput then
1714 local link = "<span style='color:#181'>"
1715 link = link.."<b>"..lang["mess_dowload_link"]..":</b>"
1716 link = link.."</span> "
1717 link = link.."</span> <a href='"..
1718 item.ZipDownloadLink.."'>"
1719 link = link..item.MovieReleaseName.."</a>"
1720
1721 setMessage(link)
1722 return false
1723 end
1724
1725 local message = ""
1726 local subfileName = openSub.file.name or ""
1727
1728 if openSub.option.langExt then
1729 subfileName = subfileName.."."..item.SubLanguageID
1730 end
1731
1732 subfileName = subfileName.."."..item.SubFormat
1733 local tmp_dir
1734 local file_target_access = true
1735
1736 if is_dir(openSub.file.dir) then
1737 tmp_dir = openSub.file.dir
1738 elseif openSub.conf.dirPath then
1739 tmp_dir = openSub.conf.dirPath
1740
1741 message = "<br>"..error_tag(lang["mess_save_fail"].." "..
1742 "<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
1743 lang["mess_click_link"].."</a>")
1744 else
1745 setError(lang["mess_save_fail"].." "..
1746 "<a href='"..item.ZipDownloadLink.."'>"..
1747 lang["mess_click_link"].."</a>")
1748 return false
1749 end
1750
1751 local tmpFileURI, tmpFileName = dump_zip(
1752 item.ZipDownloadLink,
1753 tmp_dir,
1754 item.SubFileName)
1755
1756 vlc.msg.dbg("[VLsub] tmpFileName: "..tmpFileName)
1757
1758 -- Determine if the path to the video file is accessible for writing
1759
1760 local target = openSub.file.dir..subfileName
1761
1762 if not file_touch(target) then
1763 if openSub.conf.dirPath then
1764 target = openSub.conf.dirPath..slash..subfileName
1765 message = "<br>"..
1766 error_tag(lang["mess_save_fail"].." "..
1767 "<a href='"..vlc.strings.make_uri(
1768 openSub.conf.dirPath).."'>"..
1769 lang["mess_click_link"].."</a>")
1770 else
1771 setError(lang["mess_save_fail"].." "..
1772 "<a href='"..item.ZipDownloadLink.."'>"..
1773 lang["mess_click_link"].."</a>")
1774 return false
1775 end
1776 end
1777
1778 vlc.msg.dbg("[VLsub] Subtitles files: "..target)
1779
1780 -- Unzipped data into file target
1781
1782 local stream = vlc.stream(tmpFileURI)
1783 local data = ""
1784 local subfile = io.open(target, "wb")
1785
1786 while data do
1787 subfile:write(data)
1788 data = stream:read(65536)
1789 end
1790
1791 subfile:flush()
1792 subfile:close()
1793
1794 stream = nil
1795 collectgarbage()
1796
1797 if not os.remove(tmpFileName) then
1798 vlc.msg.err("[VLsub] Unable to remove temp: "..tmpFileName)
1799 end
1800
1801 -- load subtitles
1802 if add_sub(target) then
1803 message = success_tag(lang["mess_loaded"]) .. message
1804 else
1805 message = error_tag(lang["mess_not_load"]) .. message
1806 end
1807
1808 setMessage(message)
1809end
1810
1811function dump_zip(url, dir, subfileName)
1812 -- Dump zipped data in a temporary file
1813 setMessage(openSub.actionLabel..": "..progressBarContent(0))
1814 local resp = get(url)
1815
1816 if not resp then
1817 setError(lang["mess_no_response"])
1818 return false
1819 end
1820
1821 local tmpFileName = dir..subfileName..".gz"
1822 if not file_touch(tmpFileName) then
1823 return false
1824 end
1825 local tmpFile = assert(io.open(tmpFileName, "wb"))
1826
1827 tmpFile:write(resp)
1828 tmpFile:flush()
1829 tmpFile:close()
1830 tmpFile = nil
1831 collectgarbage()
1832 return "zip://"..make_uri(tmpFileName)
1833 .."!/"..subfileName, tmpFileName
1834end
1835
1836function add_sub(subPath)
1837 if vlc.item or vlc.input.item() then
1838 subPath = decode_uri(subPath)
1839 vlc.msg.dbg("[VLsub] Adding subtitle :" .. subPath)
1840 return vlc.input.add_subtitle(subPath)
1841 end
1842 return false
1843end
1844
1845 --[[ Interface helpers]]--
1846
1847function progressBarContent(pct)
1848 local accomplished = math.ceil(
1849 openSub.option.progressBarSize*pct/100)
1850 local left = openSub.option.progressBarSize - accomplished
1851 local content = "<span style='background-color:#181;color:#181;'>"..
1852 string.rep ("-", accomplished).."</span>"..
1853 "<span style='background-color:#fff;color:#fff;'>"..
1854 string.rep ("-", left)..
1855 "</span>"
1856 return content
1857end
1858
1859function setMessage(str)
1860 if input_table["message"] then
1861 input_table["message"]:set_text(str)
1862 dlg:update()
1863 end
1864end
1865
1866function setError(mess)
1867 setMessage(error_tag(mess))
1868end
1869
1870function success_tag(str)
1871 return "<span style='color:#181'><b>"..
1872 lang["mess_success"]..":</b></span> "..str..""
1873end
1874
1875function error_tag(str)
1876 return "<span style='color:#B23'><b>"..
1877 lang["mess_error"]..":</b></span> "..str..""
1878end
1879
1880 --[[ Network utils]]--
1881
1882function get(url)
1883 local host, path = parse_url(url)
1884 local header = {
1885 "GET "..path.." HTTP/"..openSub.conf.HTTPVersion,
1886 "Host: "..host,
1887 "User-Agent: "..openSub.conf.userAgentHTTP,
1888 "",
1889 ""
1890 }
1891 local request = table.concat(header, "\r\n")
1892
1893 local status, response = http_req(host, 80, request)
1894
1895 if status == 200 then
1896 return response
1897 else
1898 vlc.msg.err("[VLSub] HTTP "..tostring(status).." : "..response)
1899 return false
1900 end
1901end
1902
1903function http_req(host, port, request)
1904 local fd = vlc.net.connect_tcp(host, port)
1905 if not fd then
1906 setError("Unable to connect to server")
1907 return nil, ""
1908 end
1909 local pollfds = {}
1910
1911 pollfds[fd] = vlc.net.POLLIN
1912 vlc.net.send(fd, request)
1913 vlc.net.poll(pollfds)
1914
1915 local response = vlc.net.recv(fd, 2048)
1916 local buf = ""
1917 local headerStr, header, body
1918 local contentLength, status, TransferEncoding, chunked
1919 local pct = 0
1920
1921 while response and #response>0 do
1922 buf = buf..response
1923
1924 if not header then
1925 headerStr, body = buf:match("(.-\r?\n)\r?\n(.*)")
1926 if headerStr then
1927 header = parse_header(headerStr);
1928 status = tonumber(header["statuscode"]);
1929 contentLength = tonumber(header["Content-Length"]);
1930 if not contentLength then
1931 contentLength = tonumber(header["X-Uncompressed-Content-Length"])
1932 end
1933
1934 TransferEncoding = trim(header["Transfer-Encoding"]);
1935 chunked = (TransferEncoding=="chunked");
1936
1937 buf = body;
1938 body = "";
1939 end
1940 end
1941
1942 if chunked then
1943 chunk_size_hex, chunk_content = buf:match("(%x+)\r?\n(.*)")
1944 chunk_size = tonumber(chunk_size_hex,16)
1945 chunk_content_len = chunk_content:len()
1946 chunk_remaining = chunk_size-chunk_content_len
1947
1948 while chunk_content_len > chunk_size do
1949 body = body..chunk_content:sub(0, chunk_size)
1950 buf = chunk_content:sub(chunk_size+2)
1951
1952 chunk_size_hex, chunk_content = buf:match("(%x+)\r?\n(.*)")
1953
1954 if not chunk_size_hex
1955 or chunk_size_hex == "0" then
1956 chunk_size = 0
1957 break
1958 end
1959
1960 chunk_size = tonumber(chunk_size_hex,16)
1961 chunk_content_len = chunk_content:len()
1962 chunk_remaining = chunk_size-chunk_content_len
1963
1964 end
1965
1966 if chunk_size == 0 then
1967 break
1968 end
1969 end
1970
1971 if contentLength then
1972 bodyLenght = #body
1973 pct = bodyLenght / contentLength * 100
1974 setMessage(openSub.actionLabel..": "..progressBarContent(pct))
1975 if bodyLenght >= contentLength then
1976 break
1977 end
1978 end
1979
1980 vlc.net.poll(pollfds)
1981 response = vlc.net.recv(fd, 1024)
1982 end
1983
1984 if not chunked then
1985 body = buf
1986 end
1987
1988 if status == 301
1989 and header["Location"] then
1990 local host, path = parse_url(trim(header["Location"]))
1991 request = request
1992 :gsub("^([^%s]+ )([^%s]+)", "%1"..path)
1993 :gsub("(Host: )([^\n]*)", "%1"..host)
1994
1995 return http_req(host, port, request)
1996 end
1997
1998 return status, body
1999end
2000
2001function parse_header(data)
2002 local header = {}
2003
2004 for name, s, val in string.gmatch(
2005 data,
2006 "([^%s:]+)(:?)%s([^\n]+)\r?\n")
2007 do
2008 if s == "" then
2009 header['statuscode'] = tonumber(string.sub(val, 1 , 3))
2010 else
2011 header[name] = val
2012 end
2013 end
2014 return header
2015end
2016
2017function parse_url(url)
2018 local url_parsed = vlc.net.url_parse(url)
2019 return url_parsed["host"],
2020 url_parsed["path"],
2021 url_parsed["option"]
2022end
2023
2024 --[[ XML utils]]--
2025
2026function parse_xml(data)
2027 local tree = {}
2028 local stack = {}
2029 local tmp = {}
2030 local level = 0
2031 local op, tag, p, empty, val
2032 table.insert(stack, tree)
2033 local resolve_xml = vlc.strings.resolve_xml_special_chars
2034
2035 for op, tag, p, empty, val in string.gmatch(
2036 data,
2037 "[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>"..
2038 "[%s\r\n\t]*([^<]*)[%s\r\n\t]*"
2039 ) do
2040 if op=="/" then
2041 if level>0 then
2042 level = level - 1
2043 table.remove(stack)
2044 end
2045 else
2046 level = level + 1
2047 if val == "" then
2048 if type(stack[level][tag]) == "nil" then
2049 stack[level][tag] = {}
2050 table.insert(stack, stack[level][tag])
2051 else
2052 if type(stack[level][tag][1]) == "nil" then
2053 tmp = nil
2054 tmp = stack[level][tag]
2055 stack[level][tag] = nil
2056 stack[level][tag] = {}
2057 table.insert(stack[level][tag], tmp)
2058 end
2059 tmp = nil
2060 tmp = {}
2061 table.insert(stack[level][tag], tmp)
2062 table.insert(stack, tmp)
2063 end
2064 else
2065 if type(stack[level][tag]) == "nil" then
2066 stack[level][tag] = {}
2067 end
2068 stack[level][tag] = resolve_xml(val)
2069 table.insert(stack, {})
2070 end
2071 if empty ~= "" then
2072 stack[level][tag] = ""
2073 level = level - 1
2074 table.remove(stack)
2075 end
2076 end
2077 end
2078
2079 collectgarbage()
2080 return tree
2081end
2082
2083function parse_xmlrpc(xmlText)
2084 local stack = {}
2085 local tree = {}
2086 local tmp, name = nil, nil
2087 table.insert(stack, tree)
2088 local FromXmlString = vlc.strings.resolve_xml_special_chars
2089
2090 local data_handle = {
2091 int = function(v) return tonumber(v) end,
2092 i4 = function(v) return tonumber(v) end,
2093 double = function(v) return tonumber(v) end,
2094 boolean = function(v) return tostring(v) end,
2095 base64 = function(v) return tostring(v) end, -- FIXME
2096 ["string"] = function(v) return FromXmlString(v) end
2097 }
2098
2099 for c, label, empty, value
2100 in xmlText:gmatch("<(%/?)([%w_:]+)(%/?)>([^<]*)") do
2101
2102 if c == ""
2103 then -- start tag
2104 if label == "struct"
2105 or label == "array" then
2106 tmp = nil
2107 tmp = {}
2108 if name then
2109 stack[#stack][name] = tmp
2110 else
2111 table.insert(stack[#stack], tmp)
2112 end
2113 table.insert(stack, tmp)
2114 name = nil
2115 elseif label == "name" then
2116 name = value
2117 elseif data_handle[label] then
2118 if name then
2119 stack[#stack][name] = data_handle[label](value)
2120 else
2121 table.insert(stack[#stack],
2122 data_handle[label](value))
2123 end
2124 name = nil
2125 end
2126 if empty == "/" -- empty tag
2127 and #stack>0
2128 and (label == "struct"
2129 or label == "array")
2130 then
2131 table.remove(stack)
2132 end
2133 else -- end tag
2134 if #stack>0
2135 and (label == "struct"
2136 or label == "array")then
2137 table.remove(stack)
2138 end
2139 end
2140 end
2141
2142 return tree[1]
2143end
2144
2145function dump_xml(data)
2146 local level = 0
2147 local stack = {}
2148 local dump = ""
2149 local convert_xml = vlc.strings.convert_xml_special_chars
2150
2151 local function parse(data, stack)
2152 local data_index = {}
2153 local k
2154 local v
2155 local i
2156 local tb
2157
2158 for k,v in pairs(data) do
2159 table.insert(data_index, {k, v})
2160 table.sort(data_index, function(a, b)
2161 return a[1] < b[1]
2162 end)
2163 end
2164
2165 for i,tb in pairs(data_index) do
2166 k = tb[1]
2167 v = tb[2]
2168 if type(k)=="string" then
2169 dump = dump.."\r\n"..string.rep(
2170 " ",
2171 level)..
2172 "<"..k..">"
2173 table.insert(stack, k)
2174 level = level + 1
2175 elseif type(k)=="number" and k ~= 1 then
2176 dump = dump.."\r\n"..string.rep(
2177 " ",
2178 level-1)..
2179 "<"..stack[level]..">"
2180 end
2181
2182 if type(v)=="table" then
2183 parse(v, stack)
2184 elseif type(v)=="string" then
2185 dump = dump..(convert_xml(v) or v)
2186 elseif type(v)=="number" then
2187 dump = dump..v
2188 else
2189 dump = dump..tostring(v)
2190 end
2191
2192 if type(k)=="string" then
2193 if type(v)=="table" then
2194 dump = dump.."\r\n"..string.rep(
2195 " ",
2196 level-1)..
2197 "</"..k..">"
2198 else
2199 dump = dump.."</"..k..">"
2200 end
2201 table.remove(stack)
2202 level = level - 1
2203
2204 elseif type(k)=="number" and k ~= #data then
2205 if type(v)=="table" then
2206 dump = dump.."\r\n"..string.rep(
2207 " ",
2208 level-1)..
2209 "</"..stack[level]..">"
2210 else
2211 dump = dump.."</"..stack[level]..">"
2212 end
2213 end
2214 end
2215 end
2216 parse(data, stack)
2217 collectgarbage()
2218 return dump
2219end
2220
2221 --[[ Misc utils]]--
2222
2223function make_uri(str)
2224 str = str:gsub("\\", "/")
2225 local windowdrive = string.match(str, "^(%a:).+$")
2226 local encode_uri = vlc.strings.encode_uri_component
2227 local encodedPath = ""
2228 for w in string.gmatch(str, "/([^/]+)") do
2229 encodedPath = encodedPath.."/"..encode_uri(w)
2230 end
2231
2232 if windowdrive then
2233 return "file:///"..windowdrive..encodedPath
2234 else
2235 return "file://"..encodedPath
2236 end
2237end
2238
2239function file_touch(name) -- test write ability
2240 if not name or trim(name) == ""
2241 then return false end
2242
2243 local f=io.open(name ,"w")
2244 if f~=nil then
2245 io.close(f)
2246 return true
2247 else
2248 return false
2249 end
2250end
2251
2252function file_exist(name) -- test readability
2253 if not name or trim(name) == ""
2254 then return false end
2255 local f=io.open(name ,"r")
2256 if f~=nil then
2257 io.close(f)
2258 return true
2259 else
2260 return false
2261 end
2262end
2263
2264function is_dir(path)
2265 if not path or trim(path) == ""
2266 then return false end
2267 -- Remove slash at the end or it won't work on Windows
2268 path = string.gsub(path, "^(.-)[\\/]?$", "%1")
2269 local f, _, code = io.open(path, "rb")
2270
2271 if f then
2272 _, _, code = f:read("*a")
2273 f:close()
2274 if code == 21 then
2275 return true
2276 end
2277 elseif code == 13 then
2278 return true
2279 end
2280
2281 return false
2282end
2283
2284function list_dir(path)
2285 if not path or trim(path) == ""
2286 then return false end
2287 local dir_list_cmd
2288 local list = {}
2289 if not is_dir(path) then return false end
2290
2291 if openSub.conf.os == "win" then
2292 dir_list_cmd = io.popen('dir /b "'..path..'"')
2293 elseif openSub.conf.os == "lin" then
2294 dir_list_cmd = io.popen('ls -1 "'..path..'"')
2295 end
2296
2297 if dir_list_cmd then
2298 for filename in dir_list_cmd:lines() do
2299 if string.match(filename, "^[^%s]+.+$") then
2300 table.insert(list, filename)
2301 end
2302 end
2303 return list
2304 else
2305 return false
2306 end
2307end
2308
2309function mkdir_p(path)
2310 if not path or trim(path) == ""
2311 then return false end
2312 if openSub.conf.os == "win" then
2313 os.execute('mkdir "' .. path..'"')
2314 elseif openSub.conf.os == "lin" then
2315 os.execute("mkdir -p '" .. path.."'")
2316 end
2317end
2318
2319function decode_uri(str)
2320 return str:gsub("/", slash)
2321end
2322
2323function is_window_path(path)
2324 return string.match(path, "^(%a:.+)$")
2325end
2326
2327function is_win_safe(path)
2328 if not path or trim(path) == ""
2329 or not is_window_path(path)
2330 then return false end
2331 return string.match(path, "^%a?%:?[\\%w%p%s§¤]+$")
2332end
2333
2334function trim(str)
2335 if not str then return "" end
2336 return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1")
2337end
2338
2339function remove_tag(str)
2340 return string.gsub(str, "{[^}]+}", "")
2341end