-- 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 -- 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") if mp.get_property("playback-time") then playback_time = "playback-time" else playback_time = "time-pos" end ------------------------------------- -- 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 () 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)