require 'mp'
require 'mp.msg'
-- Copy utilities for mpv with proper UTF-8 support
local homedir = os.getenv("HOME")
local WINDOWS = 2
local UNIX = 3
local function platform_type()
local utils = require 'mp.utils'
local workdir = utils.to_string(mp.get_property_native("working-directory"))
if string.find(workdir, "\\") then
return WINDOWS
else
return UNIX
end
end
local function command_exists(cmd)
local pipe = io.popen("command -v " .. cmd .. " > /dev/null 2>&1; printf \"$?\"", "r")
if not pipe then
return false
end
local exists = pipe:read() == "0"
pipe:close()
return exists
end
local function get_clipboard_cmd()
if command_exists("xclip") then
-- Explicitly specify UTF-8 encoding for xclip
return "xclip -silent -in -selection clipboard -t text/plain"
elseif command_exists("wl-copy") then
-- wl-copy handles UTF-8 by default, but we can be explicit
return "wl-copy --type text/plain"
elseif command_exists("pbcopy") then
-- pbcopy on macOS handles UTF-8 by default
return "pbcopy"
else
mp.msg.error("No supported clipboard command found")
return false
end
end
local function set_clipboard(text)
if not text or text == "" then
return false
end
if platform == WINDOWS then
-- For Windows, use PowerShell with explicit UTF-8 encoding
-- Create a temporary file with UTF-8 encoding
local temp_file = os.tmpname()
local file = io.open(temp_file, "w")
if file then
file:write(text)
file:close()
-- Use PowerShell to read the file with UTF-8 encoding and set clipboard
local ps_cmd = string.format(
'Get-Content -Path "%s" -Encoding UTF8 -Raw | Set-Clipboard',
temp_file
)
mp.commandv("run", "powershell", "-Command", ps_cmd)
-- Clean up temp file
os.remove(temp_file)
return true
else
mp.msg.error("Failed to create temporary file for clipboard")
return false
end
elseif platform == UNIX and clipboard_cmd then
-- Set UTF-8 locale environment variables to ensure proper encoding
local env_cmd = "LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 " .. clipboard_cmd
local pipe = io.popen(env_cmd, "w")
if pipe then
pipe:write(text)
local success = pipe:close()
return success
else
mp.msg.error("Failed to open clipboard command pipe")
return false
end
else
mp.msg.error("Set_clipboard error: unsupported platform or no clipboard command")
return false
end
end
-- Copy Current Displayed Subtitle with UTF-8 support
local function copySubtitle()
-- Use sub-text property instead of sub-text-osd to get raw subtitle text
local subtitle = mp.get_property("sub-text")
if not subtitle or subtitle == "" then
mp.osd_message("No subtitle currently displayed")
return
end
mp.msg.info("Copying subtitle: " .. subtitle)
if set_clipboard(subtitle) then
mp.osd_message("Subtitle copied: " .. subtitle)
else
mp.osd_message("Failed to copy subtitle to clipboard")
end
end
-- Copy Time
local function copyTime()
local time_pos = mp.get_property_number("time-pos")
local minutes, remainder = divmod(time_pos, 60)
local hours, minutes = divmod(minutes, 60)
local seconds = math.floor(remainder)
local milliseconds = math.floor((remainder - seconds) * 1000)
local time = string.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds)
if set_clipboard(time) then
mp.osd_message(string.format("Time Copied to Clipboard: %s", time))
else
mp.osd_message("Failed to copy time to clipboard")
end
end
-- Copy Filename with Extension
local function copyFilename()
local filename = string.format("%s", mp.get_property_osd("filename"))
local extension = string.match(filename, "%.(%w+)$")
local succ_message = "Filename Copied to Clipboard"
local fail_message = "Failed to copy filename to clipboard"
-- If filename doesn't have an extension then it is a URL.
if not extension then
filename = mp.get_property_osd("path")
succ_message = "URL Copied to Clipboard"
fail_message = "Failed to copy URL to clipboard"
end
if set_clipboard(filename) then
mp.osd_message(string.format("%s: %s", succ_message, filename))
else
mp.osd_message(string.format("%s", fail_message))
end
end
-- Copy Full Filename Path
local function copyFullPath()
if platform == WINDOWS then
full_path = string.format("%s\\%s", mp.get_property_osd("working-directory"), mp.get_property_osd("path"))
else
full_path = string.format("%s/%s", mp.get_property_osd("working-directory"), mp.get_property_osd("path"))
end
if set_clipboard(full_path) then
mp.osd_message(string.format("Full Filename Path Copied to Clipboard: %s", full_path))
else
mp.osd_message("Failed to copy full filename path to clipboard")
end
end
-- Copy Chapter-List
local function copyChapterlist()
local chapterlist = string.format("%s", mp.get_property_native("chapter-list"))
if chapterlist == "" then
mp.osd_message("There is no chapter-list.")
return
end
if set_clipboard(chapterlist) then
mp.osd_message(string.format("Displayed Chapter-List Copied to Clipboard: %s", chapterlist))
else
mp.osd_message("Failed to copy displayed Chapter-List to clipboard")
end
end
-- Copy Current Video Duration
local function copyDuration()
local duration = string.format("%s", mp.get_property_osd("duration"))
if set_clipboard(duration) then
mp.osd_message(string.format("Video/Audio Duration Copied to Clipboard: %s", duration))
else
mp.osd_message("Failed to copy video duration to clipboard")
end
end
-- Copy Current Video Metadata
local function copyMetadata()
local metadata = string.format("%s", mp.get_property_osd("metadata"))
if set_clipboard(metadata) then
mp.osd_message(string.format("Video Metadata Copied to Clipboard: %s", metadata))
else
mp.osd_message("Failed to copy metadata to clipboard")
end
end
-- Function to format duration to "Xh Ym" format, omit hours if zero
local function format_duration(seconds)
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds % 3600) / 60)
if hours > 0 then
return string.format("%dh %dm", hours, minutes)
else
return string.format("%dm", minutes)
end
end
-- -- Function to convert bytes to MiB
-- local function format_filesize(bytes)
-- local mib = bytes / (1024 * 1024)
-- return string.format("%.0fMiB", mib)
-- end
-- Function to convert bytes to MiB, rounding down
local function format_filesize(bytes)
local mib = math.floor(bytes / (1024 * 1024)) -- Use math.floor to round down
return string.format("%dMiB", mib)
end
-- Copy Current Video Metadata Year, Filesize, Duration, Artist, and Title
local function copyMetadataYearAndDuration()
-- Get metadata
local artist = mp.get_property_osd("metadata/Artist") or "Unknown Artist"
local title = mp.get_property_osd("metadata/Title") or "Unknown Title"
local metadatayear = mp.get_property_osd("metadata/Year") or "Unknown Year"
-- Format year, artist, and title
local metadatayearformatted = string.format(" (%s)", metadatayear)
local metadata_artist_title = string.format("%s - %s", artist, title)
-- Get the filesize in bytes and format it
local filesize = mp.get_property_number("file-size", 0) -- Get the file size in bytes
local formatted_filesize = format_filesize(filesize) -- Format filesize in MiB
-- Get the duration in seconds and format it
local duration = mp.get_property_number("duration", 0) -- Get the duration in seconds
local formatted_duration = format_duration(duration) -- Format duration
-- Ensure all variables are not nil before formatting the clipboard text
if metadata_artist_title and metadatayearformatted and formatted_filesize and formatted_duration then
-- Combine the artist, title, year, filesize, and duration for clipboard
local clipboard_text = string.format("%s%s %s %s", metadata_artist_title, metadatayearformatted, formatted_filesize, formatted_duration)
-- Assume set_clipboard is a function that sets the clipboard text
if set_clipboard(clipboard_text) then
mp.osd_message(string.format("Metadata Copied to Clipboard: %s", clipboard_text))
else
mp.osd_message("Failed to copy metadata to clipboard")
end
else
mp.osd_message("Metadata is missing or invalid")
end
end
-- Copy Current LUT3d cube
local function copyLUT()
local lut = string.format("%s", mp.get_property_osd("lut"))
local lutnew = string.gsub(lut, "~", "")
local lutuserpath = homedir .. lutnew
if set_clipboard(lutuserpath) then
mp.osd_message(string.format("Current LUT Copied to Clipboard: %s", lutuserpath))
else
mp.osd_message("Failed to copy LUT to clipboard")
end
end
-- Copy Sub-Font
local function copySubFont()
local subfont = string.format("%s", mp.get_property_osd("sub-font"))
if set_clipboard(subfont) then
mp.osd_message(string.format("sub-font: %s", subfont))
else
mp.osd_message("Failed to copy sub-font to clipboard")
end
end
-- Copy Current Track Subtitle
local function copySubtitleTrack()
local subtrack = string.format("%s", mp.get_property_osd("current-tracks/sub/external-filename"))
if set_clipboard(subtrack) then
mp.osd_message(string.format("Current Track Subtitle Copied to Clipboard: %s", subtrack))
else
mp.osd_message("Failed to copy Current Track Subtitle to clipboard")
end
end
-- Copy Sub-Start Time
local function copySubStart()
local substart = string.format("%s", mp.get_property_osd("sub-start/full"))
if set_clipboard(substart) then
mp.osd_message(string.format("Current Sub-Start Time Copied to Clipboard and moved to Start of Sub: %s", substart))
mp.commandv("seek", substart, "absolute")
else
mp.osd_message("Failed to copy Sub-Start Time to clipboard")
end
end
-- Copy Sub-End Time
local function copySubEnd()
local subend = string.format("%s", mp.get_property_osd("sub-end/full"))
if set_clipboard(subend) then
mp.osd_message(string.format("Current Sub-End Time Copied to Clipboard and moved to End of Sub: %s", subend))
mp.commandv("seek", subend, "absolute")
else
mp.osd_message("Failed to copy Sub-End Time to clipboard")
end
end
-- CopySkim PDF reader to use tabs and collapse TOC by default
local function copySkimMac()
local copyskimmac = "defaults write net.sourceforge.skim-app.skim SKCollapseTOCSublevels -boolean true; defaults write net.sourceforge.skim-app.skim AppleWindowTabbingMode -string always"
if set_clipboard(copyskimmac) then
mp.osd_message(string.format("Skim Settings Copied to Clipboard and moved to End of Sub: %s", copyskimmac))
else
mp.osd_message("Failed to copy CopySkimMac Settings to clipboard")
end
end
-- CopyZipMac avoids putting .DS_Store and __MACOSX folders
local function copyZipMac()
local copyzipmac = "zip -r somedir.zip . -x \"*.DS_Store\""
if set_clipboard(copyzipmac) then
mp.osd_message(string.format("ZipMac directory Copied to Clipboard: %s", copyzipmac))
else
mp.osd_message("Failed to copy ZipMac Settings to clipboard")
end
end
-- CopyOpenwithMac sets file associations to mpv, vscode & skim PDF reader
local function copyOpenwithMac()
local copyopenwithmac = "cd ~/.config/mpv/extrastuff/mac; openwith io.mpv mkv mov mp4 avi mp4 mpeg mpg ogv ts webm wmv avif bmp gif j2k jp2 jpeg jpg jxl png tga tif tiff webp aiff ape flac m4a mka mp3 oga ogg ogm opus wav wma m3u m3u8; openwith com.microsoft.VSCode js lua py conf txt css xml md vtt srt json html; openwith net.sourceforge.skim-app.skim pdf"
if set_clipboard(copyopenwithmac) then
mp.osd_message(string.format("Open with (Mac) Copied to Clipboard: %s", copyopenwithmac))
else
mp.osd_message("Failed to copy Open with (Mac) to clipboard")
end
end
-- ffmpeg is for muxing video, decoding/encoding video/audio
-- yt-dlp is for download yt video/audio/subs/copyDownloadAudioPlaylistReverse
-- parallel is for executing multiple instance at once ..maxing out CPU. Mason Gallery and make opus audiobook
-- imagemagick is for converting image formats, resizing, etc. Mason Gallery
-- rename is a perl/raku to rename files used for Mason Gallery
-- aha is used to convert text to html used for keybindings.html & fix overlapping subs
-- mkvtoolnix used for embedding chapters in mkv video/audio (includes mkvproperedit)
-- gnu-sed (gsed) is equivalent to sed on Linux...needed for Make opus audiobook
-- titlecase (perl script) titlecasing chapters for Make opus audiobook
-- copyMacBrewInstall brew install some apps one time on Mac
local function copyMacBrewInstall()
local copyMacBrewInstall = "brew install ffmpeg yt-dlp parallel imagemagick rename aha mkvtoolnix gnu-sed titlecase typst"
if set_clipboard(copyMacBrewInstall) then
mp.osd_message(string.format("brew install ffmpeg yt-dlp parallel imagemagick rename aha mkvtoolnix gnu-sed titlecase typst Copied to Clipboard: %s", copyMacBrewInstall))
else
mp.osd_message("Failed to copy Mac Brew Install to clipboard")
end
end
-- copyLinuxAptInstall sudo apt install some apps one time on Linux
local function copyLinuxAptInstall()
local copyLinuxAptInstall = "sudo apt install ffmpeg yt-dlp parallel imagemagick rename aha mkvtoolnix titlecase typst"
if set_clipboard(copyLinuxAptInstall) then
mp.osd_message(string.format("sudo apt parallel ffmpeg yt-dlp imagemagick rename aha mkvtoolnix titlecase typst Copied to Clipboard: %s", copyLinuxAptInstall))
else
mp.osd_message("Failed to copy Linux Apt Install to clipboard")
end
end
-- copyepubchapters takes epubs that were split by chapters with calibre using epubsplit plugin and converts them to free-flowing paragraphs plain text to be ready to process for text to speech edge-tss. Start of chapters must contain numbers like so 001. So and So, 002. So and So .....or 1. So and So, 2. So and So
local function copyepubchapters()
local copyepubchapters = "find \"$PWD\" -type f -name \"*.epub\" -exec epub2txt -f {} \\; ; find \"$PWD\" -type f -name \"*.txt\" -exec mv {} . \\; ; for f in *.txt ; do n=`echo $f | sed 's/[^0-9]*//g'` ; p=`printf \"%03d\" $n`; new=`echo $f | sed \"s/${n}/${p}/\"`; mv \"$f\" \"$new\"; done"
if set_clipboard(copyepubchapters) then
mp.osd_message(string.format("Epub Chapters Copied to Clipboard: %s", copyepubchapters))
else
mp.osd_message("Failed to copy Epub Chapters to clipboard")
end
end
-- copyepubchapters to edge-tts
local function copyepubedgetts()
local copyepubedgetts = "for f in *.txt; do edge-tts --voice en-US-SteffanNeural -f \"$f\" --write-media \"${f%.*}.mp3\"; done"
if set_clipboard(copyepubedgetts) then
mp.osd_message(string.format("Edge-tts Copied to Clipboard: %s", copyepubedgetts))
else
mp.osd_message("Failed to copy Edge-tts to clipboard")
end
end
-- copyepubchapters to edge-tts alternate voices
local function copyepubedgettsalternate()
local copyepubedgettsalternate = "~/.config/mpv/extrastuff/bashscripts/edgettsalternatevoices.sh"
if set_clipboard(copyepubedgettsalternate) then
mp.osd_message(string.format("Edge-tts Copied to Clipboard: %s", copyepubedgettsalternate))
else
mp.osd_message("Failed to copy Edge-tts to clipboard")
end
end
-- copySubstitcher Substicher bunch of scripts to process subs such as transcribing, merging
local function copySubstitcher()
local copySubstitcher = "~/.config/mpv/extrastuff/bashscripts/substitcher.sh"
if set_clipboard(copySubstitcher) then
mp.osd_message(string.format("Substitcher Copied to Clipboard: %s", copySubstitcher))
else
mp.osd_message("Failed to copy Substitcher to clipboard")
end
end
platform = platform_type()
if platform == UNIX then
clipboard_cmd = get_clipboard_cmd()
end
-- Key-Bindings
mp.add_key_binding(nil, "copyTime", copyTime)
mp.add_key_binding(nil, "copySubStart", copySubStart)
mp.add_key_binding(nil, "copySubEnd", copySubEnd)
mp.add_key_binding(nil, "copyFilename", copyFilename)
mp.add_key_binding(nil, "copySubExtFilename", copySubExtFilename)
mp.add_key_binding(nil, "copyFullPath", copyFullPath)
mp.add_key_binding(nil, "copySubtitle", copySubtitle)
mp.add_key_binding(nil, "copySubtitleTrack", copySubtitleTrack)
mp.add_key_binding(nil, "copyDuration", copyDuration)
mp.add_key_binding(nil, "copyMetadata", copyMetadata)
mp.add_key_binding(nil, "copyMetadataYearAndDuration", copyMetadataYearAndDuration)
mp.add_key_binding(nil, "copyLUT", copyLUT)
mp.add_key_binding(nil, "copySubFont", copySubFont)
mp.add_key_binding(nil, "copySkimMac", copySkimMac)
mp.add_key_binding(nil, "copyZipMac", copyZipMac)
mp.add_key_binding(nil, "copyOpenwithMac", copyOpenwithMac)
mp.add_key_binding(nil, "copyMacBrewInstall", copyMacBrewInstall)
mp.add_key_binding(nil, "copyLinuxAptInstall", copyLinuxAptInstall)
mp.add_key_binding(nil, "copyepubchapters", copyepubchapters)
mp.add_key_binding(nil, "copyepubedgetts", copyepubedgetts)
mp.add_key_binding(nil, "copyepubedgettsalternate", copyepubedgettsalternate)
mp.add_key_binding(nil, "copySubstitcher", copySubstitcher)
mp.add_key_binding(nil, "copyChapterlist", copyChapterlist)