├── README.md └── mpv-webp.lua /README.md: -------------------------------------------------------------------------------- 1 | # mpv-webp-generator *for windows* 2 | Creates high quality animated webp using mpv hotkeys 3 | 4 | ![Fruits Basket Season 2](https://files.catbox.moe/rt0czz.webp) 5 | 6 | Based on [Scheliux](https://github.com/Scheliux/)'s [gif generator](https://gist.github.com/Ruin0x11/8fae0a9341b41015935f76f913b28d2a) port to windows, which you cand find [here](https://github.com/Scheliux/mpv-gif-generator). 7 | 8 | Thanks to July who helped me with ffmpeg's parameters. 9 | 10 | # Requirements 11 | - Windows 12 | - mpv 13 | - ffmpeg 14 | 15 | # Installation 16 | 17 | First of all, you must make sure `ffmpeg` is in your `%PATH%` and accesible via your command line. After ensuring this, clone or download as zip. Then, head to `%APPDATA%/mpv/scripts` and place `mpv-webp.lua` in there; if neither `%APPDATA%/mpv` nor `%APPDATA%/mpv/scripts` exist, you will have to create them. It's as easy as that! 18 | 19 | [How to install ffmpeg](https://www.wikihow.com/Install-FFmpeg-on-Windows) 20 | 21 | # Configuration 22 | *Note this is still in progress and might not work properly* 23 | 24 | The three options the script offers (at least for now) are: 25 | 26 | * `dir` – Sets the output directory. Default is `C:\Users\%USERNAME%\Desktop\`. 27 | * `rez` – Sets the resolution of the output webp. Default is 600 width. 28 | * `fps` – Sets the framerate of the output webp. Default is 15. Don't go too overboard or the filesize will balloon. 29 | 30 | # webp settings 31 | * `qscale` - set as 90 out of 100. It will determine the quality of the webp. Not recommended to go lower than 85. 32 | * `lossless` - set as 0 by default (lossy), change to 1 for lossless. When doing a lossless export, `qscale` will no longer determine the quality, but the encoding eficiency. 33 | * `compression_level` - set as 6 out of 6. The process might take a while, so if you don't want to wait, you should lower it, but the lower the value, the bigger the filesize. 34 | 35 | ## Usage 36 | You can use `,` and `.` to rewind or foward one frame at a time in order to select the desired starting and ending frames of the webp. You only need to define the start and ending times and to choose if exporting with or without subtitles. In order to do that, you can use the following hotkeys: 37 | 38 | * `w` - Start time 39 | * `W` - End time 40 | * `CTRL+w` - Export webp 41 | * `CTRL+W` - Export webp with subtitles - *only works with srt* 42 | 43 | ## Stuff to do 44 | If you wish to contribute, here are some suggestions: 45 | 46 | * Remake the section that handles hardsubbing to support not only srt, but also ass/ssa while keeping its styles. 47 | * Add support for a config file to customize not only webp's settings, but also keybindings and output directory. (at least I can't make it work on my pc) 48 | * Add automatic crop to crop black borders out of video without interfering with DAR. User must be able to enable and disable this feature via .conf file. 49 | -------------------------------------------------------------------------------- /mpv-webp.lua: -------------------------------------------------------------------------------- 1 | -- Original by Scheliux, Dragoner7 which was ported from Ruin0x11 2 | -- Adapted to webp by DonCanjas 3 | 4 | -- Create animated webps with mpv 5 | -- Requires ffmpeg. 6 | -- Adapted from https://github.com/Scheliux/mpv-gif-generator 7 | -- Usage: "w" to set start frame, "W" to set end frame, "Ctrl+w" to create. 8 | 9 | -- Note: 10 | -- Requires FFmpeg in PATH environment variable or edit ffmpeg_path in the script options, 11 | -- for example, by replacing "ffmpeg" with "C:\Programs\ffmpeg\bin\ffmpeg.exe" 12 | -- Note: 13 | -- A small circle at the top-right corner is a sign that creat is happenning now. 14 | 15 | require 'mp.options' 16 | local msg = require 'mp.msg' 17 | local utils = require "mp.utils" 18 | 19 | local options = { 20 | ffmpeg_path = "ffmpeg", 21 | dir = "~~desktop/", 22 | rez = 600, 23 | fps = 15, 24 | lossless = 0, 25 | quality = 90, 26 | compression_level = 6, 27 | loop = 0, 28 | } 29 | 30 | read_options(options, "webp") 31 | 32 | 33 | local fps 34 | 35 | -- Check for invalid fps values 36 | -- Can you believe Lua doesn't have a proper ternary operator in the year of our lord 2020? 37 | if options.fps ~= nil and options.fps >= 1 and options.fps < 30 then 38 | fps = options.fps 39 | else 40 | fps = 15 41 | end 42 | 43 | -- Set this to the filters to pass into ffmpeg's -vf option. 44 | -- filters="fps=24,scale=320:-1:flags=spline" 45 | filters=string.format("fps=%s,zscale='trunc(ih*dar/2)*2:trunc(ih/2)*2':f=spline36,setsar=1/1,zscale=%s:-1:f=spline36", fps, options.rez) 46 | 47 | -- Setup output directory 48 | local output_directory = mp.command_native({ "expand-path", options.dir }) 49 | --create output_directory if it doesn't exist 50 | if utils.readdir(output_directory) == nil then 51 | local args = { 'powershell', '-NoProfile', '-Command', 'mkdir', output_directory } 52 | local res = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) 53 | if res.status ~= 0 then 54 | msg.error("Failed to create webp_dir save directory "..output_directory..". Error: "..(res.error or "unknown")) 55 | return 56 | end 57 | end 58 | 59 | start_time = -1 60 | end_time = -1 61 | 62 | function make_webp_with_subtitles() 63 | make_webp_internal(true) 64 | end 65 | 66 | function make_webp() 67 | make_webp_internal(false) 68 | end 69 | 70 | function table_length(t) 71 | local count = 0 72 | for _ in pairs(t) do count = count + 1 end 73 | return count 74 | end 75 | 76 | 77 | function make_webp_internal(burn_subtitles) 78 | local start_time_l = start_time 79 | local end_time_l = end_time 80 | if start_time_l == -1 or end_time_l == -1 or start_time_l >= end_time_l then 81 | mp.osd_message("Invalid start/end time.") 82 | return 83 | end 84 | 85 | msg.info("Creating webP.") 86 | mp.osd_message("Creating webP.") 87 | 88 | -- shell escape 89 | function esc_for_sub(s) 90 | s = string.gsub(s, [[\]], [[/]]) 91 | s = string.gsub(s, '"', '"\\""') 92 | s = string.gsub(s, ":", [[\\:]]) 93 | s = string.gsub(s, "'", [[\\']]) 94 | s = string.gsub(s, "%[", "\\%[") 95 | s = string.gsub(s, "%]", "\\%]") 96 | return s 97 | end 98 | 99 | local pathname = mp.get_property("path", "") 100 | local trim_filters = filters 101 | 102 | local position = start_time_l 103 | local duration = end_time_l - start_time_l 104 | 105 | if burn_subtitles then 106 | -- Determine currently active sub track 107 | 108 | local i = 0 109 | local tracks_count = mp.get_property_number("track-list/count") 110 | local subs_array = {} 111 | 112 | -- check for subtitle tracks 113 | 114 | while i < tracks_count do 115 | local type = mp.get_property(string.format("track-list/%d/type", i)) 116 | local selected = mp.get_property(string.format("track-list/%d/selected", i)) 117 | local external = mp.get_property(string.format("track-list/%d/external", i)) 118 | 119 | -- if it's a sub track, save it 120 | 121 | if type == "sub" then 122 | local length = table_length(subs_array) 123 | if selected == "yes" and external == "yes" then 124 | msg.info("Error: external subtitles have been selected") 125 | mp.osd_message("Error: external subtitles have been selected", 2) 126 | return 127 | else 128 | subs_array[length] = selected == "yes" 129 | end 130 | end 131 | i = i + 1 132 | end 133 | 134 | if table_length(subs_array) > 0 then 135 | 136 | local correct_track = 0 137 | 138 | -- iterate through saved subtitle tracks until the correct one is found 139 | 140 | for index, is_selected in pairs(subs_array) do 141 | if (is_selected) then 142 | correct_track = index 143 | end 144 | end 145 | 146 | trim_filters = trim_filters .. string.format(",subtitles='%s':si=%s", esc_for_sub(pathname), correct_track) 147 | 148 | end 149 | 150 | end 151 | 152 | -- make the webp 153 | local filename = mp.get_property("filename/no-ext") 154 | local file_path = output_directory .. "/" .. filename 155 | 156 | -- increment filename 157 | for i=0,999 do 158 | local fn = string.format('%s_%03d.webp', file_path, i) 159 | if not file_exists(fn) then 160 | webpname = fn 161 | break 162 | end 163 | end 164 | if not webpname then 165 | mp.osd_message('No available filenames!') 166 | return 167 | end 168 | 169 | local copyts = "" 170 | 171 | if burn_subtitles then 172 | copyts = "-copyts" 173 | end 174 | 175 | cmd = string.format("%s -y -hide_banner -loglevel error -ss %s %s -t %s -i '%s' -lavfi %s -lossless %s -q:v %s -compression_level %s -loop %s '%s'", options.ffmpeg_path, position, copyts, duration, pathname, trim_filters, options.lossless, options.quality, options.compression_level, options.loop, webpname) 176 | args = { 'powershell', '-NoProfile', '-Command', cmd } 177 | local screenx, screeny, aspect = mp.get_osd_size() 178 | mp.set_osd_ass(screenx, screeny, "{\\an9}● ") 179 | local res = mp.command_native({name = "subprocess", capture_stdout = true, playback_only = false, args = args}) 180 | mp.set_osd_ass(screenx, screeny, "") 181 | if res.status ~= 0 then 182 | msg.info("Failed to creat webP.") 183 | mp.osd_message("Error creating webP, check console for more info.") 184 | return 185 | end 186 | msg.info("webP created.") 187 | mp.osd_message("webP created.") 188 | end 189 | 190 | function set_webp_start() 191 | start_time = mp.get_property_number("time-pos", -1) 192 | mp.osd_message("webP Start: " .. start_time) 193 | end 194 | 195 | function set_webp_end() 196 | end_time = mp.get_property_number("time-pos", -1) 197 | mp.osd_message("webP End: " .. end_time) 198 | end 199 | 200 | function file_exists(name) 201 | local f=io.open(name,"r") 202 | if f~=nil then io.close(f) return true else return false end 203 | end 204 | 205 | mp.add_key_binding("w", "set_webp_start", set_webp_start) 206 | mp.add_key_binding("W", "set_webp_end", set_webp_end) 207 | mp.add_key_binding("Ctrl+w", "make_webp", make_webp) 208 | mp.add_key_binding("Ctrl+W", "make_webp_with_subtitles", make_webp_with_subtitles) --only works with srt for now 209 | --------------------------------------------------------------------------------