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×tamp'. 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×tamp') 414 return 415 end 416 end 417 copy_specific('path') 418 return 419 end 420 else 421 copy_specific('path×tamp') 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×tamp' 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)