diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..667ae92 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +lunit 0.5-1 diff --git a/tests/vlsub_core.lua b/tests/vlsub_core.lua new file mode 100644 index 0000000..5a5d837 --- /dev/null +++ b/tests/vlsub_core.lua @@ -0,0 +1,108 @@ +require "lunit" +package.path = package.path .. ";../vlsub.lua" +local vlsub = require("vlsub") + +module( "vlsub_core", lunit.testcase, package.seeall) + +function test_last_subtitle_time_text_shouldReturnQuestionMark_WhenNoDuration() + -- Given + lang_mock = {int_last_sub="last sub at"} + subtitle_mock = {SubLastTS=nil} + -- When + local result = vlsub.last_subtitle_time_text(subtitle_mock, lang_mock) + -- Then + assert_equal("[last sub at ?]", result) +end + +function test_last_subtitle_time_text_shouldReturnQuestionMark_WhenDurationFormatIsWrong() + -- Given + lang_mock = {int_last_sub="last sub at"} + subtitle_mock = {SubLastTS="123456"} + -- When + local result = vlsub.last_subtitle_time_text(subtitle_mock, lang_mock) + -- Then + assert_equal("[last sub at ?]", result) +end + +function test_last_subtitle_time_text_shouldReturnDuration_WhenDurationFormatIsRight() + -- Given + lang_mock = {int_last_sub="last sub at"} + subtitle_mock = {SubLastTS="12:34:56"} + -- When + local result = vlsub.last_subtitle_time_text(subtitle_mock, lang_mock) + -- Then + assert_equal("[last sub at 12:34:56]", result) +end + +function test_date_string_to_time_shouldReturnTime_FromStringFormatedWithHoursMinutesSeconds() + -- When + local result = vlsub.date_string_to_time("01:02:03") + + -- Then + assert_equal(3723, result) +end + +function test_date_string_to_time_shouldReturnZero_WhenGivenMalformatedString() + -- When + local result = vlsub.date_string_to_time("01.23azerty") + + -- Then + assert_equal(0, result) +end + +function test_order_subs_shouldOrderUnorderedSubs_ByDistanceBetweenLastSpokenLineAndMovieDuration() + -- Given + local movie_duration = 60 + local unordered_table = {} + local incorrect = {SubLastTS="xxxxx"} + local greater = {SubLastTS="00:01:10"} + local equals = {SubLastTS="00:01:00"} + local lesser = {SubLastTS="00:00:10"} + table.insert(unordered_table, greater) + table.insert(unordered_table, incorrect) + table.insert(unordered_table, equals) + table.insert(unordered_table, lesser) + + -- When + local result = vlsub.order_by_ascending_distance_between_last_sub_time_and_movie_duration(unordered_table, movie_duration) + + -- Then + assert_equal(table_tostring({equals, greater, lesser, incorrect}), table_tostring(result)) +end + +-- test-utils +function table_val_to_str ( v ) + if "string" == type( v ) then + v = string.gsub( v, "\n", "\\n" ) + if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then + return "'" .. v .. "'" + end + return '"' .. string.gsub(v,'"', '\\"' ) .. '"' + else + return "table" == type( v ) and table_tostring( v ) or + tostring( v ) + end +end + +function table_key_to_str ( k ) + if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then + return k + else + return "[" .. table_val_to_str( k ) .. "]" + end +end + +function table_tostring( tbl ) + local result, done = {}, {} + for k, v in ipairs( tbl ) do + table.insert( result, table_val_to_str( v ) ) + done[ k ] = true + end + for k, v in pairs( tbl ) do + if not done[ k ] then + table.insert( result, + table_key_to_str( k ) .. "=" .. table_val_to_str( v ) ) + end + end + return "{" .. table.concat( result, "," ) .. "}" +end \ No newline at end of file diff --git a/tests/vlsub_interface_data.lua b/tests/vlsub_interface_data.lua new file mode 100644 index 0000000..0faf91b --- /dev/null +++ b/tests/vlsub_interface_data.lua @@ -0,0 +1,44 @@ +require "lunit" +package.path = package.path .. ";../vlsub.lua" +local vlsub = require("vlsub") + +module( "vlsub_interface_data", lunit.testcase, package.seeall) + +function test_movie_length_text_shouldReturnNone_WhenNoMovie() + -- Given + function item_mock(string) + return nil + end + vlc_mock = {input={item=item_mock}} + lang_mock = {int_movie_duration="movie duration"} + -- When + local result = vlsub.movie_duration_text(vlc_mock, lang_mock) + -- Then + assert_equal(result, "movie duration : ?") +end + +function test_movie_length_text_shouldReturnNone_WhenMovieHasNone() + -- Given + function item_mock(string) + return {duration=function() return -1 end} + end + vlc_mock = {input={item=item_mock}} + lang_mock = {int_movie_duration="movie duration"} + -- When + local result = vlsub.movie_duration_text(vlc_mock, lang_mock) + -- Then + assert_equal(result, "movie duration : ?") +end + +function test_movie_length_text_shouldReturnLength_WhenMovieHasOne() + -- Given + function item_mock(string) + return {duration=function() return 70 end} + end + vlc_mock = {input={item=item_mock}} + lang_mock = {int_movie_duration="movie duration"} + -- When + local result = vlsub.movie_duration_text(vlc_mock, lang_mock) + -- Then + assert_equal(result, "movie duration : 00:01:10") +end diff --git a/vlsub.lua b/vlsub.lua index a4c2a84..c41f9a6 100644 --- a/vlsub.lua +++ b/vlsub.lua @@ -3,7 +3,7 @@ VLSub Extension for VLC media player 1.1 and 2.0 Copyright 2013 Guillaume Le Maout Authors: Guillaume Le Maout -Contact: +Contact: http://addons.videolan.org/messages/?action=newmessage&username=exebetche Bug report: http://addons.videolan.org/content/show.php/?content=148752 @@ -23,15 +23,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. --]] - --[[ Global var ]]-- + --[[ Global var ]]-- -- You can set here your default language by replacing nil with --- your language code (see below).Example: --- language = "fre", --- language = "ger", +-- your language code (see below).Example: +-- language = "fre", +-- language = "ger", -- language = "eng", -- ... +local export = {} local options = { language = nil, downloadBehaviour = 'save', @@ -42,8 +43,8 @@ local options = { intLang = 'eng', translations_avail = { eng = 'English', - cze = 'Czech', - dan = 'Danish', + cze = 'Czech', + dan = 'Danish', dut = 'Nederlands', fre = 'Français', ell = 'Greek', @@ -92,70 +93,71 @@ local options = { int_os_username = 'Username', int_os_password = 'Password', int_help_mess =[[ - Download subtitles from + Download subtitles from opensubtitles.org and display them while watching a video.

