dotfiles

Mahdi's dotfiles
git clone git://mahdi.pw/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

SmartCopyPaste.lua (33962B)


      1 -- Copyright (c) 2022, Eisa AlAwadhi
      2 -- License: BSD 2-Clause License
      3 -- Creator: Eisa AlAwadhi
      4 -- Project: SmartCopyPaste
      5 -- Version: 3.1
      6 
      7 local o = {
      8 ---------------------------USER CUSTOMIZATION SETTINGS---------------------------
      9 --These settings are for users to manually change some options.
     10 --Changes are recommended to be made in the script-opts directory.
     11 
     12 	-----Script Settings----
     13 	device = 'auto', --'auto' is for automatic device detection, or manually change to: 'windows' or 'mac' or 'linux'
     14 	linux_copy = 'xclip -silent -selection clipboard -in', --copy command that will be used in Linux. OR write a different command
     15 	linux_paste = 'xclip -selection clipboard -o', --paste command that will be used in Linux. OR write a different command
     16 	mac_copy = 'pbcopy', --copy command that will be used in MAC. OR write a different command
     17 	mac_paste = 'pbpaste', --paste command that will be used in MAC. OR write a different command
     18 	windows_copy = 'powershell', --'powershell' is for using windows powershell to copy. OR write the copy command, e.g: ' clip'
     19 	windows_paste = 'powershell', --'powershell' is for using windows powershell to paste. OR write the paste command
     20 	resume_offset = -0.65, --change to 0 so item resumes from the exact position, or decrease the value so that it gives you a little preview before loading the resume point
     21 	osd_messages = true, --true is for displaying osd messages when actions occur. Change to false will disable all osd messages generated from this script
     22 	time_seperator = ' 🕒 ', --Time seperator that will be shown before the saved time in osd messages
     23 	prefer_filename_over_title = 'local', --Prefers to copy filename over filetitle. Select between 'local', 'protocols', 'all', and 'none'. 'local' prefer filenames for videos that are not protocols. 'protocols' will prefer filenames for protocols only. 'all' will prefer filename over filetitle for both protocols and not protocols videos. 'none' will always use filetitle instead of filename
     24 	copy_time_method = 'all', --Option to copy time with video, 'none' for disabled, 'all' to copy time for all videos, 'protocols' for copying time only for protocols, 'specifics' to copy time only for websites defined below, 'local' to copy time for videos that are not protocols
     25 	specific_time_attributes=[[
     26 	[ ["twitter", "?t=", ""], ["twitch", "?t=", "s"], ["youtube", "&t=", "s"] ]
     27 	]], --The time attributes which will be added when copying protocols of specific websites from this list. Additional attributes can be added following the same format.
     28 	protocols_time_attribute = '&t=', --The text that will be copied before the seek time when copying a protocol video from mpv 
     29 	local_time_attribute = '&time=', --The text that will be copied before the seek time when copying a local video from mpv
     30 	pastable_time_attributes=[[
     31 	[" | time="]
     32 	]], --The time attributes that can be pasted for resume, specific_time_attributes, protocols_time_attribute, local_time_attribute are automatically added
     33 	copy_keybind=[[
     34 	["ctrl+c", "ctrl+C", "meta+c", "meta+C"]
     35 	]], --Keybind that will be used to copy
     36 	running_paste_behavior = 'playlist', --The priority of paste behavior when a video is running. select between 'playlist', 'timestamp', 'force'.
     37 	paste_keybind=[[
     38 	["ctrl+v", "ctrl+V", "meta+v", "meta+V"]
     39 	]], --Keybind that will be used to paste
     40 	copy_specific_behavior = 'path', --Copy behavior when using copy_specific_keybind. select between 'title', 'path', 'timestamp', 'path&timestamp'.
     41 	copy_specific_keybind=[[
     42 	["ctrl+alt+c", "ctrl+alt+C", "meta+alt+c", "meta+alt+C"]
     43 	]], --Keybind that will be used to copy based on the copy behavior specified
     44 	paste_specific_behavior = 'playlist', --Paste behavior when using paste_specific_keybind. select between 'playlist', 'timestamp', 'force'.
     45 	paste_specific_keybind=[[
     46 	["ctrl+alt+v", "ctrl+alt+V", "meta+alt+v", "meta+alt+V"]
     47 	]], --Keybind that will be used to paste based on the paste behavior specified
     48 	paste_protocols=[[
     49 	["https?://", "magnet:", "rtmp:"]
     50 	]], --add above (after a comma) any protocol you want paste to work with; e.g: ,'ftp://'. Or set it as "" by deleting all defined protocols to make paste works with any protocol.
     51 	paste_extensions=[[
     52 	["ac3", "a52", "eac3", "mlp", "dts", "dts-hd", "dtshd", "true-hd", "thd", "truehd", "thd+ac3", "tta", "pcm", "wav", "aiff", "aif",  "aifc", "amr", "awb", "au", "snd", "lpcm", "yuv", "y4m", "ape", "wv", "shn", "m2ts", "m2t", "mts", "mtv", "ts", "tsv", "tsa", "tts", "trp", "adts", "adt", "mpa", "m1a", "m2a", "mp1", "mp2", "mp3", "mpeg", "mpg", "mpe", "mpeg2", "m1v", "m2v", "mp2v", "mpv", "mpv2", "mod", "tod", "vob", "vro", "evob", "evo", "mpeg4", "m4v", "mp4", "mp4v", "mpg4", "m4a", "aac", "h264", "avc", "x264", "264", "hevc", "h265", "x265", "265", "flac", "oga", "ogg", "opus", "spx", "ogv", "ogm", "ogx", "mkv", "mk3d", "mka", "webm", "weba", "avi", "vfw", "divx", "3iv", "xvid", "nut", "flic", "fli", "flc", "nsv", "gxf", "mxf", "wma", "wm", "wmv", "asf", "dvr-ms", "dvr", "wtv", "dv", "hdv", "flv","f4v", "f4a", "qt", "mov", "hdmov", "rm", "rmvb", "ra", "ram", "3ga", "3ga2", "3gpp", "3gp", "3gp2", "3g2", "ay", "gbs", "gym", "hes", "kss", "nsf", "nsfe", "sap", "spc", "vgm", "vgz", "m3u", "m3u8", "pls", "cue",
     53 	"ase", "art", "bmp", "blp", "cd5", "cit", "cpt", "cr2", "cut", "dds", "dib", "djvu", "egt", "exif", "gif", "gpl", "grf", "icns", "ico", "iff", "jng", "jpeg", "jpg", "jfif", "jp2", "jps", "lbm", "max", "miff", "mng", "msp", "nitf", "ota", "pbm", "pc1", "pc2", "pc3", "pcf", "pcx", "pdn", "pgm", "PI1", "PI2", "PI3", "pict", "pct", "pnm", "pns", "ppm", "psb", "psd", "pdd", "psp", "px", "pxm", "pxr", "qfx", "raw", "rle", "sct", "sgi", "rgb", "int", "bw", "tga", "tiff", "tif", "vtf", "xbm", "xcf", "xpm", "3dv", "amf", "ai", "awg", "cgm", "cdr", "cmx", "dxf", "e2d", "egt", "eps", "fs", "gbr", "odg", "svg", "stl", "vrml", "x3d", "sxd", "v2d", "vnd", "wmf", "emf", "art", "xar", "png", "webp", "jxr", "hdp", "wdp", "cur", "ecw", "iff", "lbm", "liff", "nrrd", "pam", "pcx", "pgf", "sgi", "rgb", "rgba", "bw", "int", "inta", "sid", "ras", "sun", "tga",
     54 	"torrent"]
     55 	]], --add above (after a comma) any extension you want paste to work with; e.g: ,'pdf'. Or set it as "" by deleting all defined extension to make paste works with any extension.
     56 	paste_subtitles=[[
     57 	["aqt", "gsub", "jss", "sub", "ttxt", "pjs", "psb", "rt", "smi", "slt", "ssf", "srt", "ssa", "ass", "usf", "idx", "vtt"]
     58 	]], --add above (after a comma) any extension you want paste to attempt to add as a subtitle file, e.g.:'txt'. Or set it as "" by deleting all defined extension to make paste attempt to add any subtitle.
     59 	
     60 	-----Time Format Settings-----
     61 	--in the first parameter, you can define from the available styles: default, hms, hms-full, timestamp, timestamp-concise "default" to show in HH:MM:SS.sss format. "hms" to show in 1h 2m 3.4s format. "hms-full" is the same as hms but keeps the hours and minutes persistent when they are 0. "timestamp" to show the total time as timestamp 123456.700 format. "timestamp-concise" shows the total time in 123456.7 format (shows and hides decimals depending on availability).
     62 	--in the second parameter, you can define whether to show milliseconds, round them or truncate them. Available options: 'truncate' to remove the milliseconds and keep the seconds. 0 to remove the milliseconds and round the seconds. 1 or above is the amount of milliseconds to display. The default value is 3 milliseconds.
     63 	--in the third parameter you can define the seperator between hour:minute:second. "default" style is automatically set to ":", "hms", "hms-full" are automatically set to " ". You can define your own. Some examples: ["default", 3, "-"],["hms-full", 5, "."],["hms", "truncate", ":"],["timestamp-concise"],["timestamp", 0],["timestamp", "truncate"],["timestamp", 5]
     64 	copy_time_format=[[
     65 	["timestamp-concise"]
     66 	]],
     67 	osd_time_format=[[
     68 	["default", "truncate"]
     69 	]],
     70 
     71 ---------------------------END OF USER CUSTOMIZATION SETTINGS---------------------------
     72 }
     73 
     74 (require 'mp.options').read_options(o)
     75 local utils = require 'mp.utils'
     76 local msg = require 'mp.msg'
     77 
     78 o.copy_keybind = utils.parse_json(o.copy_keybind)
     79 o.paste_keybind = utils.parse_json(o.paste_keybind)
     80 o.copy_specific_keybind = utils.parse_json(o.copy_specific_keybind)
     81 o.paste_specific_keybind = utils.parse_json(o.paste_specific_keybind)
     82 o.paste_protocols = utils.parse_json(o.paste_protocols)
     83 o.paste_extensions = utils.parse_json(o.paste_extensions)
     84 o.paste_subtitles = utils.parse_json(o.paste_subtitles)
     85 o.specific_time_attributes = utils.parse_json(o.specific_time_attributes)
     86 o.pastable_time_attributes = utils.parse_json(o.pastable_time_attributes)
     87 o.copy_time_format = utils.parse_json(o.copy_time_format)
     88 o.osd_time_format = utils.parse_json(o.osd_time_format)
     89 
     90 local protocols = {'https?:', 'magnet:', 'rtmps?:', 'smb:', 'ftps?:', 'sftp:'}
     91 local seekTime = 0
     92 local clip, clip_time, clip_file, filePath, fileTitle
     93 local clipboard_pasted = false
     94 
     95 function has_value(tab, val)
     96 	for index, value in ipairs(tab) do
     97 		if value == val then
     98 			return true
     99 		end
    100 	end
    101 	
    102 	return false
    103 end
    104 
    105 table.insert(o.pastable_time_attributes, o.protocols_time_attribute)
    106 table.insert(o.pastable_time_attributes, o.local_time_attribute)
    107 for i = 1, #o.specific_time_attributes do
    108 	if not has_value(o.pastable_time_attributes, o.specific_time_attributes[i][2]) then
    109 		table.insert(o.pastable_time_attributes, o.specific_time_attributes[i][2])
    110 	end
    111 end
    112 
    113 if not o.device or o.device == 'auto' then
    114 	if os.getenv('windir') ~= nil then
    115 		o.device = 'windows'
    116 	elseif os.execute '[ -d "/Applications" ]' == 0 and os.execute '[ -d "/Library" ]' == 0 or os.execute '[ -d "/Applications" ]' == true and os.execute '[ -d "/Library" ]' == true then
    117 		o.device = 'mac'
    118 	else
    119 		o.device = 'linux'
    120   end
    121 end
    122 
    123 function starts_protocol(tab, val)
    124 	for index, value in ipairs(tab) do
    125 		if (val:find(value) == 1) then
    126 			return true
    127 		end
    128 	end
    129 	return false
    130 end
    131 
    132 function contain_value(tab, val)
    133 	if not tab then return end
    134 	if not val then return end
    135 
    136 	for index, value in ipairs(tab) do
    137 		if value.match(string.lower(val), string.lower(value)) then
    138 			return true
    139 		end
    140 	end
    141 	
    142 	return false
    143 end
    144 
    145 function file_exists(name)
    146 	local f = io.open(name, "r")
    147 	if f ~= nil then io.close(f) return true else return false end
    148 end
    149 
    150 function format_time(seconds, sep, decimals, style)
    151 	local function divmod (a, b)
    152 		return math.floor(a / b), a % b
    153 	end
    154 	decimals = decimals == nil and 3 or decimals
    155 	
    156 	local s = seconds
    157 	local h, s = divmod(s, 60*60)
    158 	local m, s = divmod(s, 60)
    159 
    160 	if decimals == 'truncate' then
    161 		s = math.floor(s)
    162 		decimals = 0
    163 		if style == 'timestamp' then
    164 			seconds = math.floor(seconds)
    165 		end
    166 	end
    167 	
    168 	if not style or style == '' or style == 'default' then
    169 		local second_format = string.format("%%0%d.%df", 2+(decimals > 0 and decimals+1 or 0), decimals)
    170 		sep = sep and sep or ":"
    171 		return string.format("%02d"..sep.."%02d"..sep..second_format, h, m, s)
    172 	elseif style == 'hms' or style == 'hms-full' then
    173 	  sep = sep ~= nil and sep or " "
    174 	  if style == 'hms-full' or h > 0 then
    175 		return string.format("%dh"..sep.."%dm"..sep.."%." .. tostring(decimals) .. "fs", h, m, s)
    176 	  elseif m > 0 then
    177 		return string.format("%dm"..sep.."%." .. tostring(decimals) .. "fs", m, s)
    178 	  else
    179 		return string.format("%." .. tostring(decimals) .. "fs", s)
    180 	  end
    181 	elseif style == 'timestamp' then
    182 		return string.format("%." .. tostring(decimals) .. "f", seconds)
    183 	elseif style == 'timestamp-concise' then
    184 		return seconds
    185 	end
    186 end
    187 
    188 function get_path()
    189 	local path = mp.get_property('path')
    190 	if not path then return end
    191 	
    192 	local title = mp.get_property('media-title'):gsub("\"", "")
    193 	
    194 	if starts_protocol(protocols, path) and o.prefer_filename_over_title == 'protocols' then
    195 		title = mp.get_property('filename'):gsub("\"", "")
    196 	elseif not starts_protocol(protocols, path) and o.prefer_filename_over_title == 'local' then
    197 		title = mp.get_property('filename'):gsub("\"", "")
    198 	elseif o.prefer_filename_over_title == 'all' then
    199 		title = mp.get_property('filename'):gsub("\"", "")
    200 	end
    201 	
    202 	return path, title
    203 end
    204 
    205 function bind_keys(keys, name, func, opts)
    206 	if not keys then
    207 		mp.add_forced_key_binding(keys, name, func, opts)
    208 		return
    209 	end
    210 	
    211 	for i = 1, #keys do
    212 		if i == 1 then 
    213 			mp.add_forced_key_binding(keys[i], name, func, opts)
    214 		else
    215 			mp.add_forced_key_binding(keys[i], name .. i, func, opts)
    216 		end
    217 	end
    218 end
    219 
    220 function handleres(res, args)
    221 	if not res.error and res.status == 0 then
    222 		return res.stdout
    223 	else
    224 		msg.error("There was an error getting "..o.device.." clipboard: ")
    225 		msg.error("  Status: "..(res.status or ""))
    226 		msg.error("  Error: "..(res.error or ""))
    227 		msg.error("  stdout: "..(res.stdout or ""))
    228 		msg.error("args: "..utils.to_string(args))
    229 		return ''
    230 	end
    231 end
    232 
    233 function os.capture(cmd)
    234   local f = assert(io.popen(cmd, 'r'))
    235   local s = assert(f:read('*a'))
    236   f:close()
    237   return s
    238 end
    239 
    240 function make_raw(s)
    241 	if not s then return end
    242 	s = string.gsub(s, '^%s+', '')
    243 	s = string.gsub(s, '%s+$', '')
    244 	s = string.gsub(s, '[\n\r]+', ' ')
    245 	return s
    246 end
    247 
    248 function get_extension(path)
    249 	if not path then return end
    250 
    251     match = string.match(path, '%.([^%.]+)$' )
    252     if match == nil then
    253         return 'nomatch'
    254     else
    255         return match
    256     end
    257 end
    258 
    259 
    260 function get_specific_attribute(target_path)
    261 		local pre_attribute = ''
    262 		local after_attribute = ''
    263 		if not starts_protocol(protocols, target_path) then
    264 			pre_attribute = o.local_time_attribute
    265 		elseif starts_protocol(protocols, target_path) then
    266 			pre_attribute = o.protocols_time_attribute
    267 			for i = 1, #o.specific_time_attributes do
    268 				if contain_value({o.specific_time_attributes[i][1]}, target_path) then
    269 					pre_attribute = o.specific_time_attributes[i][2]
    270 					after_attribute = o.specific_time_attributes[i][3]
    271 					break
    272 				end
    273 			end
    274 		end
    275 	return pre_attribute, after_attribute
    276 end
    277 
    278 function get_time_attribute(target_path)
    279 	local pre_attribute = ''
    280 	for i = 1, #o.pastable_time_attributes do
    281 		if contain_value({o.pastable_time_attributes[i]}, target_path) then
    282 			pre_attribute = o.pastable_time_attributes[i]
    283 			break
    284 		end
    285 	end
    286 	return pre_attribute
    287 end
    288 
    289 
    290 function get_clipboard()
    291 	local clipboard
    292 	if o.device == 'linux' then
    293 		clipboard = os.capture(o.linux_paste)
    294 		return clipboard
    295 	elseif o.device == 'windows' then
    296 		if o.windows_paste == 'powershell' then
    297 			local args = {
    298 				'powershell', '-NoProfile', '-Command', [[& {
    299 					Trap {
    300 						Write-Error -ErrorRecord $_
    301 						Exit 1
    302 					}
    303 					$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
    304 					if (-not $clip) {
    305 						$clip = Get-Clipboard -Raw -Format FileDropList
    306 					}
    307 					Write-Output $clip
    308 				}]]
    309 			}
    310 			return handleres(utils.subprocess({ args =  args, cancellable = false }), args)
    311 		else
    312 			clipboard = os.capture(o.windows_paste)
    313 			return clipboard
    314 		end
    315 	elseif o.device == 'mac' then
    316 		clipboard = os.capture(o.mac_paste)
    317 		return clipboard
    318 	end
    319 	return ''
    320 end
    321 
    322 
    323 function set_clipboard(text)
    324 	local pipe
    325 	if o.device == 'linux' then
    326 		pipe = io.popen(o.linux_copy, 'w')
    327 		pipe:write(text)
    328 		pipe:close()
    329 	elseif o.device == 'windows' then
    330 		if o.windows_copy == 'powershell' then
    331 			local res = utils.subprocess({ args = {
    332 				'powershell', '-NoProfile', '-Command', string.format([[& {
    333 					Trap {
    334 						Write-Error -ErrorRecord $_
    335 						Exit 1
    336 					}
    337 					Add-Type -AssemblyName PresentationCore
    338 					[System.Windows.Clipboard]::SetText('%s')
    339 				}]], text)
    340 			} })
    341 		else
    342 			pipe = io.popen(o.windows_copy,'w')
    343 			pipe:write(text)
    344 			pipe:close()
    345 		end
    346 	elseif o.device == 'mac' then
    347 		pipe = io.popen(o.mac_copy,'w')
    348 		pipe:write(text)
    349 		pipe:close()
    350 	end
    351 	return ''
    352 end
    353 
    354 function parse_clipboard(text)
    355 	if not text then return end
    356 	
    357 	local clip, clip_file, clip_time, pre_attribute
    358 	local clip_table = {}
    359 	clip = text
    360 	
    361 	for c in clip:gmatch("[^\n\r+]+") do
    362 		local c_pre_attribute, c_clip_file, c_clip_time, c_clip_extension
    363 		c = make_raw(c)
    364 		
    365 		c_pre_attribute = get_time_attribute(c)
    366 		if string.match(c, '(.*)'..c_pre_attribute) then
    367 			c_clip_file = string.match(c, '(.*)'..c_pre_attribute)
    368 			c_clip_time = tonumber(string.match(c, c_pre_attribute..'(%d*%.?%d*)'))
    369 		elseif string.match(c, '^\"(.*)\"$') then
    370 			c_clip_file = string.match(c, '^\"(.*)\"$')
    371 		else
    372 			c_clip_file = c
    373 		end
    374 		
    375 		c_clip_extension = get_extension(c_clip_file)
    376 		
    377 		table.insert(clip_table, {c_clip_file, c_clip_time, c_clip_extension})
    378 	end
    379 
    380 	clip = make_raw(clip)
    381 	pre_attribute = get_time_attribute(clip)
    382 
    383 	if string.match(clip, '(.*)'..pre_attribute) then
    384 		clip_file = string.match(clip, '(.*)'..pre_attribute)
    385 		clip_time = tonumber(string.match(clip, pre_attribute..'(%d*%.?%d*)'))
    386 	elseif string.match(clip, '^\"(.*)\"$') then
    387 		clip_file = string.match(clip, '^\"(.*)\"$')
    388 	else
    389 		clip_file = clip
    390 	end
    391 
    392 	return clip, clip_file, clip_time, clip_table
    393 end
    394 
    395 function copy()
    396 	if filePath ~= nil then
    397 		if o.copy_time_method == 'none' or copy_time_method == '' then
    398 			copy_specific('path')
    399 			return
    400 		elseif o.copy_time_method == 'protocols' and not starts_protocol(protocols, filePath) then
    401 			copy_specific('path')
    402 			return
    403 		elseif o.copy_time_method == 'local' and starts_protocol(protocols, filePath) then
    404 			copy_specific('path')
    405 			return
    406 		elseif o.copy_time_method == 'specifics' then
    407 			if not starts_protocol(protocols, filePath) then
    408 				copy_specific('path')
    409 				return
    410 			else
    411 				for i = 1, #o.specific_time_attributes do
    412 					if contain_value({o.specific_time_attributes[i][1]}, filePath) then
    413 						copy_specific('path&timestamp')
    414 						return
    415 					end
    416 				end
    417 				copy_specific('path')
    418 				return
    419 			end
    420 		else
    421 			copy_specific('path&timestamp')
    422 			return
    423 		end
    424 	else
    425 		if o.osd_messages == true then
    426 			mp.osd_message('Failed to Copy\nNo Video Found')
    427 		end
    428 		msg.info('Failed to copy, no video found')
    429 	end
    430 end
    431 
    432 
    433 function copy_specific(action)
    434 	if not action then return end
    435 
    436 	if filePath == nil then
    437 		if o.osd_messages == true then
    438 			mp.osd_message('Failed to Copy\nNo Video Found')
    439 		end
    440 		msg.info("Failed to copy, no video found")
    441 		return
    442 	else
    443 		if action == 'title' then
    444 			if o.osd_messages == true then
    445 				mp.osd_message("Copied:\n"..fileTitle)
    446 			end
    447 			set_clipboard(fileTitle)
    448 			msg.info("Copied the below into clipboard:\n"..fileTitle)
    449 		end
    450 		if action == 'path' then
    451 			if o.osd_messages == true then
    452 				mp.osd_message("Copied:\n"..filePath)
    453 			end
    454 			set_clipboard(filePath)
    455 			msg.info("Copied the below into clipboard:\n"..filePath)
    456 		end
    457 		if action == 'timestamp' then
    458 			local pre_attribute, after_attribute = get_specific_attribute(filePath)
    459 			local video_time = mp.get_property_number('time-pos')
    460 			if o.osd_messages == true then
    461 				mp.osd_message("Copied"..o.time_seperator..format_time(video_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    462 			end
    463 			set_clipboard(pre_attribute..format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])..after_attribute)
    464 			msg.info('Copied the below into clipboard:\n'..pre_attribute..format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])..after_attribute)
    465 		end
    466 		if action == 'path&timestamp' then
    467 			local pre_attribute, after_attribute = get_specific_attribute(filePath)
    468 			local video_time = mp.get_property_number('time-pos')
    469 			if o.osd_messages == true then
    470 				mp.osd_message("Copied:\n" .. fileTitle .. o.time_seperator .. format_time(video_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    471 			end
    472 			set_clipboard(filePath..pre_attribute..format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])..after_attribute)
    473 			msg.info('Copied the below into clipboard:\n'..filePath..pre_attribute..format_time(video_time, o.copy_time_format[3], o.copy_time_format[2], o.copy_time_format[1])..after_attribute)
    474 		end
    475 	end
    476 end
    477 
    478 function trigger_paste_action(action)
    479 	if not action then return end
    480 	
    481 	if action == 'load-file' then
    482 		filePath = clip_file
    483 		if o.osd_messages == true then
    484 			if clip_time ~= nil then
    485 				mp.osd_message("Pasted:\n"..clip_file .. o.time_seperator .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    486 			else
    487 				mp.osd_message("Pasted:\n"..clip_file)
    488 			end
    489 		end
    490 		mp.commandv('loadfile', clip_file)
    491 		clipboard_pasted = true
    492 		
    493 		if clip_time ~= nil then
    494 			msg.info("Pasted the below file into mpv:\n"..clip_file .. format_time(clip_time))
    495 		else
    496 			msg.info("Pasted the below file into mpv:\n"..clip_file)
    497 		end
    498 	end
    499 	
    500 	if action == 'load-subtitle' then
    501 		if o.osd_messages == true then
    502 			mp.osd_message("Pasted Subtitle:\n"..clip_file)
    503 		end
    504 		mp.commandv('sub-add', clip_file, 'select')
    505 		msg.info("Pasted the below subtitle into mpv:\n"..clip_file)
    506 	end
    507 	
    508 	if action == 'file-seek' then
    509 		local video_duration = mp.get_property_number('duration')
    510 		seekTime = clip_time + o.resume_offset
    511 		
    512 		if seekTime > video_duration then 
    513 			if o.osd_messages == true then
    514 				mp.osd_message('Time Paste Exceeds Video Length' .. o.time_seperator .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    515 			end
    516 			msg.info("The time pasted exceeds the video length:\n"..format_time(clip_time))
    517 			return
    518 		end 
    519 		
    520 		if (seekTime < 0) then
    521 			seekTime = 0
    522 		end
    523 	
    524 		if o.osd_messages == true then
    525 			mp.osd_message('Resumed to Pasted Time' .. o.time_seperator .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    526 		end
    527 		mp.commandv('seek', seekTime, 'absolute', 'exact')
    528 		msg.info("Resumed to the pasted time" .. o.time_seperator .. format_time(clip_time))
    529 	end
    530 	
    531 	if action == 'add-playlist' then
    532 		if o.osd_messages == true then
    533 			mp.osd_message('Pasted Into Playlist:\n'..clip_file)
    534 		end
    535 		mp.commandv('loadfile', clip_file, 'append-play')
    536 		msg.info("Pasted the below into playlist:\n"..clip_file)
    537 	end
    538 	
    539 	if action == 'error-subtitle' then
    540 		if o.osd_messages == true then
    541 			mp.osd_message('Subtitle Paste Requires Running Video:\n'..clip_file)
    542 		end
    543 		msg.info('Subtitles can only be pasted if a video is running:\n'..clip_file)
    544 	end
    545 	
    546 	if action == 'error-unsupported' then
    547 		if o.osd_messages == true then
    548 			mp.osd_message('Paste of this item is unsupported possibly due to configuration:\n'..clip)
    549 		end
    550 		msg.info('Failed to paste into mpv, pasted item shown below is unsupported possibly due to configuration:\n'..clip)
    551 	end
    552 	
    553 	if action == 'error-missing' then
    554 		if o.osd_messages == true then
    555 			mp.osd_message('File Doesn\'t Exist:\n' .. clip_file)
    556 		end
    557 		msg.info('The file below doesn\'t seem to exist:\n' .. clip_file)
    558 	end
    559 	
    560 	if action == 'error-time' then
    561 		if o.osd_messages == true then
    562 			if clip_time ~= nil then
    563 				mp.osd_message('Time Paste Requires Running Video' .. o.time_seperator .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    564 			else
    565 				mp.osd_message('Time Paste Requires Running Video')
    566 			end
    567 		end
    568 		
    569 		if clip_time ~= nil then
    570 			msg.info('Time can only be pasted if a video is running:\n'.. format_time(clip_time))
    571 		else
    572 			msg.info('Time can only be pasted if a video is running')
    573 		end
    574 	end
    575 	
    576 	if action == 'error-missingtime' then
    577 		if o.osd_messages == true then
    578 			mp.osd_message('Clipboard does not contain time for seeking:\n'..clip)
    579 		end
    580 		msg.info("Clipboard does not contain the time attribute and time for seeking:\n"..clip)
    581 	end
    582 	
    583 	if action == 'error-samefile' then
    584 		if o.osd_messages == true then
    585 			mp.osd_message('Pasted file is already running:\n'..clip)
    586 		end
    587 		msg.info("Pasted file shown below is already running:\n"..clip)
    588 	end
    589 	
    590 	if action == 'error-unknown' then
    591 		if o.osd_messages == true then
    592 			mp.osd_message('Paste was ignored due to an error:\n'..clip)
    593 		end
    594 		msg.info('Paste was ignored due to an error:\n'..clip)
    595 	end
    596 
    597 end
    598 
    599 function multipaste()
    600 	if #clip_table < 2 then return msg.warn('Single paste should be called instead of multipaste') end
    601 	local file_ignored_total = 0
    602 	local file_subtitle_total = 0
    603 	local triggered_multipaste = {}
    604 
    605 	if filePath == nil then
    606 		for i=1, #clip_table do
    607 			if file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3]) 
    608 			or starts_protocol(o.paste_protocols, clip_table[i][1]) then
    609 				filePath = clip_table[i][1]
    610 				mp.commandv('loadfile', clip_table[i][1])
    611 				clipboard_pasted = true
    612 				table.remove(clip_table, i)
    613 				triggered_multipaste[1] = true
    614 				break
    615 			end
    616 		end
    617 	end
    618 	
    619 	if filePath ~= nil then
    620 		for i=1, #clip_table do
    621 			if file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3])
    622 			or starts_protocol(o.paste_protocols, clip_table[i][1]) then
    623 				mp.commandv('loadfile', clip_table[i][1], 'append-play')
    624 				triggered_multipaste[2] = true
    625 			elseif file_exists(clip_table[i][1]) and has_value(o.paste_subtitles, clip_table[i][3]) then
    626 				mp.commandv('sub-add', clip_table[i][1])
    627 				file_subtitle_total = file_subtitle_total + 1
    628 			elseif not has_value(o.paste_extensions, clip_table[i][3]) and not has_value(o.paste_subtitles, clip_table[i][3]) then
    629 				msg.warn('The below was ignored since it is unsupported possibly due to configuration:\n'..clip_table[i][1])
    630 				file_ignored_total = file_ignored_total + 1
    631 			elseif not file_exists(clip_table[i][1]) then
    632 				msg.warn('The below doesn\'t seem to exist:\n' .. clip_table[i][1])
    633 				file_ignored_total = file_ignored_total + 1
    634 			else
    635 				msg.warn('The below was ignored due to an error:\n' .. clip_table[i][1])
    636 				file_ignored_total = file_ignored_total + 1
    637 			end
    638 		end
    639 	end
    640 	
    641 	local osd_msg = ''
    642 	if triggered_multipaste[1] == true then
    643 		if osd_msg ~= '' then osd_msg = osd_msg..'\n' end
    644 		osd_msg = osd_msg..'Pasted: '..filePath
    645 	end
    646 	if file_subtitle_total > 0 then
    647 		if osd_msg ~= '' then osd_msg = osd_msg..'\n' end
    648 		osd_msg = osd_msg..'Added '..file_subtitle_total..' Subtitle/s'
    649 	end
    650 	if triggered_multipaste[2] == true then
    651 		if osd_msg ~= '' then osd_msg = osd_msg..'\n' end
    652 		osd_msg = osd_msg..'Added Into Playlist '..#clip_table - file_ignored_total - file_subtitle_total..' item/s'
    653 	end	
    654 	if file_ignored_total > 0 then
    655 		if osd_msg ~= '' then osd_msg = osd_msg..'\n' end
    656 		osd_msg = osd_msg..'Ignored '..file_ignored_total.. ' Item/s'
    657 	end
    658 	
    659 	if osd_msg == '' then
    660 		osd_msg = 'Pasted Items Ignored or Unable To Append Into Video:\n'..clip
    661 	end
    662 	
    663 	if o.osd_messages == true then
    664 		mp.osd_message(osd_msg)
    665 	end
    666 	msg.info(osd_msg)
    667 end
    668 
    669 
    670 function paste()
    671 	if o.osd_messages == true then
    672 		mp.osd_message("Pasting...")
    673 	end
    674 	msg.info("Pasting...")
    675 
    676 	clip = get_clipboard(clip)
    677 	if not clip then msg.error('Error: clip is null' .. clip) return end
    678 	clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
    679 	
    680 	if #clip_table > 1 then
    681 		multipaste()
    682 	else
    683 		local currentVideoExtension = string.lower(get_extension(clip_file))
    684 		if filePath == nil then
    685 			if file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension) 
    686 			or starts_protocol(o.paste_protocols, clip_file) then
    687 				trigger_paste_action('load-file')
    688 			elseif file_exists(clip_file) and has_value(o.paste_subtitles, currentVideoExtension) then
    689 				trigger_paste_action('error-subtitle')
    690 			elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    691 				trigger_paste_action('error-unsupported')
    692 			elseif not file_exists(clip_file) then
    693 				trigger_paste_action('error-missing')
    694 			else
    695 				trigger_paste_action('error-unknown')				
    696 			end
    697 		else
    698 			if file_exists(clip_file) and has_value(o.paste_subtitles, currentVideoExtension) then
    699 				trigger_paste_action('load-subtitle')
    700 			elseif o.running_paste_behavior == 'playlist' then
    701 				if filePath ~= clip_file and file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension)
    702 				or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file)
    703 				or filePath == clip_file and file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension) and clip_time == nil
    704 				or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file) and clip_time == nil then
    705 					trigger_paste_action('add-playlist')
    706 				elseif clip_time ~= nil then
    707 					trigger_paste_action('file-seek')
    708 				elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    709 					trigger_paste_action('error-unsupported')
    710 				elseif not file_exists(clip_file) then
    711 					trigger_paste_action('error-missing')
    712 				else
    713 					trigger_paste_action('error-unknown')
    714 				end
    715 			elseif o.running_paste_behavior == 'timestamp' then
    716 				if clip_time ~= nil then
    717 					trigger_paste_action('file-seek')
    718 				elseif file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension) 
    719 				or starts_protocol(o.paste_protocols, clip_file) then
    720 					trigger_paste_action('add-playlist')
    721 				elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    722 					trigger_paste_action('error-unsupported')
    723 				elseif not file_exists(clip_file) then
    724 					trigger_paste_action('error-missing')
    725 				else
    726 					trigger_paste_action('error-unknown')
    727 				end
    728 			elseif o.running_paste_behavior == 'force' then
    729 				if filePath ~= clip_file and file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension) 
    730 				or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file) then
    731 					trigger_paste_action('load-file')
    732 				elseif clip_time ~= nil then
    733 					trigger_paste_action('file-seek')
    734 				elseif file_exists(clip_file) and filePath == clip_file 
    735 				or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file) then
    736 					trigger_paste_action('add-playlist')
    737 				elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    738 					trigger_paste_action('error-unsupported')
    739 				elseif not file_exists(clip_file) then
    740 					trigger_paste_action('error-missing')
    741 				else
    742 					trigger_paste_action('error-unknown')
    743 				end
    744 			end
    745 		end
    746 	end
    747 end
    748 
    749 
    750 function paste_specific(action)
    751 	if not action then return end
    752 	
    753 	if o.osd_messages == true then
    754 		mp.osd_message("Pasting...")
    755 	end
    756 	msg.info("Pasting...")
    757 	
    758 	clip = get_clipboard(clip)
    759 	if not clip then msg.error('Error: clip is null' .. clip) return end
    760 	clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
    761 	
    762 	if #clip_table > 1 then
    763 		multipaste()
    764 	else
    765 		local currentVideoExtension = string.lower(get_extension(clip_file))
    766 		if action == 'playlist' then
    767 			if file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension)
    768 			or starts_protocol(o.paste_protocols, clip_file) then
    769 				trigger_paste_action('add-playlist')
    770 			elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    771 				trigger_paste_action('error-unsupported')
    772 			elseif not file_exists(clip_file) then
    773 				trigger_paste_action('error-missing')
    774 			else
    775 				trigger_paste_action('error-unknown')
    776 			end
    777 		end
    778 		
    779 		if action == 'timestamp' then
    780 			if filePath == nil then
    781 				trigger_paste_action('error-time')
    782 			elseif clip_time ~= nil then
    783 				trigger_paste_action('file-seek')
    784 			elseif clip_time == nil then
    785 				trigger_paste_action('error-missingtime')
    786 			elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    787 				trigger_paste_action('error-unsupported')
    788 			elseif not file_exists(clip_file) then
    789 				trigger_paste_action('error-missing')
    790 			else
    791 				trigger_paste_action('error-unknown')
    792 			end
    793 		end
    794 		
    795 		if action == 'force' then
    796 			if filePath ~= clip_file and file_exists(clip_file) and has_value(o.paste_extensions, currentVideoExtension) 
    797 			or filePath ~= clip_file and starts_protocol(o.paste_protocols, clip_file) then
    798 				trigger_paste_action('load-file')
    799 			elseif file_exists(clip_file) and filePath == clip_file 
    800 			or filePath == clip_file and starts_protocol(o.paste_protocols, clip_file) then
    801 				trigger_paste_action('error-samefile')
    802 			elseif not has_value(o.paste_extensions, currentVideoExtension) and not has_value(o.paste_subtitles, currentVideoExtension) then
    803 				trigger_paste_action('error-unsupported')
    804 			elseif not file_exists(clip_file) then
    805 				trigger_paste_action('error-missing')
    806 			else
    807 				trigger_paste_action('error-unknown')
    808 			end
    809 		end
    810 	end
    811 end
    812 
    813 mp.register_event('file-loaded', function()
    814 	filePath, fileTitle = get_path()
    815 	if clipboard_pasted == true then
    816 		clip = get_clipboard(clip)
    817 		if not clip then msg.error('Error: clip is null' .. clip) return end
    818 		clip, clip_file, clip_time, clip_table = parse_clipboard(clip)
    819 		
    820 		if #clip_table > 1 then
    821 			for i=1, #clip_table do
    822 				if file_exists(clip_table[i][1]) and has_value(o.paste_extensions, clip_table[i][3]) 
    823 				or starts_protocol(o.paste_protocols, clip_table[i][1]) then
    824 					clip_file = clip_table[i][1]
    825 					clip_time = clip_table[i][2]
    826 					break
    827 				end
    828 			end
    829 		end
    830 		
    831 		if filePath == clip_file and clip_time ~= nil then
    832 			local video_duration = mp.get_property_number('duration')
    833 			seekTime = clip_time + o.resume_offset
    834 
    835 			if seekTime > video_duration then 
    836 				if o.osd_messages == true then
    837 					mp.osd_message('Time Paste Exceeds Video Length' .. o.time_seperator .. format_time(clip_time, o.osd_time_format[3], o.osd_time_format[2], o.osd_time_format[1]))
    838 				end
    839 				msg.info("The time pasted exceeds the video length:\n"..format_time(clip_time))
    840 				return
    841 			end 
    842 
    843 			if seekTime < 0 then
    844 				seekTime = 0
    845 			end
    846 
    847 			mp.commandv('seek', seekTime, 'absolute', 'exact')
    848 			clipboard_pasted = false
    849 		end
    850 	end
    851 end)
    852 
    853 bind_keys(o.copy_keybind, 'copy', copy)
    854 bind_keys(o.copy_specific_keybind, 'copy-specific', function()copy_specific(o.copy_specific_behavior)end)
    855 bind_keys(o.paste_keybind, 'paste', paste)
    856 bind_keys(o.paste_specific_keybind, 'paste-specific', function()paste_specific(o.paste_specific_behavior)end)