-
-
Save davehorner/c38b235e995268b7631ae1864e66fdea to your computer and use it in GitHub Desktop.
Script to quickly convert and crop videos from within mpv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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