Usage:

- Start your video. If you use Vlsub witout playing a video - you will get a link to download the subtitles in your browser + Start your video. If you use Vlsub witout playing a video + you will get a link to download the subtitles in your browser but the subtitles won't be saved and loaded automatically.

- Choose the language for your subtitles and click on the - button corresponding to one of the two research methods + Choose the language for your subtitles and click on the + button corresponding to one of the two research methods provided by VLSub:

Method 1: Search by hash
- It is recommended to try this method first, because it - performs a research based on the video file print, so you + 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.

Method 2: Search by name
- If you have no luck with the first method, just check the - title is correct before clicking. If you search subtitles - for a series, you can also provide a season and episode + If you have no luck with the first method, just check the + title is correct before clicking. If you search subtitles + for a series, you can also provide a season and episode number.

Downloading Subtitles
Select one subtitle in the list and click on 'Download'.
- It will be put in the same directory that your video, with + It will be put in the same directory that your video, with the same name (different extension) - so VLC will load them automatically the next time you'll + so VLC will load them automatically the next time you'll start the video.

- /!\\ Beware : Existing subtitles are overwritten - without asking confirmation, so put them elsewhere if + /!\\ Beware : Existing subtitles are overwritten + without asking confirmation, so put them elsewhere if they're important.

- Find more VLC extensions at + Find more VLC extensions at addons.videolan.org. ]], int_no_support_mess = [[ - VLSub is not working with Vlc 2.1.x on + VLSub is not working with Vlc 2.1.x on any platform - because the lua "net" module needed to interact - with opensubtitles has been + because the lua "net" module needed to interact + with opensubtitles has been removed in this release for the extensions.
Works with Vlc 2.2 on mac and linux.
- On windows you have to install an older version + On windows you have to install an older version of Vlc (2.0.8 for example) to use Vlsub:
- http://download.videolan.org/pub/videolan/vlc/2.0.8/
]], - + int_movie_duration = 'Movie duration', + int_last_sub = 'Last sub at', action_login = 'Logging in', action_logout = 'Logging out', action_noop = 'Checking session', action_search = 'Searching subtitles', action_hash = 'Calculating movie hash', - + mess_success = 'Success', mess_error = 'Error', mess_no_response = 'Server not responding', @@ -321,12 +323,12 @@ local lang_os_to_iso = { local dlg = nil local input_table = {} -- General widget id reference -local select_conf = {} -- Drop down widget / option table association +local select_conf = {} -- Drop down widget / option table association --[[ VLC extension stuff ]]-- function descriptor() - return { + return { title = "VLsub 0.9.13", version = "0.9.13", author = "exebetche", @@ -339,17 +341,17 @@ end function activate() vlc.msg.dbg("[VLsub] Welcome") - - if not check_config() then + + if not check_config() then vlc.msg.err("[VLsub] Unsupported VLC version") - return false + return false end - + if vlc.input.item() then openSub.getFileInfo() openSub.getMovieInfo() end - + show_main() end @@ -360,18 +362,18 @@ end function deactivate() vlc.msg.dbg("[VLsub] Bye bye!") if dlg then - dlg:hide() + dlg:hide() end - + if openSub.session.token and openSub.session.token ~= "" then openSub.request("LogOut") end end function menu() - return { - lang.int_research, - lang.int_config, + return { + lang.int_research, + lang.int_config, lang.int_help } end @@ -388,52 +390,65 @@ end --[[ Interface data ]]-- +function movie_duration_text(vlc_global, lang_global) + lang_global = lang_global or lang + vlc_global = vlc_global or vlc + local movie = vlc_global.input.item() + local duration = "?" + if movie and movie:duration() >= 0 then + duration = os.date("!%X",movie:duration()) + end + return string.format("%s : %s", lang_global["int_movie_duration"], duration) +end +export.movie_duration_text = movie_duration_text + function interface_main() dlg:add_label(lang["int_default_lang"]..':', 1, 1, 1, 1) input_table['language'] = dlg:add_dropdown(2, 1, 2, 1) - dlg:add_button(lang["int_search_hash"], + dlg:add_button(lang["int_search_hash"], searchHash, 4, 1, 1, 1) - - dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1) + + dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1) input_table['title'] = dlg:add_text_input( - openSub.movie.title or "", 2, 2, 2, 1) - dlg:add_button(lang["int_search_name"], - searchIMBD, 4, 2, 1, 1) - dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1) + openSub.movie.title or "", 2, 2, 2, 1) + dlg:add_button(lang["int_search_name"], + searchIMBD, 4, 2, 1, 1) + dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1) input_table['seasonNumber'] = dlg:add_text_input( - openSub.movie.seasonNumber or "", 2, 3, 2, 1) + openSub.movie.seasonNumber or "", 2, 3, 2, 1) dlg:add_label(lang["int_episode"]..':', 1, 4, 1, 1) input_table['episodeNumber'] = dlg:add_text_input( - openSub.movie.episodeNumber or "", 2, 4, 2, 1) - input_table['mainlist'] = dlg:add_list(1, 5, 4, 1) + openSub.movie.episodeNumber or "", 2, 4, 2, 1) + input_table['movieDuration'] = dlg:add_label(movie_duration_text()) + input_table['mainlist'] = dlg:add_list( 1, 6, 4, 1) input_table['message'] = nil - input_table['message'] = dlg:add_label(' ', 1, 6, 4, 1) + input_table['message'] = dlg:add_label(' ', 1, 7, 4, 1) dlg:add_button( - lang["int_show_help"], show_help, 1, 7, 1, 1) + lang["int_show_help"], show_help, 1, 8, 1, 1) dlg:add_button( - ' '..lang["int_show_conf"]..' ', show_conf, 2, 7, 1, 1) + ' '..lang["int_show_conf"]..' ', show_conf, 2, 8, 1, 1) dlg:add_button( - lang["int_dowload_sel"], download_subtitles, 3, 7, 1, 1) + lang["int_dowload_sel"], download_subtitles, 3, 8, 1, 1) dlg:add_button( - lang["int_close"], close, 4, 7, 1, 1) - + lang["int_close"], deactivate, 4, 8, 1, 1) + assoc_select_conf( 'language', 'language', - openSub.conf.languages, - 2, + openSub.conf.languages, + 2, lang["int_all"]) - + display_subtitles() end function set_interface_main() -- Update movie title and co. if video input change if not type(input_table['title']) == 'userdata' then return false end - + openSub.getFileInfo() openSub.getMovieInfo() - + input_table['title']:set_text( openSub.movie.title or "") input_table['episodeNumber']:set_text( @@ -448,10 +463,10 @@ function interface_config() input_table['intLangBut'] = dlg:add_button( lang["int_search_transl"], get_available_translations, 2, 1, 1, 1) - input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1) + input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1) dlg:add_label( lang["int_default_lang"]..':', 1, 2, 2, 1) - input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1) + input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1) dlg:add_label( lang["int_dowload_behav"]..':', 1, 3, 2, 1) input_table['downloadBehaviour'] = dlg:add_dropdown(3, 3, 1, 1) @@ -461,7 +476,7 @@ function interface_config() dlg:add_label( lang["int_remove_tag"]..':', 1, 5, 0, 1) input_table['removeTag'] = dlg:add_dropdown(3, 5, 1, 1) - + if openSub.conf.dirPath then if openSub.conf.os == "win" then dlg:add_label( @@ -476,31 +491,31 @@ function interface_config() dlg :add_label( lang["int_vlsub_work_dir"], 1, 6, 2, 1) end - + input_table['dir_path'] = dlg:add_text_input( openSub.conf.dirPath, 2, 6, 2, 1) - + dlg:add_label( lang["int_os_username"]..':', 1, 7, 0, 1) input_table['os_username'] = dlg:add_text_input( - type(openSub.option.os_username) == "string" + type(openSub.option.os_username) == "string" and openSub.option.os_username or "", 2, 7, 2, 1) dlg:add_label( lang["int_os_password"]..':', 1, 8, 0, 1) input_table['os_password'] = dlg:add_text_input( - type(openSub.option.os_password) == "string" + type(openSub.option.os_password) == "string" and openSub.option.os_password or "", 2, 8, 2, 1) - + input_table['message'] = nil input_table['message'] = dlg:add_label(' ', 1, 9, 3, 1) - + dlg:add_button( lang["int_cancel"], show_main, 2, 10, 1, 1) dlg:add_button( lang["int_save"], apply_config, 3, 10, 1, 1) - + input_table['langExt']:add_value( lang["int_bool_"..tostring(openSub.option.langExt)], 1) input_table['langExt']:add_value( @@ -509,7 +524,7 @@ function interface_config() lang["int_bool_"..tostring(openSub.option.removeTag)], 1) input_table['removeTag']:add_value( lang["int_bool_"..tostring(not openSub.option.removeTag)], 2) - + assoc_select_conf( 'intLang', 'intLang', @@ -530,7 +545,7 @@ end function interface_help() local help_html = lang["int_help_mess"] - + input_table['help'] = dlg:add_html( help_html, 1, 1, 4, 1) dlg:add_label( @@ -541,7 +556,7 @@ end function interface_no_support() local no_support_html = lang["int_no_support_mess"] - + input_table['no_support'] = dlg:add_html( no_support_html, 1, 1, 4, 1) dlg:add_label( @@ -565,8 +580,8 @@ function trigger_menu(dlg_id) openSub.conf.useragent..': '..lang["int_help"]) interface_help() end - collectgarbage() --~ !important -end + collectgarbage() --~ !important +end function show_main() trigger_menu(1) @@ -583,15 +598,15 @@ end function close_dlg() vlc.msg.dbg("[VLSub] Closing dialog") - if dlg ~= nil then + if dlg ~= nil then --~ dlg:delete() -- Throw an error - dlg:hide() + dlg:hide() end - + dlg = nil input_table = nil input_table = {} - collectgarbage() --~ !important + collectgarbage() --~ !important end --[[ Drop down / config association]]-- @@ -609,18 +624,18 @@ function assoc_select_conf(select_id, option, conf, ind, default) end function set_default_option(select_id) --- Put the selected option of a list in first place of the associated table +-- Put the selected option of a list in first place of the associated table local opt = select_conf[select_id].opt local cfg = select_conf[select_id].cf local ind = select_conf[select_id].ind if openSub.option[opt] then - table.sort(cfg, function(a, b) + table.sort(cfg, function(a, b) if a[1] == openSub.option[opt] then return true elseif b[1] == openSub.option[opt] then return false else - return a[ind] < b[ind] + return a[ind] < b[ind] end end) end @@ -633,11 +648,11 @@ function display_select(select_id) local option = openSub.option[opt] local default = select_conf[select_id].dflt local default_isset = false - - if not default then + + if not default then default_isset = true end - + for k, l in ipairs(conf) do if default_isset then input_table[select_id]:add_value(l[2], k) @@ -657,19 +672,19 @@ end --[[ Config & interface localization]]-- function check_config() - -- Make a copy of english translation to use it as default + -- Make a copy of english translation to use it as default -- in case some element aren't translated in other translations eng_translation = {} for k, v in pairs(openSub.option.translation) do eng_translation[k] = v end - + -- Get available translation full name from code trsl_names = {} for i, lg in ipairs(languages) do trsl_names[lg[1]] = lg[2] end - + if is_window_path(vlc.config.datadir()) then openSub.conf.os = "win" slash = "\\" @@ -677,17 +692,17 @@ function check_config() openSub.conf.os = "lin" slash = "/" end - + local path_generic = {"lua", "extensions", "userdata", "vlsub"} local dirPath = slash..table.concat(path_generic, slash) local filePath = slash.."vlsub_conf.xml" local config_saved = false sub_dir = slash.."vlsub_subtitles" - + -- Check if config file path is stored in vlc config local other_dirs = {} - - for path in + + for path in vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do if path:match(".*"..sub_dir.."$") then openSub.conf.dirPath = path:gsub( @@ -696,10 +711,10 @@ function check_config() end table.insert(other_dirs, path) end - + -- if not stored in vlc config - -- try to find a suitable config file path - + -- try to find a suitable config file path + if openSub.conf.dirPath then if not is_dir(openSub.conf.dirPath) and (openSub.conf.os == "lin" or @@ -709,7 +724,7 @@ function check_config() else local userdatadir = vlc.config.userdatadir() local datadir = vlc.config.datadir() - + -- check if the config already exist if file_exist(userdatadir..dirPath..filePath) then -- in vlc.config.userdatadir() @@ -723,15 +738,15 @@ function check_config() -- if not found determine an accessible path local extension_path = slash..path_generic[1] ..slash..path_generic[2] - + -- use the same folder as the extension if accessible - if is_dir(userdatadir..extension_path) + if is_dir(userdatadir..extension_path) and file_touch(userdatadir..dirPath..filePath) then openSub.conf.dirPath = userdatadir..dirPath elseif file_touch(datadir..dirPath..filePath) then openSub.conf.dirPath = datadir..dirPath end - + -- try to create working dir in user folder if not openSub.conf.dirPath and is_dir(userdatadir) then @@ -743,8 +758,8 @@ function check_config() openSub.conf.dirPath = userdatadir..dirPath end end - - -- try to create working dir in vlc folder + + -- try to create working dir in vlc folder if not openSub.conf.dirPath and is_dir(datadir) then if not is_dir(datadir..dirPath) then @@ -756,15 +771,15 @@ function check_config() end end end - + if openSub.conf.dirPath then vlc.msg.dbg("[VLSub] Working directory: " .. (openSub.conf.dirPath or "not found")) - - openSub.conf.filePath = openSub.conf.dirPath..filePath + + openSub.conf.filePath = openSub.conf.dirPath..filePath openSub.conf.localePath = openSub.conf.dirPath..slash.."locale" - - if config_saved + + if config_saved and file_exist(openSub.conf.filePath) then vlc.msg.dbg( "[VLSub] Loading config file: "..openSub.conf.filePath) @@ -777,20 +792,20 @@ function check_config() vlc.msg.dbg("[VLSub] Unable to save config") end end - - -- Check presence of a translation file + + -- Check presence of a translation file -- in "%vlsub_directory%/locale" -- Add translation files to available translation list local file_list = list_dir(openSub.conf.localePath) local translations_avail = openSub.conf.translations_avail - + if file_list then for i, file_name in ipairs(file_list) do local lg = string.gsub( file_name, "^(%w%w%w).xml$", "%1") - if lg + if lg and not openSub.option.translations_avail[lg] then table.insert(translations_avail, { lg, @@ -799,9 +814,9 @@ function check_config() end end end - + -- Load selected translation from file - if openSub.option.intLang ~= "eng" + if openSub.option.intLang ~= "eng" and not openSub.conf.translated then local transl_file_path = openSub.conf.localePath.. @@ -817,10 +832,10 @@ function check_config() vlc.msg.dbg("[VLSub] Unable to find a suitable path".. "to save config, please set it manually") end - + lang = nil lang = options.translation -- just a short cut - + if not vlc.net or not vlc.net.poll then dlg = vlc.dialog( openSub.conf.useragent..': '..lang["mess_error"]) @@ -828,16 +843,16 @@ function check_config() dlg:show() return false end - + SetDownloadBehaviours() if not openSub.conf.dirPath then setError(lang["mess_err_conf_access"]) end - - -- Set table list of available translations from assoc. array + + -- Set table list of available translations from assoc. array -- so it is sortable - - for k, l in pairs(openSub.option.translations_avail) do + + for k, l in pairs(openSub.option.translations_avail) do if k == openSub.option.int_research then table.insert(openSub.conf.translations_avail, 1, {k, l}) else @@ -856,7 +871,7 @@ function load_config() tmpFile:flush() tmpFile:close() local option = parse_xml(resp) - + for key, value in pairs(option) do if type(value) == "table" then if key == "translation" then @@ -887,8 +902,8 @@ function load_transl(path) tmpFile:flush() tmpFile:close() openSub.option.translation = nil - - openSub.option.translation = parse_xml(resp) + + openSub.option.translation = parse_xml(resp) collectgarbage() end @@ -902,9 +917,9 @@ function apply_translation() end function getenv_lang() --- Retrieve the user OS language +-- Retrieve the user OS language local os_lang = os.getenv("LANG") - + if os_lang then -- unix, mac os_lang = string.sub(os_lang, 0, 2) if type(lang_os_to_iso[os_lang]) then @@ -918,7 +933,7 @@ function getenv_lang() if v[2] == lang_w then openSub.option.language = v[1] end - end + end end end @@ -928,8 +943,8 @@ function apply_config() local sel_val local opt local sel_cf - - if lg_sel and lg_sel ~= 1 + + if lg_sel and lg_sel ~= 1 and openSub.conf.translations_avail[lg_sel] then local lg = openSub.conf.translations_avail[lg_sel][1] if not set_translation(lg) then @@ -938,48 +953,48 @@ function apply_config() end SetDownloadBehaviours() end - + for select_id, v in pairs(select_conf) do - if input_table[select_id] + if input_table[select_id] and select_conf[select_id] then sel_val = input_table[select_id]:get_value() sel_cf = select_conf[select_id] opt = sel_cf.opt - + if sel_val == 0 then openSub.option[opt] = nil else openSub.option[opt] = sel_cf.cf[sel_val][1] end - + set_default_option(select_id) end end - - + + openSub.option.os_username = input_table['os_username']:get_text() openSub.option.os_password = input_table['os_password']:get_text() - + if input_table["langExt"]:get_value() == 2 then openSub.option.langExt = not openSub.option.langExt end - + if input_table["removeTag"]:get_value() == 2 then openSub.option.removeTag = not openSub.option.removeTag end - + -- Set a custom working directory local dir_path = input_table['dir_path']:get_text() local dir_path_err = false if trim(dir_path) == "" then dir_path = nil end - + if dir_path ~= openSub.conf.dirPath then - if openSub.conf.os == "lin" - or is_win_safe(dir_path) + if openSub.conf.os == "lin" + or is_win_safe(dir_path) or not dir_path then local other_dirs = {} - - for path in + + for path in vlc.config.get( "sub-autodetect-path"):gmatch("[^,]+" ) do @@ -990,13 +1005,13 @@ function apply_config() end openSub.conf.dirPath = dir_path if dir_path then - table.insert(other_dirs, + table.insert(other_dirs, string.gsub(dir_path, "^(.-)[\\/]?$", "%1")..sub_dir) - + if not is_dir(dir_path) then mkdir_p(dir_path) end - + openSub.conf.filePath = openSub.conf.dirPath.. slash.."vlsub_conf.xml" openSub.conf.localePath = openSub.conf.dirPath.. @@ -1020,7 +1035,7 @@ function apply_config() "") end end - + if openSub.conf.dirPath and not dir_path_err then local config_saved = save_config() @@ -1034,13 +1049,13 @@ function apply_config() end function save_config() --- Dump local config into config file +-- Dump local config into config file if openSub.conf.dirPath and openSub.conf.filePath then vlc.msg.dbg( "[VLSub] Saving config file: ".. openSub.conf.filePath) - + if file_touch(openSub.conf.filePath) then local tmpFile = assert( io.open(openSub.conf.filePath, "wb")) @@ -1063,8 +1078,8 @@ function save_config() end function SetDownloadBehaviours() - openSub.conf.downloadBehaviours = nil - openSub.conf.downloadBehaviours = { + openSub.conf.downloadBehaviours = nil + openSub.conf.downloadBehaviours = { {'save', lang["int_dowload_save"]}, {'manual', lang["int_dowload_manual"]} } @@ -1072,17 +1087,17 @@ end function get_available_translations() -- Get all available translation files from the internet --- (drop previous direct download from github repo +-- (drop previous direct download from github repo -- causing error with github https CA certficate on OS X an XP) -- https://github.com/exebetche/vlsub/tree/master/locale - + local translations_url = "http://addons.videolan.org/CONTENT/".. "content-files/148752-vlsub_translations.xml" - - if input_table['intLangBut']:get_text() == lang["int_search_transl"] + + if input_table['intLangBut']:get_text() == lang["int_search_transl"] then openSub.actionLabel = lang["int_searching_transl"] - + local translations_content = get(translations_url) if not translations_content then collectgarbage() @@ -1091,9 +1106,9 @@ function get_available_translations() local translations_avail = openSub.option.translations_avail all_trsl = parse_xml(translations_content) local lg, trsl - + for lg, trsl in pairs(all_trsl) do - if lg ~= options.intLang[1] + if lg ~= options.intLang[1] and not translations_avail[lg] then translations_avail[lg] = trsl_names[lg] or "" table.insert(openSub.conf.translations_avail, { @@ -1105,7 +1120,7 @@ function get_available_translations() #openSub.conf.translations_avail) end end - + setMessage(success_tag(lang["mess_complete"])) collectgarbage() end @@ -1115,14 +1130,14 @@ end function set_translation(lg) openSub.option.translation = nil openSub.option.translation = {} - + if lg == 'eng' then for k, v in pairs(eng_translation) do openSub.option.translation[k] = v end else -- If translation file exists in /locale directory load it - if openSub.conf.localePath + if openSub.conf.localePath and file_exist(openSub.conf.localePath.. slash..lg..".xml") then local transl_file_path = openSub.conf.localePath.. @@ -1147,12 +1162,12 @@ function set_translation(lg) all_trsl = nil end end - + lang = nil lang = openSub.option.translation collectgarbage() return true -end +end --[[ Core ]]-- @@ -1198,24 +1213,24 @@ openSub = { local params = openSub.methods[methodName].params() local reqTable = openSub.getMethodBase(methodName, params) local request = ""..dump_xml(reqTable) - local host, path = parse_url(openSub.conf.url) + local host, path = parse_url(openSub.conf.url) local header = { - "POST "..path.." HTTP/1.1", - "Host: "..host, - "User-Agent: "..openSub.conf.userAgentHTTP, - "Content-Type: text/xml", + "POST "..path.." HTTP/1.1", + "Host: "..host, + "User-Agent: "..openSub.conf.userAgentHTTP, + "Content-Type: text/xml", "Content-Length: "..string.len(request), "", "" } request = table.concat(header, "\r\n")..request - + local response local status, responseStr = http_req(host, 80, request) - - if status == 200 then + + if status == 200 then response = parse_xmlrpc(responseStr) - + if response then if response.status == "200 OK" then return openSub.methods[methodName] @@ -1241,22 +1256,22 @@ openSub = { openSub.request(methodName) end return false - elseif status == 503 then + elseif status == 503 then setError("Server overloaded, please retry later") return false end - + end, getMethodBase = function(methodName, param) if openSub.methods[methodName].methodName then methodName = openSub.methods[methodName].methodName end - + local request = { methodCall={ methodName=methodName, params={ param=param }}} - + return request end, methods = { @@ -1267,7 +1282,7 @@ openSub = { { value={ string=openSub.option.os_username } }, { value={ string=openSub.option.os_password } }, { value={ string=openSub.movie.sublanguageid } }, - { value={ string=openSub.conf.useragent } } + { value={ string=openSub.conf.useragent } } } end, callback = function(resp) @@ -1280,7 +1295,7 @@ openSub = { params = function() openSub.actionLabel = lang["action_logout"] return { - { value={ string=openSub.session.token } } + { value={ string=openSub.session.token } } } end, callback = function() @@ -1291,7 +1306,7 @@ openSub = { params = function() openSub.actionLabel = lang["action_noop"] return { - { value={ string=openSub.session.token } } + { value={ string=openSub.session.token } } } end, callback = function(resp) @@ -1304,7 +1319,7 @@ openSub = { openSub.actionLabel = lang["action_search"] setMessage(openSub.actionLabel..": ".. progressBarContent(0)) - + return { { value={ string=openSub.session.token } }, { value={ @@ -1313,13 +1328,13 @@ openSub = { value={ struct={ member={ - { name="sublanguageid", value={ - string=openSub.movie.sublanguageid } + { name="sublanguageid", value={ + string=openSub.movie.sublanguageid } }, - { name="moviehash", value={ + { name="moviehash", value={ string=openSub.file.hash } }, - { name="moviebytesize", value={ - double=openSub.file.bytesize } } + { name="moviebytesize", value={ + double=openSub.file.bytesize } } }}}}}}} } end, @@ -1333,24 +1348,24 @@ openSub = { openSub.actionLabel = lang["action_search"] setMessage(openSub.actionLabel..": ".. progressBarContent(0)) - + local member = { - { name="sublanguageid", value={ + { name="sublanguageid", value={ string=openSub.movie.sublanguageid } }, - { name="query", value={ + { name="query", value={ string=openSub.movie.title } } } - - + + if openSub.movie.seasonNumber ~= nil then - table.insert(member, { name="season", value={ + table.insert(member, { name="season", value={ string=openSub.movie.seasonNumber } }) - end - + end + if openSub.movie.episodeNumber ~= nil then - table.insert(member, { name="episode", value={ + table.insert(member, { name="episode", value={ string=openSub.movie.episodeNumber } }) - end - + end + return { { value={ string=openSub.session.token } }, { value={ @@ -1372,13 +1387,13 @@ openSub = { openSub.actionLabel = lang["action_search"] setMessage(openSub.actionLabel..": ".. progressBarContent(0)) - + local member = { - { name="sublanguageid", value={ + { name="sublanguageid", value={ string=openSub.movie.sublanguageid } }, - { name="tag", value={ + { name="tag", value={ string=openSub.file.completeName } } } - + return { { value={ string=openSub.session.token } }, { value={ @@ -1415,12 +1430,12 @@ openSub = { file.uri = item:uri() file.protocol = parsed_uri["protocol"] file.path = parsed_uri["path"] - + -- Corrections - + -- For windows file.path = string.match(file.path, "^/(%a:/.+)$") or file.path - + -- For file in archive local archive_path, name_in_archive = string.match( file.path, '^([^!]+)!/([^!/]*)$') @@ -1442,26 +1457,26 @@ openSub = { file.dir, file.completeName = string.match( file.path, '^(.+/)([^/]*)$') - + local file_stat = vlc.net.stat(file.path) - if file_stat + if file_stat then file.stat = file_stat end - + file.is_archive = false end - + file.name, file.ext = string.match( file.completeName, '^([^/]-)%.?([^%.]*)$') - + if file.ext == "part" then file.name, file.ext = string.match( file.name, '^([^/]+)%.([^%.]+)$') end - + file.hasInput = true; file.cleanName = string.gsub( file.name, @@ -1476,9 +1491,9 @@ openSub = { openSub.movie.title = "" openSub.movie.seasonNumber = "" openSub.movie.episodeNumber = "" - return false + return false end - + local showName, seasonNumber, episodeNumber = string.match( openSub.file.cleanName, "(.+)[sS](%d?%d)[eE](%d%d).*") @@ -1488,7 +1503,7 @@ openSub = { openSub.file.cleanName, "(.-)(%d?%d)[xX](%d%d).*") end - + if showName then openSub.movie.title = showName openSub.movie.seasonNumber = seasonNumber @@ -1505,37 +1520,37 @@ openSub = { openSub.actionLabel = lang["action_hash"] setMessage(openSub.actionLabel..": ".. progressBarContent(0)) - + local item = openSub.getInputItem() - + if not item then setError(lang["mess_no_input"]) return false end - + openSub.getFileInfo() - + if not openSub.file.path then setError(lang["mess_not_found"]) return false end - + local data_start = "" local data_end = "" local size local chunk_size = 65536 - + -- Get data for hash calculation if openSub.file.is_archive then vlc.msg.dbg("[VLSub] Read hash data from stream") - + local file = vlc.stream(openSub.file.uri) local dataTmp1 = "" local dataTmp2 = "" size = chunk_size - + data_start = file:read(chunk_size) - + while data_end do size = size + string.len(data_end) dataTmp1 = dataTmp2 @@ -1544,31 +1559,31 @@ openSub = { collectgarbage() end data_end = string.sub((dataTmp1..dataTmp2), -chunk_size) - elseif not file_exist(openSub.file.path) + elseif not file_exist(openSub.file.path) and openSub.file.stat then vlc.msg.dbg("[VLSub] Read hash data from stream") - + local file = vlc.stream(openSub.file.uri) - + if not file then vlc.msg.dbg("[VLSub] No stream") return false end - + size = openSub.file.stat.size local decal = size%chunk_size - + data_start = file:read(chunk_size) - - -- "Seek" to the end + + -- "Seek" to the end file:read(decal) - + for i = 1, math.floor(((size-decal)/chunk_size))-2 do file:read(chunk_size) end - + data_end = file:read(chunk_size) - + file = nil else vlc.msg.dbg("[VLSub] Read hash data from file") @@ -1577,13 +1592,13 @@ openSub = { vlc.msg.dbg("[VLSub] No stream") return false end - + data_start = file:read(chunk_size) size = file:seek("end", -chunk_size) + chunk_size data_end = file:read(chunk_size) file = nil end - + -- Hash calculation local lo = size local hi = 0 @@ -1591,24 +1606,24 @@ openSub = { local hash_data = data_start..data_end local max_size = 4294967296 local overflow - + for i = 1, #hash_data, 8 do a,b,c,d,e,f,g,h = hash_data:byte(i,i+7) lo = lo + a + b*256 + c*65536 + d*16777216 hi = hi + e + f*256 + g*65536 + h*16777216 - + if lo > max_size then overflow = math.floor(lo/max_size) lo = lo-(overflow*max_size) hi = hi+overflow end - + if hi > max_size then overflow = math.floor(hi/max_size) hi = hi-(overflow*max_size) end end - + openSub.file.bytesize = size openSub.file.hash = string.format("%08x%08x", hi,lo) vlc.msg.dbg("[VLSub] Video hash: "..openSub.file.hash) @@ -1617,7 +1632,7 @@ openSub = { return true end, checkSession = function() - + if openSub.session.token == "" then openSub.request("LogIn") else @@ -1633,9 +1648,9 @@ function searchHash() else openSub.movie.sublanguageid = openSub.conf.languages[sel][1] end - + openSub.getMovieHash() - + if openSub.file.hash then openSub.checkSession() openSub.request("SearchSubtitlesByHash") @@ -1656,7 +1671,7 @@ function searchIMBD() else openSub.movie.sublanguageid = openSub.conf.languages[sel][1] end - + if openSub.movie.title ~= "" then openSub.checkSession() openSub.request("SearchSubtitles") @@ -1664,20 +1679,58 @@ function searchIMBD() end end +function last_subtitle_time_text(subtitle, lang_global) + lang_global = lang_global or lang + local last_subtitle_time = "?" + if subtitle.SubLastTS + and string.find(subtitle.SubLastTS, ":") then + last_subtitle_time = subtitle.SubLastTS + end + return string.format("[%s %s]", lang_global["int_last_sub"], last_subtitle_time) +end +export.last_subtitle_time_text = last_subtitle_time_text + +function rank_subtitles(unordered_subs, movie_duration) + marked_subs = {} + for _, item in ipairs(unordered_subs) do + local last_subtitle_time = date_string_to_time(item.SubLastTS) + local distance = -1 + if last_subtitle_time ~= 0 then + distance = math.abs(movie_duration - last_subtitle_time) + end + table.insert(marked_subs, {rank=distance, sub=item}) + end + return marked_subs +end + +function order_by_ascending_distance_between_last_sub_time_and_movie_duration(unordered_subs, movie_duration) + marked_subs = rank_subtitles(unordered_subs, movie_duration) + table.sort(marked_subs, function(a, b) return b.rank == -1 or a.rank < b.rank and a.rank ~= -1 end) + ordered_subs = {} + for _, marked_sub in ipairs(marked_subs) do + table.insert(ordered_subs, marked_sub.sub) + end + return ordered_subs +end +export.order_by_ascending_distance_between_last_sub_time_and_movie_duration = order_by_ascending_distance_between_last_sub_time_and_movie_duration + function display_subtitles() + input_table['movieDuration']:set_text(movie_duration_text()) local mainlist = input_table["mainlist"] mainlist:clear() - - if openSub.itemStore == "0" then + + if openSub.itemStore == "0" then mainlist:add_value(lang["mess_no_res"], 1) setMessage(""..lang["mess_complete"]..": ".. lang["mess_no_res"]) - elseif openSub.itemStore then - for i, item in ipairs(openSub.itemStore) do + elseif openSub.itemStore then + local ordered_subs = order_by_ascending_distance_between_last_sub_time_and_movie_duration(openSub.itemStore, vlc.input.item():duration()) + for i, item in ipairs(ordered_subs) do mainlist:add_value( - (item.SubFileName or "???").. - " ["..(item.SubLanguageID or "?").."]".. - " ("..(item.SubSumCD or "?").." CD)", i) + last_subtitle_time_text(item).. + "["..(item.SubLanguageID or "?").."]".. + "("..(item.SubSumCD or "?").." CD)".. + " "..(item.SubFileName or "???"), i) end setMessage(""..lang["mess_complete"]..": ".. #(openSub.itemStore).." "..lang["mess_res"]) @@ -1686,7 +1739,7 @@ end function get_first_sel(list) local selection = list:get_selection() - for index, name in pairs(selection) do + for index, name in pairs(selection) do return index end return 0 @@ -1694,17 +1747,17 @@ end function download_subtitles() local index = get_first_sel(input_table["mainlist"]) - + if index == 0 then setMessage(lang["mess_no_selection"]) return false end - - openSub.actionLabel = lang["mess_downloading"] - + + openSub.actionLabel = lang["mess_downloading"] + local item = openSub.itemStore[index] - - if openSub.option.downloadBehaviour == 'manual' + + if openSub.option.downloadBehaviour == 'manual' or not openSub.file.hasInput then local link = "" link = link..""..lang["mess_dowload_link"]..":" @@ -1712,27 +1765,27 @@ function download_subtitles() link = link.."  " link = link..item.MovieReleaseName.."" - + setMessage(link) return false end - + local message = "" local subfileName = openSub.file.name or "" - + if openSub.option.langExt then subfileName = subfileName.."."..item.SubLanguageID end - + subfileName = subfileName.."."..item.SubFormat local tmp_dir local file_target_access = true - + if is_dir(openSub.file.dir) then tmp_dir = openSub.file.dir elseif openSub.conf.dirPath then tmp_dir = openSub.conf.dirPath - + message = "
"..error_tag(lang["mess_save_fail"].."  ".. "".. lang["mess_click_link"].."") @@ -1742,18 +1795,18 @@ function download_subtitles() lang["mess_click_link"].."") return false end - + local tmpFileURI, tmpFileName = dump_zip( - item.ZipDownloadLink, - tmp_dir, + item.ZipDownloadLink, + tmp_dir, item.SubFileName) - + vlc.msg.dbg("[VLsub] tmpFileName: "..tmpFileName) - + -- Determine if the path to the video file is accessible for writing - + local target = openSub.file.dir..subfileName - + if not file_touch(target) then if openSub.conf.dirPath then target = openSub.conf.dirPath..slash..subfileName @@ -1769,37 +1822,37 @@ function download_subtitles() return false end end - + vlc.msg.dbg("[VLsub] Subtitles files: "..target) - - -- Unzipped data into file target - + + -- Unzipped data into file target + local stream = vlc.stream(tmpFileURI) local data = "" local subfile = io.open(target, "wb") - + while data do subfile:write(data) data = stream:read(65536) end - + subfile:flush() subfile:close() - + stream = nil collectgarbage() - + if not os.remove(tmpFileName) then vlc.msg.err("[VLsub] Unable to remove temp: "..tmpFileName) end - + -- load subtitles - if add_sub(target) then + if add_sub(target) then message = success_tag(lang["mess_loaded"]) .. message else message = error_tag(lang["mess_not_load"]) .. message end - + setMessage(message) end @@ -1807,18 +1860,18 @@ function dump_zip(url, dir, subfileName) -- Dump zipped data in a temporary file setMessage(openSub.actionLabel..": "..progressBarContent(0)) local resp = get(url) - - if not resp then + + if not resp then setError(lang["mess_no_response"]) - return false + return false end - + local tmpFileName = dir..subfileName..".gz" if not file_touch(tmpFileName) then return false end local tmpFile = assert(io.open(tmpFileName, "wb")) - + tmpFile:write(resp) tmpFile:flush() tmpFile:close() @@ -1877,8 +1930,8 @@ end function get(url) local host, path = parse_url(url) local header = { - "GET "..path.." HTTP/1.1", - "Host: "..host, + "GET "..path.." HTTP/1.1", + "Host: "..host, "User-Agent: "..openSub.conf.userAgentHTTP, "", "" @@ -1886,8 +1939,8 @@ function get(url) local request = table.concat(header, "\r\n") local status, response = http_req(host, 80, request) - - if status == 200 then + + if status == 200 then return response else vlc.msg.err("[VLSub] HTTP "..tostring(status).." : "..response) @@ -1899,19 +1952,19 @@ function http_req(host, port, request) local fd = vlc.net.connect_tcp(host, port) if not fd then return false end local pollfds = {} - + pollfds[fd] = vlc.net.POLLIN vlc.net.send(fd, request) vlc.net.poll(pollfds) - + local chunk = vlc.net.recv(fd, 2048) local response = "" local headerStr, header, body local contentLength, status local pct = 0 - + --~ vlc.msg.err(headerStr) - + while chunk do response = response..chunk --~ vlc.msg.err("response", response) @@ -1940,14 +1993,14 @@ function http_req(host, port, request) end vlc.net.close(fd) - - if status == 301 + + if status == 301 and header["Location"] then local host, path = parse_url(trim(header["Location"])) request = request :gsub("^([^%s]+ )([^%s]+)", "%1"..path) :gsub("(Host: )([^\n]*)", "%1"..host) - + return http_req(host, port, request) end @@ -1956,23 +2009,23 @@ end function parse_header(data) local header = {} - + for name, s, val in string.gmatch( data, "([^%s:]+)(:?)%s([^\n]+)\r?\n") do - if s == "" then + if s == "" then header['statuscode'] = tonumber(string.sub(val, 1 , 3)) - else + else header[name] = val end end return header -end +end function parse_url(url) local url_parsed = vlc.net.url_parse(url) - return url_parsed["host"], + return url_parsed["host"], url_parsed["path"], url_parsed["option"] end @@ -1989,7 +2042,7 @@ function parse_xml(data) local resolve_xml = vlc.strings.resolve_xml_special_chars for op, tag, p, empty, val in string.gmatch( - data, + data, "[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>".. "[%s\r\n\t]*([^<]*)[%s\r\n\t]*" ) do @@ -2031,7 +2084,7 @@ function parse_xml(data) end end end - + collectgarbage() return tree end @@ -2042,7 +2095,7 @@ function parse_xmlrpc(xmlText) local tmp, name = nil, nil table.insert(stack, tree) local FromXmlString = vlc.strings.resolve_xml_special_chars - + local data_handle = { int = function(v) return tonumber(v) end, i4 = function(v) return tonumber(v) end, @@ -2051,11 +2104,11 @@ function parse_xmlrpc(xmlText) base64 = function(v) return tostring(v) end, -- FIXME ["string"] = function(v) return FromXmlString(v) end } - - for c, label, empty, value + + for c, label, empty, value in xmlText:gmatch("<(%/?)([%w_:]+)(%/?)>([^<]*)") do - - if c == "" + + if c == "" then -- start tag if label == "struct" or label == "array" then @@ -2074,27 +2127,27 @@ function parse_xmlrpc(xmlText) if name then stack[#stack][name] = data_handle[label](value) else - table.insert(stack[#stack], + table.insert(stack[#stack], data_handle[label](value)) end name = nil end if empty == "/" -- empty tag - and #stack>0 + and #stack>0 and (label == "struct" or label == "array") then table.remove(stack) end else -- end tag - if #stack>0 + if #stack>0 and (label == "struct" or label == "array")then table.remove(stack) end end end - + return tree[1] end @@ -2103,21 +2156,21 @@ function dump_xml(data) local stack = {} local dump = "" local convert_xml = vlc.strings.convert_xml_special_chars - + local function parse(data, stack) local data_index = {} local k local v local i local tb - + for k,v in pairs(data) do table.insert(data_index, {k, v}) table.sort(data_index, function(a, b) - return a[1] < b[1] + return a[1] < b[1] end) end - + for i,tb in pairs(data_index) do k = tb[1] v = tb[2] @@ -2125,7 +2178,7 @@ function dump_xml(data) dump = dump.."\r\n"..string.rep( " ", level).. - "<"..k..">" + "<"..k..">" table.insert(stack, k) level = level + 1 elseif type(k)=="number" and k ~= 1 then @@ -2134,7 +2187,7 @@ function dump_xml(data) level-1).. "<"..stack[level]..">" end - + if type(v)=="table" then parse(v, stack) elseif type(v)=="string" then @@ -2144,7 +2197,7 @@ function dump_xml(data) else dump = dump..tostring(v) end - + if type(k)=="string" then if type(v)=="table" then dump = dump.."\r\n"..string.rep( @@ -2156,7 +2209,7 @@ function dump_xml(data) end table.remove(stack) level = level - 1 - + elseif type(k)=="number" and k ~= #data then if type(v)=="table" then dump = dump.."\r\n"..string.rep( @@ -2182,9 +2235,9 @@ function make_uri(str) local encode_uri = vlc.strings.encode_uri_component local encodedPath = "" for w in string.gmatch(str, "/([^/]+)") do - encodedPath = encodedPath.."/"..encode_uri(w) + encodedPath = encodedPath.."/"..encode_uri(w) end - + if windowdrive then return "file:///"..windowdrive..encodedPath else @@ -2193,38 +2246,38 @@ function make_uri(str) end function file_touch(name) -- test write ability - if not name or trim(name) == "" + if not name or trim(name) == "" then return false end - + local f=io.open(name ,"w") - if f~=nil then - io.close(f) - return true - else - return false + if f~=nil then + io.close(f) + return true + else + return false end end function file_exist(name) -- test readability - if not name or trim(name) == "" + if not name or trim(name) == "" then return false end local f=io.open(name ,"r") - if f~=nil then - io.close(f) - return true - else - return false + if f~=nil then + io.close(f) + return true + else + return false end end function is_dir(path) - if not path or trim(path) == "" + if not path or trim(path) == "" then return false end -- Remove slash at the end or it won't work on Windows path = string.gsub(path, "^(.-)[\\/]?$", "%1") local f, _, code = io.open(path, "rb") - - if f then + + if f then _, _, code = f:read("*a") f:close() if code == 21 then @@ -2233,23 +2286,23 @@ function is_dir(path) elseif code == 13 then return true end - + return false end function list_dir(path) - if not path or trim(path) == "" + if not path or trim(path) == "" then return false end - local dir_list_cmd + local dir_list_cmd local list = {} if not is_dir(path) then return false end - + if openSub.conf.os == "win" then dir_list_cmd = io.popen('dir /b "'..path..'"') elseif openSub.conf.os == "lin" then dir_list_cmd = io.popen('ls -1 "'..path..'"') end - + if dir_list_cmd then for filename in dir_list_cmd:lines() do if string.match(filename, "^[^%s]+.+$") then @@ -2263,7 +2316,7 @@ function list_dir(path) end function mkdir_p(path) - if not path or trim(path) == "" + if not path or trim(path) == "" then return false end if openSub.conf.os == "win" then os.execute('mkdir "' .. path..'"') @@ -2281,12 +2334,12 @@ function is_window_path(path) end function is_win_safe(path) - if not path or trim(path) == "" + if not path or trim(path) == "" or not is_window_path(path) then return false end return string.match(path, "^%a?%:?[\\%w%p%s§¤]+$") end - + function trim(str) if not str then return "" end return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1") @@ -2295,3 +2348,18 @@ end function remove_tag(str) return string.gsub(str, "{[^}]+}", "") end + +function date_string_to_time(date_string) + if not date_string + or not string.find(date_string, ":") then + return 0 + end + local results = {} + for str in string.gmatch(date_string, "[^:]+") do + table.insert(results, tonumber(str) or 0) + end + return results[1]*60*60 + results[2]*60 + results[3] +end +export.date_string_to_time = date_string_to_time + +return export