Skip to content
This repository was archived by the owner on May 4, 2026. It is now read-only.
This repository was archived by the owner on May 4, 2026. It is now read-only.

some additional ones. Think best is copy subtitle with utf-8 and also copy metadata filesize, duration, etc. #9

@mrfragger

Description

@mrfragger
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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions