├── .gitignore ├── LICENSE ├── README.md └── scripts ├── vlcaspectratio.lua └── vlccrop.lua /.gitignore: -------------------------------------------------------------------------------- 1 | input.conf 2 | mpv.conf 3 | scripts/autocrop.lua 4 | scripts/autoload.lua 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kieran Gee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mpvscripts 2 | KiSM's mpv scripts 3 | 4 | ### Why? 5 | I created this project to: 6 | - Implement the VLC crop hotkey 'c' 7 | - Implement the VLC aspect ratio hotkey 'a' 8 | 9 | More scripts may come in the future. 10 | 11 | ### File location 12 | 13 | Where to put mpv lua scripts, create the folders if the don't exist: 14 | 15 | \*nix: 16 | 17 | `~/.config/mpv/scripts` 18 | 19 | Windows: 20 | 21 | `%appdata%\mpv\scripts` 22 | -------------------------------------------------------------------------------- /scripts/vlcaspectratio.lua: -------------------------------------------------------------------------------- 1 | -- vlc style aspect ratio stretch for mpv 2 | -- uses hotey 'a', same as VLC 3 | -- https://github.com/kism/mpvscripts 4 | 5 | require "mp.msg" 6 | require "mp.options" 7 | 8 | local ar_option = 0 9 | 10 | function has_video() 11 | for _, track in pairs(mp.get_property_native('track-list')) do 12 | if track.type == 'video' and track.selected then 13 | return not track.albumart 14 | end 15 | end 16 | 17 | return false 18 | end 19 | 20 | function on_press() 21 | -- If there is no video to stretch the AR of 22 | if not has_video() then 23 | mp.msg.warn("autocrop only works for videos.") 24 | return 25 | end 26 | 27 | local ar 28 | local ar_text 29 | 30 | ar_option = ar_option + 1 31 | 32 | if ar_option == 1 then 33 | ar = "16:9" 34 | elseif ar_option == 2 then 35 | ar = "4:3" 36 | elseif ar_option == 3 then 37 | ar = "1:1" 38 | elseif ar_option == 4 then 39 | ar = "16:10" 40 | elseif ar_option == 5 then 41 | ar = "2.21:1" 42 | elseif ar_option == 6 then 43 | ar = "2.35:1" 44 | elseif ar_option == 7 then 45 | ar = "2.39:1" 46 | elseif ar_option == 8 then 47 | ar = "5:4" 48 | elseif ar_option == 9 then 49 | ar = 0 50 | elseif ar_option == 10 then 51 | ar = -1 52 | ar_option = 0 53 | end 54 | 55 | if type(ar) == "number" then 56 | if ar == 0 then 57 | ar_text = "Force PAR 1:1" 58 | elseif ar == -1 then 59 | ar_text = "Default" 60 | end 61 | else 62 | ar_text = tostring(ar) 63 | end 64 | 65 | mp.msg.info("Aspect Ratio: " .. ar_text) 66 | mp.osd_message("Aspect Ratio: " .. ar_text) 67 | mp.set_property("video-aspect-override", ar) 68 | end 69 | 70 | function cleanup() 71 | mp.msg.verbose("Cleanup") 72 | ar_option = 0 73 | mp.set_property("video-aspect-override", -1) 74 | return true 75 | end 76 | 77 | mp.add_key_binding("a", "toggle_stretch", on_press) 78 | mp.register_event("file-loaded", cleanup) 79 | -------------------------------------------------------------------------------- /scripts/vlccrop.lua: -------------------------------------------------------------------------------- 1 | -- vlc style crop for mpv 2 | -- uses hotey 'c', same as VLC 3 | -- https://github.com/kism/mpvscripts 4 | 5 | require "mp.msg" 6 | require "mp.options" 7 | 8 | local crop_option = 0 9 | local cropstring = string.format("%s-crop", mp.get_script_name()) 10 | local command_prefix = 'no-osd' --set this to null to debug 11 | 12 | function get_target_ar(file_ar) 13 | local result 14 | -- Handling the current crop status in this function since its scope needs to transcent this function 15 | crop_option = crop_option + 1 16 | 17 | if crop_option == 1 then 18 | mp.osd_message("Crop: 16:10") 19 | result = 16 / 10 20 | elseif crop_option == 2 then 21 | mp.osd_message("Crop: 16:9") 22 | result = 16 / 9 23 | elseif crop_option == 3 then 24 | mp.osd_message("Crop: 4:3") 25 | result = 4 / 3 26 | elseif crop_option == 4 then 27 | mp.osd_message("Crop: 1.85:1") 28 | result = 1.85 / 1 29 | elseif crop_option == 5 then 30 | mp.osd_message("Crop: 2.21:1") 31 | result = 2.21 / 1 32 | elseif crop_option == 6 then 33 | mp.osd_message("Crop: 2.35:1") 34 | result = 2.35 / 1 35 | elseif crop_option == 7 then 36 | mp.osd_message("Crop: 2.39:1") 37 | result = 2.39 / 1 38 | elseif crop_option == 8 then 39 | mp.osd_message("Crop: 5:3") 40 | result = 5 / 3 41 | elseif crop_option == 9 then 42 | mp.osd_message("Crop: 5:4") 43 | result = 5 / 4 44 | elseif crop_option == 10 then 45 | mp.osd_message("Crop: 1:1") 46 | result = 1 / 1 47 | elseif crop_option == 11 then 48 | mp.osd_message("Crop: 9:16") 49 | result = 9 / 16 50 | else 51 | mp.osd_message("Crop: Default") 52 | crop_option = 0 53 | result = file_ar 54 | end 55 | 56 | return result 57 | end 58 | 59 | function has_video() 60 | for _, track in pairs(mp.get_property_native('track-list')) do 61 | if track.type == 'video' and track.selected then 62 | return not track.albumart 63 | end 64 | end 65 | 66 | return false 67 | end 68 | 69 | function on_press() 70 | -- If it's not cropable, exit. 71 | if not has_video() then 72 | mp.msg.warn("autocrop only works for videos.") 73 | return 74 | end 75 | 76 | -- Get current video fields, this doesnt take into consideration pixel aspect ratio 77 | local width = mp.get_property_native("width") 78 | local height = mp.get_property_native("height") 79 | local aspect = mp.get_property_native("video-params/aspect") 80 | local par = mp.get_property_native("video-params/par") 81 | 82 | local new_w 83 | local new_h 84 | local new_x 85 | local new_y 86 | 87 | -- Get target aspect ratio 88 | target_ar = get_target_ar(aspect) 89 | mp.msg.info("Cropping Video, Target Aspect Ratio: " .. tostring(target_ar)) 90 | 91 | -- Compare target AR to current AR, crop height or width depending on what is needed 92 | -- The if statements 93 | if target_ar < aspect * 0.99 then 94 | -- Reduce width 95 | new_w = (height * target_ar) / par -- New width is the width multiple by the aspect ratio, adjusted for the PAR (pixel aspect ratio) incase it's not 1:1 96 | new_h = height -- Height stays the same since we only ever crop one axis in this script 97 | new_x = (width - new_w) / 2 -- Width - new width will equal the total space cropped, since its evenly cropped from both sides the offset needs to be halved 98 | new_y = 0 -- This along with the height being the video height means that it will crop zero pixels 99 | elseif target_ar > aspect * 1.01 then 100 | -- Reduce height 101 | new_w = width -- See new_h above 102 | new_h = (width * (1 / target_ar)) * par -- See new_w above, need to adjust for PAR but it's in the reverse direction 103 | new_x = 0 -- See new_y above 104 | new_y = (height - new_h) / 2 -- See new_h above 105 | else 106 | -- So if the target aspect ratio is the same as the source (or within 1%), ) 107 | mp.msg.verbose("Target aspect ratio = source aspect ratio, removing filter") 108 | cleanup() -- remove the crop filter 109 | return -- exit the function before we apply that crop 110 | end 111 | 112 | -- Apply crop 113 | mp.command(string.format("%s vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s", 114 | command_prefix, cropstring, new_w, new_h, new_x, new_y)) 115 | end 116 | 117 | function cleanup() 118 | mp.msg.verbose("Cleanup") 119 | 120 | -- This looks for applied filters that match the filter that we are using, then removes them 121 | local filters = mp.get_property_native("vf") 122 | for index, filter in pairs(filters) do 123 | mp.msg.verbose("Applied Crop : " .. tostring(filter["label"])) 124 | mp.msg.verbose("Comparing to : " .. tostring(cropstring)) 125 | if filter["label"] == cropstring then 126 | mp.msg.info("Removing Crop") 127 | mp.command(string.format('%s vf remove @%s', command_prefix, cropstring)) 128 | return true 129 | end 130 | end 131 | 132 | return false 133 | end 134 | 135 | function on_start() 136 | cleanup() 137 | crop_option = 0 -- Reset crop option 138 | end 139 | 140 | mp.add_key_binding("c", "toggle_crop", on_press) 141 | mp.register_event("file-loaded", on_start) 142 | --------------------------------------------------------------------------------