VLSub: correct description
[vlc/vlc-acra.git] / share / lua / extensions / VLSub.lua
blob319c9f79ef94034d383ea4a3edc9845926057fda
1 --[[
2 VLSub Extension for VLC media player 1.1 and 2.0
3 Copyright 2013 Guillaume Le Maout
5 Authors: Guillaume Le Maout
6 Contact: http://addons.videolan.org/messages/?action=newmessage&username=exebetche
7 Bug report: http://addons.videolan.org/content/show.php/?content=148752
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 --]]
25 --[[ Global var ]]--
27 -- You can set here your default language by replacing nil with your language code (see below)
28 -- Example:
29 -- language = "fre",
30 -- language = "ger",
31 -- language = "eng",
32 -- ...
34 local options = {
35 language = nil,
36 downloadBehaviour = 'save',
37 langExt = false,
38 removeTag = false,
39 showMediaInformation = true,
40 progressBarSize = 80,
41 intLang = 'eng',
42 translations_avail = {
43 eng = 'English',
44 cze = 'Czech',
45 dan = 'Danish',
46 fre = 'Français',
47 ell = 'Greek',
48 baq = 'Basque',
49 pob = 'Brazilian Portuguese',
50 slo = 'Slovak',
51 spa = 'Spanish',
52 swe = 'Swedish',
53 ukr = 'Ukrainian'
55 translation = {
56 int_all = 'All',
57 int_descr = 'Download subtitles from OpenSubtitles.org',
58 int_research = 'Research',
59 int_config = 'Config',
60 int_configuration = 'Configuration',
61 int_help = 'Help',
62 int_search_hash = 'Search by hash',
63 int_search_name = 'Search by name',
64 int_title = 'Title',
65 int_season = 'Season (series)',
66 int_episode = 'Episode (series)',
67 int_show_help = 'Show help',
68 int_show_conf = 'Show config',
69 int_dowload_sel = 'Download selection',
70 int_close = 'Close',
71 int_ok = 'Ok',
72 int_save = 'Save',
73 int_cancel = 'Cancel',
74 int_bool_true = 'Yes',
75 int_bool_false = 'No',
76 int_search_transl = 'Search translations',
77 int_searching_transl = 'Searching translations ...',
78 int_int_lang = 'Interface language',
79 int_default_lang = 'Subtitles language',
80 int_dowload_behav = 'What to do with subtitles',
81 int_dowload_save = 'Load and save',
82 int_dowload_load = 'Load only',
83 int_dowload_manual = 'Manual download',
84 int_display_code = 'Display language code in file name',
85 int_remove_tag = 'Remove tags',
86 int_vlsub_work_dir = 'VLSub working directory',
87 int_os_username = 'Username',
88 int_os_password = 'Password',
89 int_help_mess = " Download subtittles from <a href='http://www.opensubtitles.org/'>opensubtitles.org</a> and display them while watching a video.<br>"..
90 " <br>"..
91 " <b><u>Usage:</u></b><br>"..
92 " <br>"..
93 " VLSub is meant to be used while your watching the video, so start it first (if nothing is playing you will get a link to download the subtitles in your browser).<br>"..
94 " <br>"..
95 " Choose the language for your subtitles and click on the button corresponding to one of the two research method provided by VLSub:<br>"..
96 " <br>"..
97 " <b>Method 1: Search by hash</b><br>"..
98 " It is recommended to try this method first, because it performs a research based on the video file print, so you can find subtitles synchronized with your video.<br>"..
99 " <br>"..
100 " <b>Method 2: Search by name</b><br>"..
101 " If you have no luck with the first method, just check the title is correct before clicking. If you search subtitles for a serie, you can also provide a season and episode number.<br>"..
102 " <br>"..
103 " <b>Downloading Subtitles</b><br>"..
104 " Select one subtitle in the list and click on 'Download'.<br>"..
105 " It will be put in the same directory that your video, with the same name (different extension)"..
106 " so Vlc will load them automatically the next time you'll start the video.<br>"..
107 " <br>"..
108 " <b>/!\\ Beware :</b> Existing subtitles are overwrited without asking confirmation, so put them elsewhere if thet're important.<br>"..
109 " <br>"..
110 " Find more Vlc extensions at <a href='http://addons.videolan.org'>addons.videolan.org</a>.",
112 action_login = 'Logging in',
113 action_logout = 'Logging out',
114 action_noop = 'Checking session',
115 action_search = 'Searching subtitles',
116 action_hash = 'Calculating movie hash',
118 mess_success = 'Success',
119 mess_error = 'Error',
120 mess_no_response = 'Server not responding',
121 mess_unauthorized = 'Request unauthorized',
122 mess_expired = 'Session expired, retrying',
123 mess_overloaded = 'Server overloaded, please retry later',
124 mess_no_input = 'Please use this method during playing',
125 mess_not_local = 'This method works with local file only (for now)',
126 mess_not_found = 'File not found',
127 mess_not_found2 = 'File not found (illegal character?)',
128 mess_no_selection = 'No subtitles selected',
129 mess_save_fail = 'Unable to save subtitles',
130 mess_click_link = 'Click here to open the file',
131 mess_complete = 'Research complete',
132 mess_no_res = 'No result',
133 mess_res = 'result(s)',
134 mess_loaded = 'Subtitles loaded',
135 mess_downloading = 'Downloading subtitle',
136 mess_dowload_link = 'Download link',
137 mess_err_conf_access ='Can\'t fount a suitable path to save config, please set it manually',
138 mess_err_wrong_path ='the path contains illegal character, please correct it'
142 local languages = {
143 {'alb', 'Albanian'},
144 {'ara', 'Arabic'},
145 {'arm', 'Armenian'},
146 {'baq', 'Basque'},
147 {'ben', 'Bengali'},
148 {'bos', 'Bosnian'},
149 {'bre', 'Breton'},
150 {'bul', 'Bulgarian'},
151 {'bur', 'Burmese'},
152 {'cat', 'Catalan'},
153 {'chi', 'Chinese'},
154 {'hrv', 'Croatian'},
155 {'cze', 'Czech'},
156 {'dan', 'Danish'},
157 {'dut', 'Dutch'},
158 {'eng', 'English'},
159 {'epo', 'Esperanto'},
160 {'est', 'Estonian'},
161 {'fin', 'Finnish'},
162 {'fre', 'French'},
163 {'glg', 'Galician'},
164 {'geo', 'Georgian'},
165 {'ger', 'German'},
166 {'ell', 'Greek'},
167 {'heb', 'Hebrew'},
168 {'hin', 'Hindi'},
169 {'hun', 'Hungarian'},
170 {'ice', 'Icelandic'},
171 {'ind', 'Indonesian'},
172 {'ita', 'Italian'},
173 {'jpn', 'Japanese'},
174 {'kaz', 'Kazakh'},
175 {'khm', 'Khmer'},
176 {'kor', 'Korean'},
177 {'lav', 'Latvian'},
178 {'lit', 'Lithuanian'},
179 {'ltz', 'Luxembourgish'},
180 {'mac', 'Macedonian'},
181 {'may', 'Malay'},
182 {'mal', 'Malayalam'},
183 {'mon', 'Mongolian'},
184 {'nor', 'Norwegian'},
185 {'oci', 'Occitan'},
186 {'per', 'Persian'},
187 {'pol', 'Polish'},
188 {'por', 'Portuguese'},
189 {'pob', 'Brazilian Portuguese'},
190 {'rum', 'Romanian'},
191 {'rus', 'Russian'},
192 {'scc', 'Serbian'},
193 {'sin', 'Sinhalese'},
194 {'slo', 'Slovak'},
195 {'slv', 'Slovenian'},
196 {'spa', 'Spanish'},
197 {'swa', 'Swahili'},
198 {'swe', 'Swedish'},
199 {'syr', 'Syriac'},
200 {'tgl', 'Tagalog'},
201 {'tel', 'Telugu'},
202 {'tha', 'Thai'},
203 {'tur', 'Turkish'},
204 {'ukr', 'Ukrainian'},
205 {'urd', 'Urdu'},
206 {'vie', 'Vietnamese'}
209 -- Languages code conversion table: iso-639-1 to iso-639-3
210 -- See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
211 local lang_os_to_iso = {
212 sq = "alb",
213 ar = "ara",
214 hy = "arm",
215 eu = "baq",
216 bn = "ben",
217 bs = "bos",
218 br = "bre",
219 bg = "bul",
220 my = "bur",
221 ca = "cat",
222 zh = "chi",
223 hr = "hrv",
224 cs = "cze",
225 da = "dan",
226 nl = "dut",
227 en = "eng",
228 eo = "epo",
229 et = "est",
230 fi = "fin",
231 fr = "fre",
232 gl = "glg",
233 ka = "geo",
234 de = "ger",
235 el = "ell",
236 he = "heb",
237 hi = "hin",
238 hu = "hun",
239 is = "ice",
240 id = "ind",
241 it = "ita",
242 ja = "jpn",
243 kk = "kaz",
244 km = "khm",
245 ko = "kor",
246 lv = "lav",
247 lt = "lit",
248 lb = "ltz",
249 mk = "mac",
250 ms = "may",
251 ml = "mal",
252 mn = "mon",
253 no = "nor",
254 oc = "oci",
255 fa = "per",
256 pl = "pol",
257 pt = "por",
258 po = "pob",
259 ro = "rum",
260 ru = "rus",
261 sr = "scc",
262 si = "sin",
263 sk = "slo",
264 sl = "slv",
265 es = "spa",
266 sw = "swa",
267 sv = "swe",
268 tl = "tgl",
269 te = "tel",
270 th = "tha",
271 tr = "tur",
272 uk = "ukr",
273 ur = "urd",
274 vi = "vie"
277 local dlg = nil
278 local input_table = {} -- General widget id reference
279 local select_conf = {} -- Drop down widget / option table association
281 --[[ Vlc extension stuff ]]--
283 function descriptor()
284 return {
285 title = "VLsub 0.9.10",
286 version = "0.9.10",
287 author = "exebetche",
288 url = 'http://www.opensubtitles.org/',
289 shortdesc = "Download Subtitles";
290 description = options.translation.int_descr,
291 capabilities = {"menu", "input-listener" }
295 function activate()
296 vlc.msg.dbg("[VLsub] Welcome")
298 check_config()
300 if vlc.input.item() then
301 openSub.getFileInfo()
302 openSub.getMovieInfo()
305 show_main()
308 function close()
309 deactivate()
312 function deactivate()
313 vlc.msg.dbg("[VLsub] Bye bye!")
314 if dlg then
315 dlg:hide()
318 if openSub.session.token and openSub.session.token ~= "" then
319 openSub.request("LogOut")
321 vlc.deactivate()
324 function menu()
325 return {
326 lang.int_research,
327 lang.int_config,
328 lang.int_help
332 function meta_changed()
333 return false
336 function input_changed()
337 collectgarbage()
338 set_interface_main()
339 collectgarbage()
342 --[[ Interface data ]]--
344 function interface_main()
345 dlg:add_label(lang["int_default_lang"]..':', 1, 1, 1, 1)
346 input_table['language'] = dlg:add_dropdown(2, 1, 2, 1)
347 dlg:add_button(lang["int_search_hash"], searchHash, 4, 1, 1, 1)
349 dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1)
350 input_table['title'] = dlg:add_text_input(openSub.movie.title or "", 2, 2, 2, 1)
351 dlg:add_button(lang["int_search_name"], searchIMBD, 4, 2, 1, 1)
352 dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1)
353 input_table['seasonNumber'] = dlg:add_text_input(openSub.movie.seasonNumber or "", 2, 3, 2, 1)
354 dlg:add_label(lang["int_episode"]..':', 1, 4, 1, 1)
355 input_table['episodeNumber'] = dlg:add_text_input(openSub.movie.episodeNumber or "", 2, 4, 2, 1)
356 input_table['mainlist'] = dlg:add_list(1, 5, 4, 1)
357 input_table['message'] = nil
358 input_table['message'] = dlg:add_label(' ', 1, 6, 4, 1)
359 dlg:add_button(lang["int_show_help"], show_help, 1, 7, 1, 1)
360 dlg:add_button(' '..lang["int_show_conf"]..' ', show_conf, 2, 7, 1, 1)
361 dlg:add_button(lang["int_dowload_sel"], download_subtitles, 3, 7, 1, 1)
362 dlg:add_button(lang["int_close"], deactivate, 4, 7, 1, 1)
364 assoc_select_conf('language', 'language', openSub.conf.languages, 2, lang["int_all"])
365 display_subtitles()
368 function set_interface_main()
369 -- Update movie title and co. if video input change
370 if not type(input_table['title']) == 'userdata' then return false end
372 openSub.getFileInfo()
373 openSub.getMovieInfo()
375 input_table['title']:set_text(openSub.movie.title or "")
376 input_table['episodeNumber']:set_text(openSub.movie.episodeNumber or "")
377 input_table['seasonNumber']:set_text(openSub.movie.seasonNumber or "")
380 function interface_config()
381 input_table['intLangLab'] = dlg:add_label(lang["int_int_lang"]..':', 1, 1, 1, 1)
382 input_table['intLangBut'] = dlg:add_button(lang["int_search_transl"], get_available_translations, 2, 1, 1, 1)
383 input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1)
384 dlg:add_label(lang["int_default_lang"]..':', 1, 2, 2, 1)
385 input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1)
386 dlg:add_label(lang["int_dowload_behav"]..':', 1, 3, 2, 1)
387 input_table['downloadBehaviour'] = dlg:add_dropdown(3, 3, 1, 1)
389 dlg:add_label(lang["int_display_code"]..':', 1, 4, 0, 1)
390 input_table['langExt'] = dlg:add_dropdown(3, 4, 1, 1)
391 dlg:add_label(lang["int_remove_tag"]..':', 1, 5, 0, 1)
392 input_table['removeTag'] = dlg:add_dropdown(3, 5, 1, 1)
394 if openSub.conf.dirPath then
395 if openSub.conf.os == "win" then
396 dlg :add_label("<a href='file:///"..openSub.conf.dirPath.."'>"..lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
397 else
398 dlg :add_label("<a href='"..openSub.conf.dirPath.."'>"..lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
400 else
401 dlg :add_label(lang["int_vlsub_work_dir"], 1, 6, 2, 1)
404 input_table['dir_path'] = dlg:add_text_input(openSub.conf.dirPath, 2, 6, 2, 1)
406 dlg:add_label(lang["int_os_username"]..':', 1, 7, 0, 1)
407 input_table['os_username'] = dlg:add_text_input(openSub.option.os_username or "", 2, 7, 2, 1)
408 dlg:add_label(lang["int_os_password"]..':', 1, 8, 0, 1)
409 input_table['os_password'] = dlg:add_text_input(openSub.option.os_password or "", 2, 8, 2, 1)
411 input_table['message'] = nil
412 input_table['message'] = dlg:add_label(' ', 1, 9, 3, 1)
414 dlg:add_button(lang["int_cancel"], show_main, 2, 10, 1, 1)
415 dlg:add_button(lang["int_save"], apply_config, 3, 10, 1, 1)
417 input_table['langExt']:add_value(lang["int_bool_"..tostring(openSub.option.langExt)], 1)
418 input_table['langExt']:add_value(lang["int_bool_"..tostring(not openSub.option.langExt)], 2)
419 input_table['removeTag']:add_value(lang["int_bool_"..tostring(openSub.option.removeTag)], 1)
420 input_table['removeTag']:add_value(lang["int_bool_"..tostring(not openSub.option.removeTag)], 2)
422 assoc_select_conf('intLang', 'intLang', openSub.conf.translations_avail, 2)
423 assoc_select_conf('default_language', 'language', openSub.conf.languages, 2, lang["int_all"])
424 assoc_select_conf('downloadBehaviour', 'downloadBehaviour', openSub.conf.downloadBehaviours, 1)
427 function interface_help()
428 local help_html = lang["int_help_mess"]
430 input_table['help'] = dlg:add_html(help_html, 1, 1, 4, 1)
431 dlg:add_label(string.rep ("&nbsp;", 100), 1, 2, 3, 1)
432 dlg:add_button(lang["int_ok"], show_main, 4, 2, 1, 1)
435 function trigger_menu(dlg_id)
436 if dlg_id == 1 then
437 close_dlg()
438 dlg = vlc.dialog(openSub.conf.useragent)
439 interface_main()
440 elseif dlg_id == 2 then
441 close_dlg()
442 dlg = vlc.dialog(openSub.conf.useragent..': '..lang["int_configuration"])
443 interface_config()
444 elseif dlg_id == 3 then
445 close_dlg()
446 dlg = vlc.dialog(openSub.conf.useragent..': '..lang["int_help"])
447 interface_help()
449 collectgarbage() --~ !important
452 function show_main()
453 trigger_menu(1)
456 function show_conf()
457 trigger_menu(2)
460 function show_help()
461 trigger_menu(3)
464 function close_dlg()
465 vlc.msg.dbg("[VLSub] Closing dialog")
467 if dlg ~= nil then
468 --~ dlg:delete() -- Throw an error
469 dlg:hide()
472 dlg = nil
473 input_table = nil
474 input_table = {}
475 collectgarbage() --~ !important
478 --[[ Drop down / config association]]--
480 function assoc_select_conf(select_id, option, conf, ind, default)
481 -- Helper for i/o interaction betwenn drop down and option list (lang...)
482 select_conf[select_id] = {cf = conf, opt = option, dflt = default, ind = ind}
483 set_default_option(select_id)
484 display_select(select_id)
487 function set_default_option(select_id)
488 -- Put the selected option of a list in first place of the associated table
489 local opt = select_conf[select_id].opt
490 local cfg = select_conf[select_id].cf
491 local ind = select_conf[select_id].ind
492 if openSub.option[opt] then
493 table.sort(cfg, function(a, b)
494 if a[1] == openSub.option[opt] then
495 return true
496 elseif b[1] == openSub.option[opt] then
497 return false
498 else
499 return a[ind] < b[ind]
501 end)
505 function display_select(select_id)
506 -- Display the drop down values with an optionnal default value at the top
507 local conf = select_conf[select_id].cf
508 local opt = select_conf[select_id].opt
509 local option = openSub.option[opt]
510 local default = select_conf[select_id].dflt
511 local default_isset = false
513 if not default then
514 default_isset = true
517 for k, l in ipairs(conf) do
518 if default_isset then
519 input_table[select_id]:add_value(l[2], k)
520 else
521 if option then
522 input_table[select_id]:add_value(l[2], k)
523 input_table[select_id]:add_value(default, 0)
524 else
525 input_table[select_id]:add_value(default, 0)
526 input_table[select_id]:add_value(l[2], k)
528 default_isset = true
533 --[[ Config & interface localization]]--
535 function check_config()
536 -- Make a copy of english translation to use it as default
537 -- in case some element aren't translated in other translations
538 eng_translation = {}
539 for k, v in pairs(openSub.option.translation) do
540 eng_translation[k] = v
543 -- Get available translation full name from code
544 trsl_names = {}
545 for i, lg in ipairs(languages) do
546 trsl_names[lg[1]] = lg[2]
549 if is_window_path(vlc.config.datadir()) then
550 openSub.conf.os = "win"
551 slash = "\\"
552 else
553 openSub.conf.os = "lin"
554 slash = "/"
557 local path_generic = {"lua", "extensions", "userdata", "vlsub"}
558 local dirPath = slash..table.concat(path_generic, slash)
559 local filePath = slash.."vlsub_conf.xml"
560 local config_saved = false
561 sub_dir = slash.."vlsub_subtitles"
563 -- Check if config file path is stored in vlc config
564 local other_dirs = {}
566 for path in vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
567 if path:match(".*"..sub_dir.."$") then
568 openSub.conf.dirPath = path:gsub("%s*(.*)"..sub_dir.."%s*$", "%1")
569 config_saved = true
571 table.insert(other_dirs, path)
574 -- if not stored in vlc config
575 -- try to find a suitable config file path
577 if openSub.conf.dirPath then
578 if not is_dir(openSub.conf.dirPath) and
579 (openSub.conf.os == "lin" or
580 is_win_safe(openSub.conf.dirPath)) then
581 mkdir_p(openSub.conf.dirPath)
583 else
584 local userdatadir = vlc.config.userdatadir()
585 local datadir = vlc.config.datadir()
587 -- check if the config already exist
588 if file_exist(userdatadir..dirPath..filePath) then
589 openSub.conf.dirPath = userdatadir..dirPath
590 config_saved = true
591 elseif file_exist(datadir..dirPath..filePath) then
592 openSub.conf.dirPath = datadir..dirPath
593 config_saved = true
594 else
595 local extension_path = slash..path_generic[1]
596 ..slash..path_generic[2]
598 -- use the same folder as the extension if accessible
599 if is_dir(userdatadir..extension_path)
600 and file_touch(userdatadir..dirPath..filePath) then
601 openSub.conf.dirPath = userdatadir..dirPath
602 elseif file_touch(datadir..dirPath..filePath) then
603 openSub.conf.dirPath = datadir..dirPath
606 -- try to create working dir in user folder
607 if not openSub.conf.dirPath
608 and is_dir(userdatadir) then
609 if not is_dir(userdatadir..dirPath) then
610 mkdir_p(userdatadir..dirPath)
612 if is_dir(userdatadir..dirPath) and
613 file_touch(userdatadir..dirPath..filePath) then
614 openSub.conf.dirPath = userdatadir..dirPath
618 -- try to create working dir in vlc folder
619 if not openSub.conf.dirPath and
620 is_dir(datadir) then
621 if not is_dir(datadir..dirPath) then
622 mkdir_p(datadir..dirPath)
624 if file_touch(datadir..dirPath..filePath) then
625 openSub.conf.dirPath = datadir..dirPath
631 if openSub.conf.dirPath then
632 vlc.msg.dbg("[VLSub] Working directory: " ..
633 (openSub.conf.dirPath or "not found"))
635 openSub.conf.filePath = openSub.conf.dirPath..filePath
636 openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"
638 if config_saved
639 and file_exist(openSub.conf.filePath) then
640 vlc.msg.dbg("[VLSub] Loading config file: "..openSub.conf.filePath)
641 load_config()
642 else
643 vlc.msg.dbg("[VLSub] No config file")
644 getenv_lang()
645 config_saved = save_config()
646 if not config_saved then
647 vlc.msg.dbg("[VLSub] Unable to save config")
651 -- Check presence of a translation file in "%vlsub_directory%/locale"
652 -- Add translation files to available translation list
654 local file_list = list_dir(openSub.conf.localePath)
655 local translations_avail = openSub.conf.translations_avail
656 if file_list then
657 for i, file_name in ipairs(file_list) do
658 local lg = string.gsub(file_name, "^(%w%w%w).xml$", "%1")
659 if lg and not translations_avail[lg] then
660 table.insert(translations_avail, {lg, trsl_names[lg]})
665 -- Load selected translation from file
666 if openSub.option.intLang ~= "eng"
667 and not openSub.conf.translated
668 then
669 local transl_file_path = openSub.conf.localePath..slash..openSub.option.intLang..".xml"
670 if file_exist(transl_file_path) then
671 vlc.msg.dbg("[VLSub] Loadin translation from file: " .. transl_file_path)
672 load_transl(transl_file_path)
675 else
676 vlc.msg.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
679 lang = nil
680 lang = options.translation -- just a shortcut
682 SetDownloadBehaviours()
683 if not openSub.conf.dirPath then
684 setError(lang["mess_err_conf_access"])
687 -- Set table list of available traduction from assoc. array
688 -- so it is sortable
690 for k, l in pairs(openSub.option.translations_avail) do
691 if k == openSub.option.int_research then
692 table.insert(openSub.conf.translations_avail, 1, {k, l})
693 else
694 table.insert(openSub.conf.translations_avail, {k, l})
697 collectgarbage()
700 function load_config()
701 -- Overwrite default conf with loaded conf
702 local tmpFile = io.open(openSub.conf.filePath, "rb")
703 if not tmpFile then return false end
704 local resp = tmpFile:read("*all")
705 tmpFile:flush()
706 tmpFile:close()
707 local option = parse_xml(resp)
709 for key, value in pairs(option) do
710 if type(value) == "table" then
711 if key == "translation" then
712 openSub.conf.translated = true
713 for k, v in pairs(value) do
714 openSub.option.translation[k] = v
716 else
717 openSub.option[key] = value
719 else
720 if value == "true" then
721 openSub.option[key] = true
722 elseif value == "false" then
723 openSub.option[key] = false
724 else
725 openSub.option[key] = value
729 collectgarbage()
732 function load_transl(path)
733 -- Overwrite default conf with loaded conf
734 local tmpFile = assert(io.open(path, "rb"))
735 local resp = tmpFile:read("*all")
736 tmpFile:flush()
737 tmpFile:close()
738 openSub.option.translation = nil
740 openSub.option.translation = parse_xml(resp)
741 collectgarbage()
744 function apply_translation()
745 -- Overwrite default conf with loaded conf
746 for k, v in pairs(eng_translation) do
747 if not openSub.option.translation[k] then
748 openSub.option.translation[k] = eng_translation[k]
753 function getenv_lang()
754 -- Retrieve the user OS language
755 local os_lang = os.getenv("LANG")
757 if os_lang then -- unix, mac
758 os_lang = string.sub(os_lang, 0, 2)
759 if type(lang_os_to_iso[os_lang]) then
760 openSub.option.language = lang_os_to_iso[os_lang]
762 else -- Windows
763 local lang_w = string.match(os.setlocale("", "collate"), "^[^_]+")
764 for i, v in ipairs(openSub.conf.languages) do
765 if v[2] == lang_w then
766 openSub.option.language = v[1]
772 function apply_config()
773 -- Apply user config selection to local config
774 local lg_sel = input_table['intLang']:get_value()
775 local sel_val
776 local opt
778 if lg_sel and lg_sel ~= 1
779 and openSub.conf.translations_avail[lg_sel] then
780 local lg = openSub.conf.translations_avail[lg_sel][1]
781 set_translation(lg)
782 SetDownloadBehaviours()
785 for select_id, v in pairs(select_conf) do
786 if input_table[select_id] and select_conf[select_id] then
787 sel_val = input_table[select_id]:get_value()
788 opt = select_conf[select_id].opt
790 if sel_val == 0 then
791 openSub.option[opt] = nil
792 else
793 openSub.option[opt] = select_conf[select_id].cf[sel_val][1]
796 set_default_option(select_id)
801 openSub.option.os_username = input_table['os_username']:get_text()
802 openSub.option.os_password = input_table['os_password']:get_text()
804 if input_table["langExt"]:get_value() == 2 then
805 openSub.option.langExt = not openSub.option.langExt
808 if input_table["removeTag"]:get_value() == 2 then
809 openSub.option.removeTag = not openSub.option.removeTag
812 -- Set a custom working directory
813 local dir_path = input_table['dir_path']:get_text()
814 local dir_path_err = false
815 if trim(dir_path) == "" then dir_path = nil end
817 if dir_path ~= openSub.conf.dirPath then
818 if openSub.conf.os == "lin"
819 or is_win_safe(dir_path)
820 or not dir_path then
821 local other_dirs = {}
823 for path in vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
824 path = trim(path)
825 if path ~= (openSub.conf.dirPath or "")..sub_dir then
826 table.insert(other_dirs, path)
829 openSub.conf.dirPath = dir_path
830 if dir_path then
831 table.insert(other_dirs,
832 string.gsub(dir_path, "^(.-)[\\/]?$", "%1")..sub_dir)
834 if not is_dir(dir_path) then
835 mkdir_p(dir_path)
838 openSub.conf.filePath = openSub.conf.dirPath..slash.."vlsub_conf.xml"
839 openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"
840 else
841 openSub.conf.filePath = nil
842 openSub.conf.localePath = nil
844 vlc.config.set("sub-autodetect-path", table.concat(other_dirs, ", "))
845 else
846 dir_path_err = true
847 setError(lang["mess_err_wrong_path"].."<br><b>"..string.gsub(dir_path, "[^%:%w%p%s§¤]+", "<span style='color:#B23'>%1</span>").."</b>")
851 if openSub.conf.dirPath and
852 not dir_path_err then
853 local config_saved = save_config()
854 trigger_menu(1)
855 if not config_saved then
856 setError(lang["mess_err_conf_access"])
858 else
859 setError(lang["mess_err_conf_access"])
863 function save_config()
864 -- Dump local config into config file
865 if openSub.conf.dirPath
866 and openSub.conf.filePath then
867 vlc.msg.dbg("[VLSub] Saving config file: " .. openSub.conf.filePath)
869 if file_touch(openSub.conf.filePath) then
870 local tmpFile = assert(io.open(openSub.conf.filePath, "wb"))
871 local resp = dump_xml(openSub.option)
872 tmpFile:write(resp)
873 tmpFile:flush()
874 tmpFile:close()
875 tmpFile = nil
876 else
877 return false
879 collectgarbage()
880 return true
881 else
882 vlc.msg.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
883 setError(lang["mess_err_conf_access"])
884 return false
888 function SetDownloadBehaviours()
889 openSub.conf.downloadBehaviours = nil
890 openSub.conf.downloadBehaviours = {
891 {'save', lang["int_dowload_save"]},
892 {'load', lang["int_dowload_load"]},
893 {'manual', lang["int_dowload_manual"]}
897 function get_available_translations()
898 -- Get all available translation files from the internet
899 -- (drop previous direct download from github repo because of problem with github https CA certficate on OS X an XP)
900 -- https://github.com/exebetche/vlsub/tree/master/locale
902 local translations_url = "http://addons.videolan.org/CONTENT/content-files/148752-vlsub_translations.xml"
904 if input_table['intLangBut']:get_text() == lang["int_search_transl"] then
905 openSub.actionLabel = lang["int_searching_transl"]
907 local translations_content, lol = get(translations_url)
909 all_trsl = parse_xml(translations_content)
910 local lg, trsl
912 for lg, trsl in pairs(all_trsl) do
913 if lg ~= options.intLang[1] and not openSub.option.translations_avail[lg] then
914 openSub.option.translations_avail[lg] = trsl_names[lg] or ""
915 table.insert(openSub.conf.translations_avail, {lg, trsl_names[lg]})
916 input_table['intLang']:add_value(trsl_names[lg], #openSub.conf.translations_avail)
920 setMessage(success_tag(lang["mess_complete"]))
921 collectgarbage()
925 function set_translation(lg)
926 openSub.option.translation = nil
927 openSub.option.translation = {}
929 if lg == 'eng' then
930 for k, v in pairs(eng_translation) do
931 openSub.option.translation[k] = v
933 else
934 -- If translation file exists in /locale directory load it
935 if openSub.conf.localePath
936 and file_exist(openSub.conf.localePath..slash..lg..".xml") then
937 local transl_file_path = openSub.conf.localePath..slash..lg..".xml"
938 vlc.msg.dbg("[VLSub] Loading translation from file: " .. transl_file_path)
939 load_transl(transl_file_path)
940 apply_translation()
941 else
942 -- Load translation file from internet
943 if not all_trsl then
944 get_available_translations()
947 if not all_trsl or not all_trsl[lg] then
948 vlc.msg.dbg("[VLSub] Error, translation not found")
949 return false
951 openSub.option.translation = all_trsl[lg]
952 apply_translation()
953 all_trsl = nil
957 lang = nil
958 lang = openSub.option.translation
959 collectgarbage()
962 --[[ Core ]]--
964 openSub = {
965 itemStore = nil,
966 actionLabel = "",
967 conf = {
968 url = "http://api.opensubtitles.org/xml-rpc",
969 path = nil,
970 userAgentHTTP = "VLSub",
971 useragent = "VLSub 0.9",
972 translations_avail = {},
973 downloadBehaviours = nil,
974 languages = languages
976 option = options,
977 session = {
978 loginTime = 0,
979 token = ""
981 file = {
982 hasInput = false,
983 uri = nil,
984 ext = nil,
985 name = nil,
986 path = nil,
987 protocol = nil,
988 cleanName = nil,
989 dir = nil,
990 hash = nil,
991 bytesize = nil,
992 fps = nil,
993 timems = nil,
994 frames = nil
996 movie = {
997 title = "",
998 seasonNumber = "",
999 episodeNumber = "",
1000 sublanguageid = ""
1002 request = function(methodName)
1003 local params = openSub.methods[methodName].params()
1004 local reqTable = openSub.getMethodBase(methodName, params)
1005 local request = "<?xml version='1.0'?>"..dump_xml(reqTable)
1006 local host, path = parse_url(openSub.conf.url)
1007 local header = {
1008 "POST "..path.." HTTP/1.1",
1009 "Host: "..host,
1010 "User-Agent: "..openSub.conf.userAgentHTTP,
1011 "Content-Type: text/xml",
1012 "Content-Length: "..string.len(request),
1016 request = table.concat(header, "\r\n")..request
1018 local response
1019 local status, responseStr = http_req(host, 80, request)
1021 if status == 200 then
1022 response = parse_xmlrpc(responseStr)
1023 if response then
1024 if response.status == "200 OK" then
1025 return openSub.methods[methodName].callback(response)
1026 elseif response.status == "406 No session" then
1027 openSub.request("LogIn")
1028 elseif response then
1029 setError("code '"..response.status.."' ("..status..")")
1030 return false
1032 else
1033 setError("Server not responding")
1034 return false
1036 elseif status == 401 then
1037 setError("Request unauthorized")
1039 response = parse_xmlrpc(responseStr)
1040 if openSub.session.token ~= response.token then
1041 setMessage("Session expired, retrying")
1042 openSub.session.token = response.token
1043 openSub.request(methodName)
1045 return false
1046 elseif status == 503 then
1047 setError("Server overloaded, please retry later")
1048 return false
1051 end,
1052 getMethodBase = function(methodName, param)
1053 if openSub.methods[methodName].methodName then
1054 methodName = openSub.methods[methodName].methodName
1057 local request = {
1058 methodCall={
1059 methodName=methodName,
1060 params={ param=param }}}
1062 return request
1063 end,
1064 methods = {
1065 LogIn = {
1066 params = function()
1067 openSub.actionLabel = lang["action_login"]
1068 return {
1069 { value={ string=openSub.option.os_username } },
1070 { value={ string=openSub.option.os_password } },
1071 { value={ string=openSub.movie.sublanguageid } },
1072 { value={ string=openSub.conf.useragent } }
1074 end,
1075 callback = function(resp)
1076 openSub.session.token = resp.token
1077 openSub.session.loginTime = os.time()
1078 return true
1081 LogOut = {
1082 params = function()
1083 openSub.actionLabel = lang["action_logout"]
1084 return {
1085 { value={ string=openSub.session.token } }
1087 end,
1088 callback = function()
1089 return true
1092 NoOperation = {
1093 params = function()
1094 openSub.actionLabel = lang["action_noop"]
1095 return {
1096 { value={ string=openSub.session.token } }
1098 end,
1099 callback = function(resp)
1100 return true
1103 SearchSubtitlesByHash = {
1104 methodName = "SearchSubtitles",
1105 params = function()
1106 openSub.actionLabel = lang["action_search"]
1107 setMessage(openSub.actionLabel..": "..progressBarContent(0))
1109 return {
1110 { value={ string=openSub.session.token } },
1111 { value={
1112 array={
1113 data={
1114 value={
1115 struct={
1116 member={
1117 { name="sublanguageid", value={
1118 string=openSub.movie.sublanguageid } },
1119 { name="moviehash", value={
1120 string=openSub.file.hash } },
1121 { name="moviebytesize", value={
1122 double=openSub.file.bytesize } } }}}}}}}
1124 end,
1125 callback = function(resp)
1126 openSub.itemStore = resp.data
1129 SearchSubtitles = {
1130 methodName = "SearchSubtitles",
1131 params = function()
1132 openSub.actionLabel = lang["action_search"]
1133 setMessage(openSub.actionLabel..": "..progressBarContent(0))
1135 local member = {
1136 { name="sublanguageid", value={
1137 string=openSub.movie.sublanguageid } },
1138 { name="query", value={
1139 string=openSub.movie.title } } }
1142 if openSub.movie.seasonNumber ~= nil then
1143 table.insert(member, { name="season", value={
1144 string=openSub.movie.seasonNumber } })
1147 if openSub.movie.episodeNumber ~= nil then
1148 table.insert(member, { name="episode", value={
1149 string=openSub.movie.episodeNumber } })
1152 return {
1153 { value={ string=openSub.session.token } },
1154 { value={
1155 array={
1156 data={
1157 value={
1158 struct={
1159 member=member
1160 }}}}}}
1162 end,
1163 callback = function(resp)
1164 openSub.itemStore = resp.data
1168 getInputItem = function()
1169 return vlc.item or vlc.input.item()
1170 end,
1171 getFileInfo = function()
1172 -- Get video file path, name, extension from input uri
1173 local item = openSub.getInputItem()
1174 local file = openSub.file
1175 if not item then
1176 file.hasInput = false;
1177 file.cleanName = nil;
1178 file.protocol = nil;
1179 file.path = nil;
1180 file.ext = nil;
1181 file.uri = nil;
1182 else
1183 vlc.msg.dbg("[VLSub] Video URI: "..item:uri())
1184 local parsed_uri = vlc.net.url_parse(item:uri())
1185 file.uri = item:uri()
1186 file.protocol = parsed_uri["protocol"]
1187 file.path = parsed_uri["path"]
1189 -- Corrections
1191 -- For windows
1192 file.path = string.match(file.path, "^/(%a:/.+)$") or file.path
1194 -- For file in archive
1195 local archive_path, name_in_archive = string.match(file.path, '^([^!]+)!/([^!/]*)$')
1196 if archive_path and archive_path ~= "" then
1197 file.path = string.gsub(archive_path, '\063', '%%')
1198 file.path = vlc.strings.decode_uri(file.path)
1199 file.completeName = string.gsub(name_in_archive, '\063', '%%')
1200 file.completeName = vlc.strings.decode_uri(file.completeName)
1201 file.is_archive = true
1202 else -- "classic" input
1203 file.path = vlc.strings.decode_uri(file.path)
1204 file.dir, file.completeName = string.match(file.path, '^(.+/)([^/]*)$')
1206 local file_stat = vlc.net.stat(file.path)
1207 if file_stat
1208 then
1209 file.stat = file_stat
1212 file.is_archive = false
1215 file.name, file.ext = string.match(file.completeName, '^([^/]-)%.?([^%.]*)$')
1217 if file.ext == "part" then
1218 file.name, file.ext = string.match(file.name, '^([^/]+)%.([^%.]+)$')
1221 file.hasInput = true;
1222 file.cleanName = string.gsub(file.name, "[%._]", " ")
1223 vlc.msg.dbg("[VLSub] file info "..(dump_xml(file)))
1225 collectgarbage()
1226 end,
1227 getMovieInfo = function()
1228 -- Clean video file name and check for season/episode pattern in title
1229 if not openSub.file.name then
1230 openSub.movie.title = ""
1231 openSub.movie.seasonNumber = ""
1232 openSub.movie.episodeNumber = ""
1233 return false
1236 local showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)[sS](%d%d)[eE](%d%d).*")
1238 if not showName then
1239 showName, seasonNumber, episodeNumber = string.match(openSub.file.cleanName, "(.+)(%d)[xX](%d%d).*")
1242 if showName then
1243 openSub.movie.title = showName
1244 openSub.movie.seasonNumber = seasonNumber
1245 openSub.movie.episodeNumber = episodeNumber
1246 else
1247 openSub.movie.title = openSub.file.cleanName
1248 openSub.movie.seasonNumber = ""
1249 openSub.movie.episodeNumber = ""
1251 collectgarbage()
1252 end,
1253 getMovieHash = function()
1254 -- Calculate movie hash
1255 openSub.actionLabel = lang["action_hash"]
1256 setMessage(openSub.actionLabel..": "..progressBarContent(0))
1258 local item = openSub.getInputItem()
1260 if not item then
1261 setError(lang["mess_no_input"])
1262 return false
1265 openSub.getFileInfo()
1267 if not openSub.file.path then
1268 setError(lang["mess_not_found"])
1269 return false
1272 local data_start = ""
1273 local data_end = ""
1274 local size
1275 local chunk_size = 65536
1277 -- Get data for hash calculation
1278 if openSub.file.is_archive then
1279 vlc.msg.dbg("[VLSub] Read hash data from stream")
1281 local file = vlc.stream(openSub.file.uri)
1282 local dataTmp1 = ""
1283 local dataTmp2 = ""
1284 size = chunk_size
1286 data_start = file:read(chunk_size)
1288 while data_end do
1289 size = size + string.len(data_end)
1290 dataTmp1 = dataTmp2
1291 dataTmp2 = data_end
1292 data_end = file:read(chunk_size)
1293 collectgarbage()
1295 data_end = string.sub((dataTmp1..dataTmp2), -chunk_size)
1296 elseif not file_exist(openSub.file.path)
1297 and openSub.file.stat then
1298 vlc.msg.dbg("[VLSub] Read hash data from stream")
1300 local file = vlc.stream(openSub.file.uri)
1302 if not file then
1303 vlc.msg.dbg("[VLSub] No stream")
1304 return false
1307 size = openSub.file.stat.size
1308 local decal = size%chunk_size
1310 data_start = file:read(chunk_size)
1312 -- "Seek" to the end
1313 file:read(decal)
1315 for i = 1, math.floor(((size-decal)/chunk_size))-2 do
1316 file:read(chunk_size)
1319 data_end = file:read(chunk_size)
1321 file = nil
1322 else
1323 vlc.msg.dbg("[VLSub] Read hash data from file")
1324 local file = io.open( openSub.file.path, "rb")
1325 if not file then
1326 vlc.msg.dbg("[VLSub] No stream")
1327 return false
1330 data_start = file:read(chunk_size)
1331 size = file:seek("end", -chunk_size) + chunk_size
1332 data_end = file:read(chunk_size)
1333 file = nil
1336 -- Hash calculation
1337 local lo = size
1338 local hi = 0
1339 local o,a,b,c,d,e,f,g,h
1340 local hash_data = data_start..data_end
1341 local max_size = 4294967296
1342 local overflow
1344 for i = 1, #hash_data, 8 do
1345 a,b,c,d,e,f,g,h = hash_data:byte(i,i+7)
1346 lo = lo + a + b*256 + c*65536 + d*16777216
1347 hi = hi + e + f*256 + g*65536 + h*16777216
1349 if lo > max_size then
1350 overflow = math.floor(lo/max_size)
1351 lo = lo-(overflow*max_size)
1352 hi = hi+overflow
1355 if hi > max_size then
1356 overflow = math.floor(hi/max_size)
1357 hi = hi-(overflow*max_size)
1361 openSub.file.bytesize = size
1362 openSub.file.hash = string.format("%08x%08x", hi,lo)
1363 vlc.msg.dbg("[VLSub] Video hash: "..openSub.file.hash)
1364 vlc.msg.dbg("[VLSub] Video bytesize: "..size)
1365 collectgarbage()
1366 return true
1367 end,
1368 checkSession = function()
1370 if openSub.session.token == "" then
1371 openSub.request("LogIn")
1372 else
1373 openSub.request("NoOperation")
1378 function searchHash()
1379 local sel = input_table["language"]:get_value()
1380 if sel == 0 then
1381 openSub.movie.sublanguageid = 'all'
1382 else
1383 openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
1386 openSub.getMovieHash()
1388 if openSub.file.hash then
1389 openSub.checkSession()
1390 openSub.request("SearchSubtitlesByHash")
1391 display_subtitles()
1395 function searchIMBD()
1396 openSub.movie.title = trim(input_table["title"]:get_text())
1397 openSub.movie.seasonNumber = tonumber(input_table["seasonNumber"]:get_text())
1398 openSub.movie.episodeNumber = tonumber(input_table["episodeNumber"]:get_text())
1400 local sel = input_table["language"]:get_value()
1401 if sel == 0 then
1402 openSub.movie.sublanguageid = 'all'
1403 else
1404 openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
1407 if openSub.movie.title ~= "" then
1408 openSub.checkSession()
1409 openSub.request("SearchSubtitles")
1410 display_subtitles()
1414 function display_subtitles()
1415 local mainlist = input_table["mainlist"]
1416 mainlist:clear()
1418 if openSub.itemStore == "0" then
1419 mainlist:add_value(lang["mess_no_res"], 1)
1420 setMessage("<b>"..lang["mess_complete"]..":</b> "..lang["mess_no_res"])
1421 elseif openSub.itemStore then
1422 for i, item in ipairs(openSub.itemStore) do
1423 mainlist:add_value(
1424 item.SubFileName..
1425 " ["..item.SubLanguageID.."]"..
1426 " ("..item.SubSumCD.." CD)", i)
1428 setMessage("<b>"..lang["mess_complete"]..":</b> "..#(openSub.itemStore).." "..lang["mess_res"])
1432 function get_first_sel(list)
1433 local selection = list:get_selection()
1434 for index, name in pairs(selection) do
1435 return index
1437 return 0
1440 function download_subtitles()
1441 local index = get_first_sel(input_table["mainlist"])
1443 if index == 0 then
1444 setMessage(lang["mess_no_selection"])
1445 return false
1448 openSub.actionLabel = lang["mess_downloading"]
1450 display_subtitles() -- reset selection
1452 local item = openSub.itemStore[index]
1454 if openSub.option.downloadBehaviour == 'manual' then
1456 local link = "<span style='color:#181'>"
1457 link = link.."<b>"..lang["mess_dowload_link"]..":</b>"
1458 link = link.."</span> &nbsp;"
1459 link = link.."</span> &nbsp;<a href='"..item.ZipDownloadLink.."'>"
1460 link = link..item.MovieReleaseName.."</a>"
1462 setMessage(link)
1463 return false
1464 elseif openSub.option.downloadBehaviour == 'load' then
1465 if add_sub("zip://"..item.ZipDownloadLink.."!/"..item.SubFileName) then
1466 setMessage(success_tag(lang["mess_loaded"]))
1468 return false
1471 local message = ""
1472 local subfileName = openSub.file.name
1474 if openSub.option.langExt then
1475 subfileName = subfileName.."."..item.SubLanguageID
1478 subfileName = subfileName.."."..item.SubFormat
1479 local tmp_dir
1480 local file_target_access = true
1482 if is_dir(openSub.file.dir) then
1483 tmp_dir = openSub.file.dir
1484 elseif openSub.conf.dirPath then
1485 tmp_dir = openSub.conf.dirPath
1487 message = "<br> "..error_tag(lang["mess_save_fail"].." &nbsp;"..
1488 "<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
1489 lang["mess_click_link"].."</a>")
1490 else
1491 setError(lang["mess_save_fail"].." &nbsp;"..
1492 "<a href='"..item.ZipDownloadLink.."'>"..
1493 lang["mess_click_link"].."</a>")
1494 return false
1497 local tmpFileURI, tmpFileName = dump_zip(
1498 item.ZipDownloadLink,
1499 tmp_dir,
1500 item.SubFileName)
1502 vlc.msg.dbg("[VLsub] tmpFileName: "..tmpFileName)
1504 -- Determine if the path to the video file is accessible for writing
1506 local target = openSub.file.dir..subfileName
1508 if not file_touch(target) then
1509 if openSub.conf.dirPath then
1510 target = openSub.conf.dirPath..slash..subfileName
1511 message = "<br> "..error_tag(lang["mess_save_fail"].." &nbsp;"..
1512 "<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
1513 lang["mess_click_link"].."</a>")
1514 else
1515 setError(lang["mess_save_fail"].." &nbsp;"..
1516 "<a href='"..item.ZipDownloadLink.."'>"..
1517 lang["mess_click_link"].."</a>")
1518 return false
1522 vlc.msg.dbg("[VLsub] Subtitles files: "..target)
1524 -- Unzipped data into file target
1526 local stream = vlc.stream(tmpFileURI)
1527 local data = ""
1528 local subfile = io.open(target, "wb")
1530 while data do
1531 subfile:write(data)
1532 data = stream:read(65536)
1535 subfile:flush()
1536 subfile:close()
1538 stream = nil
1539 collectgarbage()
1541 if not os.remove(tmpFileName) then
1542 vlc.msg.err("[VLsub] Unable to remove temp: "..tmpFileName)
1545 subfileURI = vlc.strings.make_uri(target)
1547 if not subfileURI then
1548 subfileURI = make_uri(target, true)
1551 -- load subtitles
1552 if add_sub(subfileURI) then
1553 message = success_tag(lang["mess_loaded"]) .. message
1557 setMessage(message)
1560 function dump_zip(url, dir, subfileName)
1561 -- Dump zipped data in a temporary file
1562 setMessage(openSub.actionLabel..": "..progressBarContent(0))
1563 local resp = get(url)
1565 if not resp then
1566 setError(lang["mess_no_response"])
1567 return false
1570 local tmpFileName = dir..slash..subfileName..".gz"
1571 if not file_touch(tmpFileName) then
1572 return false
1574 local tmpFile = assert(io.open(tmpFileName, "wb"))
1576 tmpFile:write(resp)
1577 tmpFile:flush()
1578 tmpFile:close()
1579 tmpFile = nil
1580 collectgarbage()
1581 return "zip://"..make_uri(tmpFileName, true).."!/"..subfileName, tmpFileName
1584 function add_sub(subfileURI)
1585 if vlc.item or vlc.input.item() then
1586 vlc.msg.dbg("[VLsub] Adding subtitle :" .. subfileURI)
1587 return vlc.input.add_subtitle(subfileURI)
1589 return false
1592 --[[ Interface helpers]]--
1594 function progressBarContent(pct)
1595 local accomplished = math.ceil(openSub.option.progressBarSize*pct/100)
1596 local left = openSub.option.progressBarSize - accomplished
1597 local content = "<span style='background-color:#181;color:#181;'>"..
1598 string.rep ("-", accomplished).."</span>"..
1599 "<span style='background-color:#fff;color:#fff;'>"..
1600 string.rep ("-", left)..
1601 "</span>"
1602 return content
1605 function setMessage(str)
1606 if input_table["message"] then
1607 input_table["message"]:set_text(str)
1608 dlg:update()
1612 function setError(mess)
1613 setMessage(error_tag(mess))
1616 function success_tag(str)
1617 return "<span style='color:#181'><b>"..
1618 lang["mess_success"]..":</b></span> "..str..""
1621 function error_tag(str)
1622 return "<span style='color:#B23'><b>"..
1623 lang["mess_error"]..":</b></span> "..str..""
1626 --[[ Network utils]]--
1628 function get(url)
1629 local host, path = parse_url(url)
1630 local header = {
1631 "GET "..path.." HTTP/1.1",
1632 "Host: "..host,
1633 "User-Agent: "..openSub.conf.userAgentHTTP,
1637 local request = table.concat(header, "\r\n")
1639 local response
1640 local status, response = http_req(host, 80, request)
1642 if status == 200 then
1643 return response
1644 else
1645 return false, status, response
1649 function http_req(host, port, request)
1650 local fd = vlc.net.connect_tcp(host, port)
1651 if not fd then return false end
1652 local pollfds = {}
1654 pollfds[fd] = vlc.net.POLLIN
1655 vlc.net.send(fd, request)
1656 vlc.net.poll(pollfds)
1658 local response = vlc.net.recv(fd, 1024)
1659 local headerStr, body = string.match(response, "(.-\r?\n)\r?\n(.*)")
1660 local header = parse_header(headerStr)
1661 local contentLength = tonumber(header["Content-Length"])
1662 local TransferEncoding = header["Transfer-Encoding"]
1663 local status = tonumber(header["statuscode"])
1664 local bodyLenght = string.len(body)
1665 local pct = 0
1667 --~ if status ~= 200 then return status end
1669 while contentLength and bodyLenght < contentLength do
1670 vlc.net.poll(pollfds)
1671 response = vlc.net.recv(fd, 1024)
1673 if response then
1674 body = body..response
1675 else
1676 vlc.net.close(fd)
1677 return false
1679 bodyLenght = string.len(body)
1680 pct = bodyLenght / contentLength * 100
1681 setMessage(openSub.actionLabel..": "..progressBarContent(pct))
1683 vlc.net.close(fd)
1685 return status, body
1688 function parse_header(data)
1689 local header = {}
1691 for name, s, val in string.gfind(data, "([^%s:]+)(:?)%s([^\n]+)\r?\n") do
1692 if s == "" then header['statuscode'] = tonumber(string.sub (val, 1 , 3))
1693 else header[name] = val end
1695 return header
1698 function parse_url(url)
1699 local url_parsed = vlc.net.url_parse(url)
1700 return url_parsed["host"], url_parsed["path"], url_parsed["option"]
1703 --[[ XML utils]]--
1705 function parse_xml(data)
1706 local tree = {}
1707 local stack = {}
1708 local tmp = {}
1709 local level = 0
1710 local op, tag, p, empty, val
1711 table.insert(stack, tree)
1713 for op, tag, p, empty, val in string.gmatch(
1714 data,
1715 "[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)[%s\r\n\t]*"
1716 ) do
1717 if op=="/" then
1718 if level>0 then
1719 level = level - 1
1720 table.remove(stack)
1722 else
1723 level = level + 1
1724 if val == "" then
1725 if type(stack[level][tag]) == "nil" then
1726 stack[level][tag] = {}
1727 table.insert(stack, stack[level][tag])
1728 else
1729 if type(stack[level][tag][1]) == "nil" then
1730 tmp = nil
1731 tmp = stack[level][tag]
1732 stack[level][tag] = nil
1733 stack[level][tag] = {}
1734 table.insert(stack[level][tag], tmp)
1736 tmp = nil
1737 tmp = {}
1738 table.insert(stack[level][tag], tmp)
1739 table.insert(stack, tmp)
1741 else
1742 if type(stack[level][tag]) == "nil" then
1743 stack[level][tag] = {}
1745 stack[level][tag] = vlc.strings.resolve_xml_special_chars(val)
1746 table.insert(stack, {})
1748 if empty ~= "" then
1749 stack[level][tag] = ""
1750 level = level - 1
1751 table.remove(stack)
1756 collectgarbage()
1757 return tree
1760 function parse_xmlrpc(data)
1761 local tree = {}
1762 local stack = {}
1763 local tmp = {}
1764 local tmpTag = ""
1765 local level = 0
1766 local op, tag, p, empty, val
1767 table.insert(stack, tree)
1769 for op, tag, p, empty, val in string.gmatch(
1770 data,
1771 "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)"
1772 ) do
1773 if op=="/" then
1774 if tag == "member" or tag == "array" then
1775 if level>0 then
1776 level = level - 1
1777 table.remove(stack)
1780 elseif tag == "name" then
1781 level = level + 1
1782 if val~= "" then tmpTag = vlc.strings.resolve_xml_special_chars(val) end
1784 if type(stack[level][tmpTag]) == "nil" then
1785 stack[level][tmpTag] = {}
1786 table.insert(stack, stack[level][tmpTag])
1787 else
1788 tmp = nil
1789 tmp = {}
1790 table.insert(stack[level-1], tmp)
1792 stack[level] = nil
1793 stack[level] = tmp
1794 table.insert(stack, tmp)
1796 if empty ~= "" then
1797 level = level - 1
1798 stack[level][tmpTag] = ""
1799 table.remove(stack)
1801 elseif tag == "array" then
1802 level = level + 1
1803 tmp = nil
1804 tmp = {}
1805 table.insert(stack[level], tmp)
1806 table.insert(stack, tmp)
1807 elseif val ~= "" then
1808 stack[level][tmpTag] = vlc.strings.resolve_xml_special_chars(val)
1811 collectgarbage()
1812 return tree
1815 function dump_xml(data)
1816 local level = 0
1817 local stack = {}
1818 local dump = ""
1820 local function parse(data, stack)
1821 local data_index = {}
1822 local k
1823 local v
1824 local i
1825 local tb
1827 for k,v in pairs(data) do
1828 table.insert(data_index, {k, v})
1829 table.sort(data_index, function(a, b)
1830 return a[1] < b[1]
1831 end)
1834 for i,tb in pairs(data_index) do
1835 k = tb[1]
1836 v = tb[2]
1837 if type(k)=="string" then
1838 dump = dump.."\r\n"..string.rep (" ", level).."<"..k..">"
1839 table.insert(stack, k)
1840 level = level + 1
1841 elseif type(k)=="number" and k ~= 1 then
1842 dump = dump.."\r\n"..string.rep (" ", level-1).."<"..stack[level]..">"
1845 if type(v)=="table" then
1846 parse(v, stack)
1847 elseif type(v)=="string" then
1848 dump = dump..(vlc.strings.convert_xml_special_chars(v) or v)
1849 elseif type(v)=="number" then
1850 dump = dump..v
1851 else
1852 dump = dump..tostring(v)
1855 if type(k)=="string" then
1856 if type(v)=="table" then
1857 dump = dump.."\r\n"..string.rep (" ", level-1).."</"..k..">"
1858 else
1859 dump = dump.."</"..k..">"
1861 table.remove(stack)
1862 level = level - 1
1864 elseif type(k)=="number" and k ~= #data then
1865 if type(v)=="table" then
1866 dump = dump.."\r\n"..string.rep (" ", level-1).."</"..stack[level]..">"
1867 else
1868 dump = dump.."</"..stack[level]..">"
1873 parse(data, stack)
1874 collectgarbage()
1875 return dump
1878 --[[ Misc utils]]--
1880 function make_uri(str, encode)
1881 local windowdrive = string.match(str, "^(%a:%).+$")
1882 if encode then
1883 local encodedPath = ""
1884 for w in string.gmatch(str, "/([^/]+)") do
1885 encodedPath = encodedPath.."/"..vlc.strings.encode_uri_component(w)
1887 str = encodedPath
1889 if windowdrive then
1890 return "file:///"..windowdrive..str
1891 else
1892 return "file://"..str
1896 function file_touch(name) -- test writetability
1897 if not name or trim(name) == ""
1898 then return false end
1900 local f=io.open(name ,"w")
1901 if f~=nil then
1902 io.close(f)
1903 return true
1904 else
1905 return false
1909 function file_exist(name) -- test readability
1910 if not name or trim(name) == ""
1911 then return false end
1912 local f=io.open(name ,"r")
1913 if f~=nil then
1914 io.close(f)
1915 return true
1916 else
1917 return false
1921 function is_dir(path)
1922 if not path or trim(path) == ""
1923 then return false end
1924 -- Remove slash at the or it won't work on Windows
1925 path = string.gsub(path, "^(.-)[\\/]?$", "%1")
1926 local f, _, code = io.open(path, "rb")
1928 if f then
1929 _, _, code = f:read("*a")
1930 f:close()
1931 if code == 21 then
1932 return true
1934 elseif code == 13 then
1935 return true
1938 return false
1941 function list_dir(path)
1942 if not path or trim(path) == ""
1943 then return false end
1944 local dir_list_cmd
1945 local list = {}
1946 if not is_dir(path) then return false end
1948 if openSub.conf.os == "win" then
1949 dir_list_cmd = io.popen('dir /b "'..path..'"')
1950 elseif openSub.conf.os == "lin" then
1951 dir_list_cmd = io.popen('ls -1 "'..path..'"')
1954 if dir_list_cmd then
1955 for filename in dir_list_cmd:lines() do
1956 if string.match(filename, "^[^%s]+.+$") then
1957 table.insert(list, filename)
1960 return list
1961 else
1962 return false
1966 function mkdir_p(path)
1967 if not path or trim(path) == ""
1968 then return false end
1969 if openSub.conf.os == "win" then
1970 os.execute('mkdir "' .. path..'"')
1971 elseif openSub.conf.os == "lin" then
1972 os.execute("mkdir -p '" .. path.."'")
1976 function is_window_path(path)
1977 return string.match(path, "^(%a:%).+$")
1980 function is_win_safe(path)
1981 if not path or trim(path) == ""
1982 or not is_window_path(path)
1983 then return false end
1984 return string.match(path, "^%a?%:?[\\%w%p%s§¤]+$")
1987 function trim(str)
1988 if not str then return "" end
1989 return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1")
1992 function remove_tag(str)
1993 return string.gsub(str, "{[^}]+}", "")
1996 function sleep(sec)
1997 local t = vlc.misc.mdate()
1998 vlc.misc.mwait(t + sec*1000*1000)