Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save davehorner/c38b235e995268b7631ae1864e66fdea to your computer and use it in GitHub Desktop.
Save davehorner/c38b235e995268b7631ae1864e66fdea to your computer and use it in GitHub Desktop.
Script to quickly convert and crop videos from within mpv
-- README:
-- convert script for mpv, just throw it into ~/.mpv/lua/
-- needs: yad, libnotify and at least mpv 0.4
-- bind it to another hotkey with
-- <key> script_message convert_script
-- in your input.conf (standard: alt+w)
-- might or might not work together with the OSC, and it’s somewhat ugly (flickering)
-- probably linux only due to yad, no idea if it compiles elsewhere
local assdraw = require 'mp.assdraw'
local msg = require 'mp.msg'
local opt = require 'mp.options'
-- default options, convert_script.conf is read
local options = {
bitrate_multiplier = 0.975, -- to make sure the file won’t go over the target file size, set it to 1 if you don’t care
output_directory = '"$HOME"',
use_pwd_instead = false, -- overrides output_directory
threads = 4,
}
read_options(options, "convert_script")
-------------------------------------
-- Rectangle selection and drawing --
-------------------------------------
alpha = 180
rect_x1, rect_x2, rect_y1, rect_y2 = 0, 0, 0, 0
function render()
ass = assdraw.ass_new()
ass:draw_start()
ass:append(string.format("{\\1a&H%X&}", alpha))
ass:rect_cw(rect_x1, rect_y1, rect_x2, rect_y2)
ass:pos(0, 0)
ass:draw_stop()
mp.set_osd_ass(width, height, ass.text)
end
function tick()
if set_mouse_area then
mp.set_mouse_area(0, 0, width, height, "draw_rectangle")
end
end
function rect_button_press_event(mouse, event)
if event == "down" then
rect_x1, rect_y1 = mp.get_mouse_pos()
rect_x2, rect_y2 = rect_x1, rect_y1
button_pressed = true
elseif event == "up" then
button_pressed = nil
if (rect_x1 == rect_x2) and (rect_y1 == rect_y2) then
mp.set_osd_ass(width, height, "")
end
if rect_x2 > rect_x1 then
rect_width = rect_x2 - rect_x1
else
rect_width = rect_x1 -rect_x2
rect_x1 = rect_x2
end
if rect_y2 > rect_y1 then
rect_height = rect_y2 - rect_y1
else
rect_height = rect_y1 -rect_y2
rect_y1 = rect_y2
end
if rect_x1 < 0 then
rect_x1 = 0
end
if rect_y1 < 0 then
rect_y1 = 0
end
if (rect_width + rect_x1) >= tonumber(width) then
rect_width = width - rect_x1
end
if (rect_height + rect_y1) >= tonumber(height) then
rect_height = height - rect_y1
end
call_gui()
end
end
function mouse_move()
if button_pressed then
rect_x2, rect_y2 = mp.get_mouse_pos()
render()
end
end
-----------------
-- Main script --
-----------------
function convert_script_hotkey_call ()
if mp.get_property("playback-time") then
playback_time = "playback-time"
else
playback_time = "time-pos"
end
set_mouse_area = true
width = mp.get_property("dwidth")
height = mp.get_property("dheight")
mp.set_osd_ass(width, height, "")
if timepos1 then
timepos2 = mp.get_property(playback_time)
timepos2_humanreadable = mp.get_property_osd(playback_time)
if tonumber(timepos1) > tonumber(timepos2) then
length = timepos1-timepos2
start = timepos2
start_humanreadable = timepos2_humanreadable
end_humanreadable = timepos1_humanreadable
msg.info("End frame set")
elseif tonumber(timepos2) > tonumber(timepos1) then
length = timepos2-timepos1
start = timepos1
start_humanreadable = timepos1_humanreadable
end_humanreadable = timepos2_humanreadable
msg.info("End frame set")
else
msg.error("Both frames are the same, ignoring the second one")
mp.osd_message("Both frames are the same, ignoring the second one")
timepos2 = nil
return
end
timepos1 = nil
call_gui()
else
timepos1 = mp.get_property(playback_time)
timepos1_humanreadable = mp.get_property_osd(playback_time)
msg.info("Start frame set")
mp.osd_message("Start frame set")
end
end
------------
-- Encode --
------------
function encode ()
set_mouse_area = nil
if rect_width == nil or rect_width == 0 then
crop = ""
else
crop = math.floor(rect_width) .. ":" .. math.floor(rect_height) .. ":" .. math.ceil(rect_x1) .. ":" .. math.ceil(rect_y1)
rect_width, rect_height= nil
end
local video = mp.get_property("path")
video = string.gsub(video, "'", "'\\''")
local sid = mp.get_property("sid")
local sub_visibility = mp.get_property("sub-visibility")
local vf = mp.get_property("vf")
if string.len(vf) > 0 then
vf = vf .. ","
end
local sub_file_table = mp.get_property_native("options/sub-file")
local sub_file = ""
for index, param in pairs(sub_file_table) do
sub_file = sub_file .. " --sub-file='" .. string.gsub(tostring(param), "'", "'\\''") .. "'"
end
local sub_auto = mp.get_property("options/sub-auto")
local sub_delay = mp.get_property("sub-delay")
local colormatrix_input_range = mp.get_property_native("colormatrix-input-range")
local hr_seek_demuxer_offset = mp.get_property_native("options/hr-seek-demuxer-offset")
if options.use_pwd_instead then
local pwd = os.getenv("PWD")
pwd = string.gsub(pwd, "'", "'\\''")
options.output_directory = "'" .. pwd .. "'"
end
local filename_ext = mp.get_property_osd("media-title")
filename_ext = string.gsub(filename_ext, "'", "'\\''")
local filename = string.gsub(filename_ext, "%....$","")
if string.len(filename) > 230 then
filename = mp.get_property("options/title")
if filename == 'mpv - ${media-title}' or string.len(filename) > 230 then
filename = 'output'
end
end
filename = filename .. " " .. start_humanreadable .. "-" .. end_humanreadable .. ".webm"
local mpv_options = "mpv '" .. video .. "' --start=+" .. start .. ' --length=' .. length
.. ' ' .. sub_file .. ' --sid=' .. sid .. ' --sub-visibility=' .. sub_visibility
.. ' --sub-delay=' .. sub_delay .. ' --sub-auto=' .. sub_auto
.. ' --colormatrix-input-range=' .. colormatrix_input_range
.. ' --vf=' .. vf .. 'sub,crop=' .. crop .. ',scale=' .. scale
.. ' --hr-seek-demuxer-offset=' .. hr_seek_demuxer_offset
local encode_options = ' --oautofps --of=webm --ovc=libvpx' .. ' --ovcopts=b=' .. bitrate
.. ',cpu-used=0,auto-alt-ref=1,lag-in-frames=25,quality=good,threads=' .. options.threads
encode_options = encode_options .. ',flags=+pass'
local full_output_path = options.output_directory .. "/'" .. filename .. "'"
local full_command = '(' .. mpv_options .. ' --no-audio' .. encode_options
full_command = full_command .. '1'
full_command = full_command .. ' --o=' .. full_output_path
full_command = full_command .. ' && ' .. mpv_options .. ' ' .. audio .. encode_options .. '2'
.. ' --o=' .. full_output_path
.. ' && rm ' .. full_output_path .. '-vo-lavc-pass1.log'
full_command = full_command .. '&& notify-send "Encoding done") &'
msg.info(full_command)
os.execute(full_command)
end
---------
-- GUI --
---------
function call_gui ()
mp.disable_key_bindings("draw_rectangle")
mp.resume_all()
local yad_command = ([[yad --title="Convert Script" --center --form --separator="\n" --fixed \
--field="Resize to height:NUM" "540" \]] --yad_table 1 and 2
.. [[--field="Resize to width instead:CHK" "false" \]] --yad_table 3
.. [[--field="Don’t resize at all:BTN" "@bash -c 'if ]] .. '[[ "a%1" == "a0.000000" ]]'
.. [[; then printf '\''1:1\n2:false'\''; else printf '\''1:0.000000\n1:@disabled@\n2:@disabled@'\''; fi'" \]]
.. [[--field="Include audio:CHK" "false" \]] --yad_table 4
.. [[--field="Target file size (kB):NUM" "3072" \]] --yad_table 5
.. [[--button="Crop:1" --button="gtk-cancel:2" --button="gtk-ok:0" && echo "$?"]])
local handle = io.popen(yad_command)
local yad = handle:read("*a")
handle:close()
if yad == "" then
mp.enable_key_bindings("draw_rectangle")
return
end
local yad_table = {}
local i = 0
for k in string.gmatch(yad, "[%a%d]+") do
i = i + 1
yad_table[i] = k
end
if (yad_table[3] == "FALSE") and (tonumber(yad_table[1]) > 0) then
scale = "-3:" .. yad_table[1]
elseif yad_table[1] == "0" then
scale = ""
else
scale = yad_table[1] .. ":-3"
end
if yad_table[4] == "FALSE" then
audio = '--no-audio'
else
audio = ""
end
bitrate = math.floor(yad_table[5]*1024*8/length*options.bitrate_multiplier)
mp.set_osd_ass(width, height, "")
if yad_table[7] == "0" then
encode()
end
end
mp.set_key_bindings({
{"mouse_move", mouse_move},
{"mouse_btn0", function(e) rect_button_press_event("mouse_btn0", "up") end, function(e) rect_button_press_event("mouse_btn0", "down") end},
}, "draw_rectangle", "force")
mp.add_key_binding("alt+w", "convert_script", convert_script_hotkey_call)
mp.register_event("tick", tick)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment