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.
27 -- You can set here your default language by replacing nil with your language code (see below)
36 downloadBehaviour
= 'save',
39 showMediaInformation
= true,
42 translations_avail
= {
49 pob
= 'Brazilian Portuguese',
57 int_descr
= 'Download subtitles from OpenSubtitles.org',
58 int_research
= 'Research',
59 int_config
= 'Config',
60 int_configuration
= 'Configuration',
62 int_search_hash
= 'Search by hash',
63 int_search_name
= 'Search by name',
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',
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>"..
91 " <b><u>Usage:</u></b><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>"..
95 " Choose the language for your subtitles and click on the button corresponding to one of the two research method provided by VLSub:<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>"..
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>"..
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>"..
108 " <b>/!\\ Beware :</b> Existing subtitles are overwrited without asking confirmation, so put them elsewhere if thet're important.<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'
150 {'bul', 'Bulgarian'},
159 {'epo', 'Esperanto'},
169 {'hun', 'Hungarian'},
170 {'ice', 'Icelandic'},
171 {'ind', 'Indonesian'},
178 {'lit', 'Lithuanian'},
179 {'ltz', 'Luxembourgish'},
180 {'mac', 'Macedonian'},
182 {'mal', 'Malayalam'},
183 {'mon', 'Mongolian'},
184 {'nor', 'Norwegian'},
188 {'por', 'Portuguese'},
189 {'pob', 'Brazilian Portuguese'},
193 {'sin', 'Sinhalese'},
195 {'slv', 'Slovenian'},
204 {'ukr', 'Ukrainian'},
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
= {
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()
285 title
= "VLsub 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" }
296 vlc
.msg
.dbg("[VLsub] Welcome")
300 if vlc
.input
.item() then
301 openSub
.getFileInfo()
302 openSub
.getMovieInfo()
312 function deactivate()
313 vlc
.msg
.dbg("[VLsub] Bye bye!")
318 if openSub
.session
.token
and openSub
.session
.token
~= "" then
319 openSub
.request("LogOut")
332 function meta_changed()
336 function input_changed()
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"])
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)
398 dlg
:add_label("<a href='"..openSub
.conf
.dirPath
.."'>"..lang
["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
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 (" ", 100), 1, 2, 3, 1)
432 dlg
:add_button(lang
["int_ok"], show_main
, 4, 2, 1, 1)
435 function trigger_menu(dlg_id
)
438 dlg
= vlc
.dialog(openSub
.conf
.useragent
)
440 elseif dlg_id
== 2 then
442 dlg
= vlc
.dialog(openSub
.conf
.useragent
..': '..lang
["int_configuration"])
444 elseif dlg_id
== 3 then
446 dlg
= vlc
.dialog(openSub
.conf
.useragent
..': '..lang
["int_help"])
449 collectgarbage() --~ !important
465 vlc
.msg
.dbg("[VLSub] Closing dialog")
468 --~ dlg:delete() -- Throw an error
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
496 elseif b
[1] == openSub
.option
[opt
] then
499 return a
[ind
] < b
[ind
]
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
517 for k
, l
in ipairs(conf
) do
518 if default_isset
then
519 input_table
[select_id
]:add_value(l
[2], k
)
522 input_table
[select_id
]:add_value(l
[2], k
)
523 input_table
[select_id
]:add_value(default
, 0)
525 input_table
[select_id
]:add_value(default
, 0)
526 input_table
[select_id
]:add_value(l
[2], k
)
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
539 for k
, v
in pairs(openSub
.option
.translation
) do
540 eng_translation
[k
] = v
543 -- Get available translation full name from code
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"
553 openSub
.conf
.os
= "lin"
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")
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
)
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
591 elseif file_exist(datadir
..dirPath
..filePath
) then
592 openSub
.conf
.dirPath
= datadir
..dirPath
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
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"
639 and file_exist(openSub
.conf
.filePath
) then
640 vlc
.msg
.dbg("[VLSub] Loading config file: "..openSub
.conf
.filePath
)
643 vlc
.msg
.dbg("[VLSub] No config file")
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
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
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
)
676 vlc
.msg
.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
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
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
})
694 table.insert(openSub
.conf
.translations_avail
, {k
, l
})
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")
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
717 openSub
.option
[key
] = value
720 if value
== "true" then
721 openSub
.option
[key
] = true
722 elseif value
== "false" then
723 openSub
.option
[key
] = false
725 openSub
.option
[key
] = value
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")
738 openSub
.option
.translation
= nil
740 openSub
.option
.translation
= parse_xml(resp
)
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
]
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()
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]
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
791 openSub
.option
[opt
] = nil
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
)
821 local other_dirs
= {}
823 for path
in vlc
.config
.get("sub-autodetect-path"):gmatch("[^,]+") do
825 if path
~= (openSub
.conf
.dirPath
or "")..sub_dir
then
826 table.insert(other_dirs
, path
)
829 openSub
.conf
.dirPath
= dir_path
831 table.insert(other_dirs
,
832 string.gsub(dir_path
, "^(.-)[\\/]?$", "%1")..sub_dir
)
834 if not is_dir(dir_path
) then
838 openSub
.conf
.filePath
= openSub
.conf
.dirPath
..slash
.."vlsub_conf.xml"
839 openSub
.conf
.localePath
= openSub
.conf
.dirPath
..slash
.."locale"
841 openSub
.conf
.filePath
= nil
842 openSub
.conf
.localePath
= nil
844 vlc
.config
.set("sub-autodetect-path", table.concat(other_dirs
, ", "))
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()
855 if not config_saved
then
856 setError(lang
["mess_err_conf_access"])
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
)
882 vlc
.msg
.dbg("[VLSub] Unable fount a suitable path to save config, please set it manually")
883 setError(lang
["mess_err_conf_access"])
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
)
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"]))
925 function set_translation(lg
)
926 openSub
.option
.translation
= nil
927 openSub
.option
.translation
= {}
930 for k
, v
in pairs(eng_translation
) do
931 openSub
.option
.translation
[k
] = v
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
)
942 -- Load translation file from internet
944 get_available_translations()
947 if not all_trsl
or not all_trsl
[lg
] then
948 vlc
.msg
.dbg("[VLSub] Error, translation not found")
951 openSub
.option
.translation
= all_trsl
[lg
]
958 lang
= openSub
.option
.translation
968 url
= "http://api.opensubtitles.org/xml-rpc",
970 userAgentHTTP
= "VLSub",
971 useragent
= "VLSub 0.9",
972 translations_avail
= {},
973 downloadBehaviours
= nil,
974 languages
= languages
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
)
1008 "POST "..path
.." HTTP/1.1",
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
1019 local status
, responseStr
= http_req(host
, 80, request
)
1021 if status
== 200 then
1022 response
= parse_xmlrpc(responseStr
)
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
..")")
1033 setError("Server not responding")
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
)
1046 elseif status
== 503 then
1047 setError("Server overloaded, please retry later")
1052 getMethodBase
= function(methodName
, param
)
1053 if openSub
.methods
[methodName
].methodName
then
1054 methodName
= openSub
.methods
[methodName
].methodName
1059 methodName
=methodName
,
1060 params
={ param
=param
}}}
1067 openSub
.actionLabel
= lang
["action_login"]
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
} }
1075 callback
= function(resp
)
1076 openSub
.session
.token
= resp
.token
1077 openSub
.session
.loginTime
= os
.time()
1083 openSub
.actionLabel
= lang
["action_logout"]
1085 { value
={ string=openSub
.session
.token
} }
1088 callback
= function()
1094 openSub
.actionLabel
= lang
["action_noop"]
1096 { value
={ string=openSub
.session
.token
} }
1099 callback
= function(resp
)
1103 SearchSubtitlesByHash
= {
1104 methodName
= "SearchSubtitles",
1106 openSub
.actionLabel
= lang
["action_search"]
1107 setMessage(openSub
.actionLabel
..": "..progressBarContent(0))
1110 { value
={ string=openSub
.session
.token
} },
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
} } }}}}}}}
1125 callback
= function(resp
)
1126 openSub
.itemStore
= resp
.data
1130 methodName
= "SearchSubtitles",
1132 openSub
.actionLabel
= lang
["action_search"]
1133 setMessage(openSub
.actionLabel
..": "..progressBarContent(0))
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
} })
1153 { value
={ string=openSub
.session
.token
} },
1163 callback
= function(resp
)
1164 openSub
.itemStore
= resp
.data
1168 getInputItem
= function()
1169 return vlc
.item
or vlc
.input
.item()
1171 getFileInfo
= function()
1172 -- Get video file path, name, extension from input uri
1173 local item
= openSub
.getInputItem()
1174 local file
= openSub
.file
1176 file
.hasInput
= false;
1177 file
.cleanName
= nil;
1178 file
.protocol
= nil;
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"]
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
)
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
)))
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
= ""
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).*")
1243 openSub
.movie
.title
= showName
1244 openSub
.movie
.seasonNumber
= seasonNumber
1245 openSub
.movie
.episodeNumber
= episodeNumber
1247 openSub
.movie
.title
= openSub
.file
.cleanName
1248 openSub
.movie
.seasonNumber
= ""
1249 openSub
.movie
.episodeNumber
= ""
1253 getMovieHash
= function()
1254 -- Calculate movie hash
1255 openSub
.actionLabel
= lang
["action_hash"]
1256 setMessage(openSub
.actionLabel
..": "..progressBarContent(0))
1258 local item
= openSub
.getInputItem()
1261 setError(lang
["mess_no_input"])
1265 openSub
.getFileInfo()
1267 if not openSub
.file
.path
then
1268 setError(lang
["mess_not_found"])
1272 local data_start
= ""
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
)
1286 data_start
= file
:read(chunk_size
)
1289 size
= size
+ string.len(data_end
)
1292 data_end
= file
:read(chunk_size
)
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
)
1303 vlc
.msg
.dbg("[VLSub] No stream")
1307 size
= openSub
.file
.stat
.size
1308 local decal
= size
%chunk_size
1310 data_start
= file
:read(chunk_size
)
1312 -- "Seek" to the end
1315 for i
= 1, math
.floor(((size
-decal
)/chunk_size
))-2 do
1316 file
:read(chunk_size
)
1319 data_end
= file
:read(chunk_size
)
1323 vlc
.msg
.dbg("[VLSub] Read hash data from file")
1324 local file
= io
.open( openSub
.file
.path
, "rb")
1326 vlc
.msg
.dbg("[VLSub] No stream")
1330 data_start
= file
:read(chunk_size
)
1331 size
= file
:seek("end", -chunk_size
) + chunk_size
1332 data_end
= file
:read(chunk_size
)
1339 local o
,a
,b
,c
,d
,e
,f
,g
,h
1340 local hash_data
= data_start
..data_end
1341 local max_size
= 4294967296
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
)
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
)
1368 checkSession
= function()
1370 if openSub
.session
.token
== "" then
1371 openSub
.request("LogIn")
1373 openSub
.request("NoOperation")
1378 function searchHash()
1379 local sel
= input_table
["language"]:get_value()
1381 openSub
.movie
.sublanguageid
= 'all'
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")
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()
1402 openSub
.movie
.sublanguageid
= 'all'
1404 openSub
.movie
.sublanguageid
= openSub
.conf
.languages
[sel
][1]
1407 if openSub
.movie
.title
~= "" then
1408 openSub
.checkSession()
1409 openSub
.request("SearchSubtitles")
1414 function display_subtitles()
1415 local mainlist
= input_table
["mainlist"]
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
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
1440 function download_subtitles()
1441 local index
= get_first_sel(input_table
["mainlist"])
1444 setMessage(lang
["mess_no_selection"])
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> "
1459 link
= link
.."</span> <a href='"..item
.ZipDownloadLink
.."'>"
1460 link
= link
..item
.MovieReleaseName
.."</a>"
1464 elseif openSub
.option
.downloadBehaviour
== 'load' then
1465 if add_sub("zip://"..item
.ZipDownloadLink
.."!/"..item
.SubFileName
) then
1466 setMessage(success_tag(lang
["mess_loaded"]))
1472 local subfileName
= openSub
.file
.name
1474 if openSub
.option
.langExt
then
1475 subfileName
= subfileName
.."."..item
.SubLanguageID
1478 subfileName
= subfileName
.."."..item
.SubFormat
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"].." "..
1488 "<a href='"..vlc
.strings
.make_uri(openSub
.conf
.dirPath
).."'>"..
1489 lang
["mess_click_link"].."</a>")
1491 setError(lang
["mess_save_fail"].." "..
1492 "<a href='"..item
.ZipDownloadLink
.."'>"..
1493 lang
["mess_click_link"].."</a>")
1497 local tmpFileURI
, tmpFileName
= dump_zip(
1498 item
.ZipDownloadLink
,
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"].." "..
1512 "<a href='"..vlc
.strings
.make_uri(openSub
.conf
.dirPath
).."'>"..
1513 lang
["mess_click_link"].."</a>")
1515 setError(lang
["mess_save_fail"].." "..
1516 "<a href='"..item
.ZipDownloadLink
.."'>"..
1517 lang
["mess_click_link"].."</a>")
1522 vlc
.msg
.dbg("[VLsub] Subtitles files: "..target
)
1524 -- Unzipped data into file target
1526 local stream
= vlc
.stream(tmpFileURI
)
1528 local subfile
= io
.open(target
, "wb")
1532 data
= stream
:read(65536)
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)
1552 if add_sub(subfileURI
) then
1553 message
= success_tag(lang
["mess_loaded"]) .. 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
)
1566 setError(lang
["mess_no_response"])
1570 local tmpFileName
= dir
..slash
..subfileName
..".gz"
1571 if not file_touch(tmpFileName
) then
1574 local tmpFile
= assert(io
.open(tmpFileName
, "wb"))
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
)
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
)..
1605 function setMessage(str
)
1606 if input_table
["message"] then
1607 input_table
["message"]:set_text(str
)
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]]--
1629 local host
, path
= parse_url(url
)
1631 "GET "..path
.." HTTP/1.1",
1633 "User-Agent: "..openSub
.conf
.userAgentHTTP
,
1637 local request
= table.concat(header
, "\r\n")
1640 local status
, response
= http_req(host
, 80, request
)
1642 if status
== 200 then
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
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
)
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)
1674 body
= body
..response
1679 bodyLenght
= string.len(body
)
1680 pct
= bodyLenght
/ contentLength
* 100
1681 setMessage(openSub
.actionLabel
..": "..progressBarContent(pct
))
1688 function parse_header(data
)
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
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"]
1705 function parse_xml(data
)
1710 local op
, tag, p
, empty
, val
1711 table.insert(stack
, tree
)
1713 for op
, tag, p
, empty
, val
in string.gmatch(
1715 "[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)[%s\r\n\t]*"
1725 if type(stack
[level
][tag]) == "nil" then
1726 stack
[level
][tag] = {}
1727 table.insert(stack
, stack
[level
][tag])
1729 if type(stack
[level
][tag][1]) == "nil" then
1731 tmp
= stack
[level
][tag]
1732 stack
[level
][tag] = nil
1733 stack
[level
][tag] = {}
1734 table.insert(stack
[level
][tag], tmp
)
1738 table.insert(stack
[level
][tag], tmp
)
1739 table.insert(stack
, tmp
)
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
, {})
1749 stack
[level
][tag] = ""
1760 function parse_xmlrpc(data
)
1766 local op
, tag, p
, empty
, val
1767 table.insert(stack
, tree
)
1769 for op
, tag, p
, empty
, val
in string.gmatch(
1771 "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)"
1774 if tag == "member" or tag == "array" then
1780 elseif tag == "name" then
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
])
1790 table.insert(stack
[level
-1], tmp
)
1794 table.insert(stack
, tmp
)
1798 stack
[level
][tmpTag
] = ""
1801 elseif tag == "array" then
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
)
1815 function dump_xml(data
)
1820 local function parse(data
, stack
)
1821 local data_index
= {}
1827 for k
,v
in pairs(data
) do
1828 table.insert(data_index
, {k
, v
})
1829 table.sort(data_index
, function(a
, b
)
1834 for i
,tb
in pairs(data_index
) do
1837 if type(k
)=="string" then
1838 dump
= dump
.."\r\n"..string.rep (" ", level
).."<"..k
..">"
1839 table.insert(stack
, k
)
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
1847 elseif type(v
)=="string" then
1848 dump
= dump
..(vlc
.strings
.convert_xml_special_chars(v
) or v
)
1849 elseif type(v
)=="number" then
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
..">"
1859 dump
= dump
.."</"..k
..">"
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
]..">"
1868 dump
= dump
.."</"..stack
[level
]..">"
1880 function make_uri(str
, encode
)
1881 local windowdrive
= string.match(str
, "^(%a:%).+$")
1883 local encodedPath
= ""
1884 for w
in string.gmatch(str
, "/([^/]+)") do
1885 encodedPath
= encodedPath
.."/"..vlc
.strings
.encode_uri_component(w
)
1890 return "file:///"..windowdrive
..str
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")
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")
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")
1929 _
, _
, code
= f
:read("*a")
1934 elseif code
== 13 then
1941 function list_dir(path
)
1942 if not path
or trim(path
) == ""
1943 then return false end
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
)
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§¤]+$")
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
, "{[^}]+}", "")
1997 local t
= vlc
.misc
.mdate()
1998 vlc
.misc
.mwait(t
+ sec
*1000*1000)