Files
2026-03-27 07:06:16 +01:00

341 lines
13 KiB
Lua

-- Copyright (c) 2022-2024 dyphire <qimoge@gmail.com>
-- License: MIT
-- link: https://github.com/dyphire/mpv-scripts
--[[
The script calls up a window in mpv to quickly load the folder/files/iso/clipboard (support url)/other subtitles/other audio tracks/other video tracks.
Usage, add bindings to input.conf:
key script-message-to open_dialog import_folder
key script-message-to open_dialog import_files
key script-message-to open_dialog import_files <type> # vid, aid, sid (video/audio/subtitle track)
key script-message-to open_dialog import_clipboard
key script-message-to open_dialog import_clipboard <type> # vid, aid, sid (video/audio/subtitle track)
key script-message-to open_dialog set_clipboard <text> # text can be mpv properties as ${path}
Also supports open dialog to select folder/files for other scripts.
Scripting Example:
-- open a folder select dialog
mp.commandv('script-message-to', 'open_dialog', 'select_folder', mp.get_script_name())
-- receive the selected folder reply
mp.register_script_message('select_folder_reply', function(folder_path)
if folder_path and folder_path ~= '' then
-- do something with folder_path
end
end)
-- open a xml file select dialog
mp.commandv('script-message-to', 'open_dialog', 'select_files', mp.get_script_name(), 'XML File|*.xml')
-- receive the selected files reply
mp.register_script_message('select_files_reply', function(file_paths)
for i, file_path in ipairs(utils.parse_json(file_paths)) do
-- do something with file_path
end
end)
]]--
local msg = require 'mp.msg'
local utils = require 'mp.utils'
local options = require 'mp.options'
o = {
video_types = '3g2,3gp,asf,avi,f4v,flv,h264,h265,m2ts,m4v,mkv,mov,mp4,mp4v,mpeg,mpg,ogm,ogv,rm,rmvb,ts,vob,webm,wmv,y4m',
audio_types = 'aac,ac3,aiff,ape,au,cue,dsf,dts,flac,m4a,mid,midi,mka,mp3,mp4a,oga,ogg,opus,spx,tak,tta,wav,weba,wma,wv',
image_types = 'apng,avif,bmp,gif,j2k,jp2,jfif,jpeg,jpg,jxl,mj2,png,svg,tga,tif,tiff,webp',
subtitle_types = 'aqt,ass,gsub,idx,jss,lrc,mks,pgs,pjs,psb,rt,sbv,slt,smi,sub,sup,srt,ssa,ssf,ttxt,txt,usf,vt,vtt',
playlist_types = 'm3u,m3u8,pls,cue',
iso_types = 'iso',
}
options.read_options(o)
local function split(input)
local ret = {}
for str in string.gmatch(input, "([^,]+)") do
ret[#ret + 1] = string.format("*.%s", str)
end
return ret
end
-- pre-defined file types
local file_types = {
video = table.concat(split(o.video_types), ';'),
audio = table.concat(split(o.audio_types), ';'),
image = table.concat(split(o.image_types), ';'),
iso = table.concat(split(o.iso_types), ';'),
subtitle = table.concat(split(o.subtitle_types), ';'),
playlist = table.concat(split(o.playlist_types), ';'),
}
local powershell = nil
local function pwsh_check()
local arg = {"cmd", "/c", "pwsh", "--version"}
local res = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = arg})
if res.status ~= 0 or res.stdout:match("^PowerShell") == nil then
powershell = "powershell"
else
powershell = "pwsh"
end
end
-- escapes a string so that it can be inserted into powershell as a string literal
local function escape_powershell(str)
if not str then return '""' end
str = str:gsub('[$"`]', '`%1'):gsub('', '`%1'):gsub('', '`%1')
return '"'..str..'"'
end
local function end_file(event)
mp.unregister_event(end_file)
if event["reason"] == "eof" or event["reason"] == "stop" or event["reason"] == "error" then
local bd_device = mp.get_property_native("bluray-device")
local dvd_device = mp.get_property_native("dvd-device")
if event["reason"] == "error" and bd_device and bd_device ~= "" then
loaded_fail = true
else
loaded_fail = false
end
if bd_device then mp.set_property("bluray-device", "") end
if dvd_device then mp.set_property("dvd-device", "") end
end
end
-- open bluray iso or dir
local function open_bluray(path)
mp.set_property('bluray-device', path)
mp.commandv('loadfile', 'bd://')
end
-- open dvd iso or dir
local function open_dvd(path)
mp.set_property('dvd-device', path)
mp.commandv('loadfile', 'dvd://')
end
-- open folder
local function open_folder(path, i)
local fpath, dir = utils.split_path(path)
if utils.file_info(utils.join_path(path, "BDMV")) then
open_bluray(path)
elseif utils.file_info(utils.join_path(path, "VIDEO_TS")) then
open_dvd(path)
elseif dir:upper() == "BDMV" then
open_bluray(fpath)
elseif dir:upper() == "VIDEO_TS" then
open_dvd(fpath)
else
mp.commandv('loadfile', path, i == 1 and 'replace' or 'append')
end
end
-- open files
local function open_files(path, type, i, is_clip)
local ext = string.match(path, "%.([^%.]+)$"):lower()
if file_types['subtitle']:match(ext) then
mp.commandv('sub-add', path, 'cached')
elseif type == 'vid' and (not is_clip or (file_types['video']:match(ext) or file_types['image']:match(ext))) then
mp.commandv('video-add', path, 'cached')
elseif type == 'aid' and (not is_clip or file_types['audio']:match(ext)) then
mp.commandv('audio-add', path, 'cached')
elseif file_types['iso']:match(ext) then
local idle = mp.get_property('idle')
if idle ~= 'yes' then mp.set_property('idle', 'yes') end
mp.register_event("end-file", end_file)
open_bluray(path)
mp.add_timeout(1.0, function()
if idle ~= 'yes' then mp.set_property('idle', idle) end
if loaded_fail then
loaded_fail = false
open_dvd(path)
end
end)
else
mp.commandv('loadfile', path, i == 1 and 'replace' or 'append')
end
end
local function select_folder()
if not powershell then pwsh_check() end
local powershell_script = string.format([[&{
Add-Type -AssemblyName System.Windows.Forms
$fbd = New-Object System.Windows.Forms.FolderBrowserDialog
$fbd.RootFolder = "Desktop"
$fbd.ShowNewFolderButton = $true
$owner = New-Object System.Windows.Forms.NativeWindow
$owner.AssignHandle((Get-Process -Id %d).MainWindowHandle)
try {
if ($fbd.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
$u8 = [System.Text.Encoding]::UTF8
$out = [Console]::OpenStandardOutput()
$selectedFolder = $fbd.SelectedPath
$u8selectedFolder = $u8.GetBytes("$selectedFolder`n")
$out.Write($u8selectedFolder, 0, $u8selectedFolder.Length)
}
} finally {
$owner.ReleaseHandle()
$fbd.Dispose()
}
}]], mp.get_property_number('pid'))
local res = mp.command_native({
name = 'subprocess',
playback_only = false,
capture_stdout = true,
args = { powershell, '-NoProfile', '-Command', powershell_script },
})
if res.status ~= 0 then
mp.osd_message("Failed to open folder dialog.")
return nil
end
local folder_path = res.stdout:match("(.-)[\r\n]?$") -- Trim any trailing newline
return folder_path
end
local function select_files(filter)
if not powershell then pwsh_check() end
local powershell_script = string.format([[&{
Add-Type -AssemblyName System.Windows.Forms
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Multiselect = $true
$ofd.Filter = %s
$owner = New-Object System.Windows.Forms.NativeWindow
$owner.AssignHandle((Get-Process -Id %d).MainWindowHandle)
try {
if ($ofd.ShowDialog($owner) -eq [System.Windows.Forms.DialogResult]::OK) {
$u8 = [System.Text.Encoding]::UTF8
$out = [Console]::OpenStandardOutput()
ForEach ($filename in $ofd.FileNames) {
$u8filename = $u8.GetBytes("$filename`n")
$out.Write($u8filename, 0, $u8filename.Length)
}
}
} finally {
$owner.ReleaseHandle()
$ofd.Dispose()
}
}]], escape_powershell(filter), mp.get_property_number('pid'))
local res = mp.command_native({
name = 'subprocess',
playback_only = false,
capture_stdout = true,
args = { powershell, '-NoProfile', '-Command', powershell_script },
})
local file_paths = {}
if res.status ~= 0 then
mp.osd_message("Failed to open files dialog.")
return file_paths
end
for file_path in string.gmatch(res.stdout, '[^\r\n]+') do
table.insert(file_paths, file_path)
end
return file_paths
end
-- import folder
local function import_folder()
local folder_path = select_folder()
if folder_path and folder_path ~= '' then open_folder(folder_path, 1) end
end
-- import files
local function import_files(type)
local filter = ''
if type == 'vid' then
filter = string.format("Video Files|%s|Image Files|%s", file_types['video'], file_types['image'])
elseif type == 'aid' then
filter = string.format("Audio Files|%s", file_types['audio'])
elseif type == 'sid' then
filter = string.format("Subtitle Files|%s", file_types['subtitle'])
else
filter = string.format("All Files (*.*)|*.*|Video Files|%s|Audio Files|%s|Image Files|%s|ISO Files|%s|Subtitle Files|%s|Playlist Files|%s",
file_types['video'], file_types['audio'], file_types['image'], file_types['iso'], file_types['subtitle'], file_types['playlist'])
end
for i, file_path in ipairs(select_files(filter)) do
open_files(file_path, type, i, false)
end
end
-- Returns a string of UTF-8 text from the clipboard
local function get_clipboard()
if mp.get_property('clipboard-backends') ~= nil or mp.get_property_bool('clipboard-enable') then
return mp.get_property('clipboard/text', '')
end
local res = mp.command_native({
name = 'subprocess',
playback_only = false,
capture_stdout = true,
args = { 'powershell', '-NoProfile', '-Command', [[& {
Trap {
Write-Error -ErrorRecord $_
Exit 1
}
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
if (-not $clip) {
$clip = Get-Clipboard -Raw -Format FileDropList
}
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
}]] }
})
if not res.error then
return res.stdout
end
return ''
end
-- open files from clipboard
local function open_clipboard(path, type, i)
local path = path:gsub("^[\'\"]", ""):gsub("[\'\"]$", ""):gsub('^%s+', ''):gsub('%s+$', '')
if path:find('^%a[%w.+-]-://') then
mp.commandv('loadfile', path, i == 1 and 'replace' or 'append')
else
local meta = utils.file_info(path)
if not meta then
mp.osd_message('Clipboard path is invalid')
msg.warn('Clipboard path is invalid')
elseif meta.is_dir then
open_folder(path, i)
elseif meta.is_file then
open_files(path, type, i, true)
else
mp.osd_message('Clipboard path is invalid')
msg.warn('Clipboard path is invalid')
end
end
end
-- import clipboard
local function import_clipboard(type)
local clip = get_clipboard()
if clip ~= '' then
local i = 0
for path in string.gmatch(clip, '[^\r\n]+') do
i = i + 1
open_clipboard(path, type, i)
end
else
mp.osd_message('Clipboard is empty')
msg.warn('Clipboard is empty')
end
end
-- sets the contents of the clipboard to the given string
local function set_clipboard(text)
msg.verbose('setting clipboard text:', text)
if mp.get_property('clipboard-backends') ~= nil or mp.get_property_bool('clipboard-enable') then
mp.commandv('set', 'clipboard/text', text)
else
mp.commandv('run', 'powershell', '-NoProfile', '-command', 'set-clipboard', escape_powershell(text))
end
end
mp.register_script_message('import_folder', import_folder)
mp.register_script_message('import_files', import_files)
mp.register_script_message('import_clipboard', import_clipboard)
mp.register_script_message('set_clipboard', set_clipboard)
mp.register_script_message('select_folder', function(script_name)
local folder_path = select_folder()
mp.commandv('script-message-to', script_name, 'select_folder_reply', folder_path)
end)
mp.register_script_message('select_files', function(script_name, filter)
local file_paths = select_files(filter)
mp.commandv('script-message-to', script_name, 'select_files_reply', utils.format_json(file_paths))
end)