--- A global map of subtitles with id and the description. subtitles = {} --- A global map of subtitles with id and corresponding VLC instances. subtitle_player = {} -- [ The following functions are the boilerplate of a VLC extension. ] -- --- Describes the extension. function descriptor() return { title = "SimSub", version = "0.1", author = "Christopher Gundler", url = '', shortdesc = "Show Multiple Subtitles"; description = "An extension for the VLC Media Player, which allows to view multiple subtitles simultaneously in different windows.", capabilities = { "menu", "playing-listener" } } end --- Activates the extension. function activate() -- Check if a media is already loaded if not vlc.input:is_playing() then print_error("Please open a file before trying to select subtitles!") elseif not vlc.input.item():info()["Stream 0"] or not vlc.input.item():info()["Stream 0"]["Frame rate"] then print_error("Please open a video stream to select subtitles!") else -- Fill the global map with the subtitles of the media. subtitles = get_current_subtitles() end end --- Closes the extension. function close() vlc.deactivate() end --- Deactivates the extension. function deactivate() -- Close any open player for key,value in pairs(subtitle_player) do subtitle_player[key]:close() end -- Reset the global map subtitle_player = {} end --- Returns the elements which are to be shown in the menu. -- @return A map of subtitle descriptions ordered by their id. function menu() return subtitles end --- Handles a click on the menu. -- @param sub_id The id of the menu entry (which corresponds to the subtitle id) function trigger_menu(sub_id) -- Check if the window is already open. if not subtitle_player[sub_id] then -- Creates a player for a specific subtitle if it is not already running. local handle = PlayerInstance:new(vlc.input.item():uri()) if handle then -- Start the slave paused if the master is paused, too. if not vlc_is_playing() then handle:pause() end -- Set the requested subtitle and store the handle handle:set_subtitle(sub_id) handle:synchronize() subtitle_player[sub_id] = handle else print_error("Your system does not support the presentation of multiple subtitles.") end else -- Close and reset the player. subtitle_player[sub_id]:close() subtitle_player[sub_id] = nil end end --- Handles a change of meta and is required by "playing-listener". function meta_changed() end --- Handles a change of playing state. function playing_changed() -- If the VLC is now playing ... if vlc_is_playing() then -- start playing in every instances ... for key,value in pairs(subtitle_player) do subtitle_player[key]:play() end else -- or pause every other instances. for key,value in pairs(subtitle_player) do subtitle_player[key]:pause() end end end -- [ The following functions are utility functions. ] -- --- Logs an error and show it in a messagebox for the user. -- @param message The message of the error function print_error(message) -- Log the message vlc.msg.err("[SimSalaSub] " .. message) -- Create the dialog local dialog = vlc.dialog("Error") dialog:add_label(message) dialog:add_button("OK", function () dialog:delete() vlc.deactivate(); return nil end) end --- Generate the subtitles of the current media. -- @return A map of subtitle descriptions ordered by their id function get_current_subtitles() subs = {} -- Get the current item local item = vlc.input.item() -- Iterate through the streams for k, v in pairs(item:info()) do -- Filter for subtitles and discard audio and video if(v.Type == "Subtitle") then -- Extract the id and combine it with the description subs[tonumber(string.match(k, "(%d+)"))] = v.Description end end return subs end --- Checks if the current media is played. -- @return True, if the media is currently played. function vlc_is_playing() return vlc.var.get(vlc.object.input(), "state") == 2 end -- [ Class: PlayerInstance ] -- --- An instance of an other VLC player. PlayerInstance = {} PlayerInstance.__index = PlayerInstance --- Creates a new instance of the VLC. -- @param uri The URI of the media which should be played. -- @return An instance of the class or NIL on error. function PlayerInstance:new(uri) -- Create an extern process and open a writeable stream for commands -- TODO: Support windows local tmp = io.popen("cvlc -I cli --no-one-instance --repeat " .. uri, "w") if not tmp then return nil end -- Create the instance local this = { handle = tmp } setmetatable(this, PlayerInstance) return this end --- Pauses the instance. function PlayerInstance:pause() assert(self.handle:write("pause", "\n"), "write failed") self.handle:flush() end --- Start playing the former paused instance. function PlayerInstance:play() assert(self.handle:write("play", "\n"), "write failed") self.handle:flush() end --- Synchronizes the time of the instance with the main one. function PlayerInstance:synchronize() -- Set the rough time local current_time = vlc.var.get(vlc.object.input(), "time") assert(self.handle:write("seek ", tostring(math.floor(current_time)), "\n"), "write failed") -- Move forward by single frames to synchronize exactly local fps = tonumber(vlc.input.item():info()["Stream 0"]["Frame rate"]) local time_fraction = current_time - math.floor(current_time) for i = 1, math.floor(time_fraction * fps + 0.5), 1 do assert(self.handle:write("frame\n"), "setting frame failed") end self.handle:flush() end --- Sets the subtitle track of the instance. -- @param id The ID of the subtitle. function PlayerInstance:set_subtitle(id) assert(self.handle:write("strack ", tostring(id), "\n"), "write failed") self.handle:flush() end --- Closes the instance and clean up. -- WARNING: Do not use afterwards! function PlayerInstance:close() assert(self.handle:write("shutdown", "\n"), "write failed") self.handle:flush() self.handle:close() end