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