2021-04-14 20:57:14 +00:00
|
|
|
|
--[[
|
|
|
|
|
Videoclip - mp4/webm clips creator for mpv.
|
|
|
|
|
|
|
|
|
|
Copyright (C) 2021 Ren Tatsumoto
|
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
]]
|
|
|
|
|
|
2022-04-04 15:14:17 +00:00
|
|
|
|
local NAME = 'videoclip'
|
2021-04-14 15:30:40 +00:00
|
|
|
|
local mp = require('mp')
|
2020-08-21 04:19:06 +00:00
|
|
|
|
local mpopt = require('mp.options')
|
2020-10-02 08:27:40 +00:00
|
|
|
|
local utils = require('mp.utils')
|
2021-04-14 20:41:24 +00:00
|
|
|
|
local OSD = require('osd_styler')
|
2023-06-25 19:23:02 +00:00
|
|
|
|
local p = require('platform')
|
|
|
|
|
local h = require('helpers')
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
2023-06-19 14:43:52 +00:00
|
|
|
|
------------------------------------------------------------
|
|
|
|
|
-- System-dependent variables
|
2023-06-17 12:47:43 +00:00
|
|
|
|
|
2020-08-21 04:19:06 +00:00
|
|
|
|
-- Options can be changed here or in a separate config file.
|
|
|
|
|
-- Config path: ~/.config/mpv/script-opts/videoclip.conf
|
|
|
|
|
local config = {
|
2020-08-27 07:12:30 +00:00
|
|
|
|
-- absolute paths
|
2020-09-19 23:47:10 +00:00
|
|
|
|
-- relative paths (e.g. ~ for home dir) do NOT work.
|
2023-06-25 19:23:02 +00:00
|
|
|
|
video_folder_path = p.default_video_folder,
|
|
|
|
|
audio_folder_path = p.default_audio_folder,
|
2020-08-21 04:19:06 +00:00
|
|
|
|
-- The range of the CRF scale is 0–51, where 0 is lossless,
|
|
|
|
|
-- 23 is the default, and 51 is worst quality possible.
|
2020-09-20 00:43:41 +00:00
|
|
|
|
-- Insane values like 9999 still work but produce the worst quality.
|
2020-08-21 04:19:06 +00:00
|
|
|
|
video_quality = 23,
|
|
|
|
|
-- Use the slowest preset that you have patience for.
|
|
|
|
|
-- https://trac.ffmpeg.org/wiki/Encode/H.264
|
|
|
|
|
preset = 'faster',
|
2020-10-23 15:34:24 +00:00
|
|
|
|
video_format = 'mp4', -- mp4, vp9, vp8
|
2020-10-23 15:34:45 +00:00
|
|
|
|
video_bitrate = '1M',
|
2020-08-21 04:19:06 +00:00
|
|
|
|
video_width = -2,
|
|
|
|
|
video_height = 480,
|
2021-12-17 11:43:57 +00:00
|
|
|
|
video_fps = 'auto',
|
2021-07-08 09:44:04 +00:00
|
|
|
|
audio_format = 'opus', -- aac, opus
|
|
|
|
|
audio_bitrate = '32k', -- 32k, 64k, 128k, 256k. aac requires higher bitrates.
|
2020-10-23 15:34:24 +00:00
|
|
|
|
font_size = 24,
|
2021-12-29 09:22:55 +00:00
|
|
|
|
clean_filename = true,
|
2023-06-18 13:20:57 +00:00
|
|
|
|
-- Whether to upload to catbox (permanent) or litterbox (temporary)
|
2023-06-21 14:39:34 +00:00
|
|
|
|
litterbox = true,
|
2023-06-18 13:20:57 +00:00
|
|
|
|
-- Determines expire time of files uploaded to litterbox
|
|
|
|
|
litterbox_expire = '72h', -- 1h, 12h, 24h, 72h
|
2023-10-10 23:07:47 +00:00
|
|
|
|
sub_font = 'Noto Sans CJK JP',
|
2020-08-21 04:19:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-04 15:14:17 +00:00
|
|
|
|
mpopt.read_options(config, NAME)
|
2020-10-23 13:34:23 +00:00
|
|
|
|
local main_menu
|
2020-10-23 13:33:10 +00:00
|
|
|
|
local pref_menu
|
2020-09-19 23:46:12 +00:00
|
|
|
|
local encoder
|
2020-08-26 04:29:22 +00:00
|
|
|
|
local Timings
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
2020-09-24 18:16:14 +00:00
|
|
|
|
local allowed_presets = {
|
2020-09-20 00:44:35 +00:00
|
|
|
|
ultrafast = true,
|
|
|
|
|
superfast = true,
|
|
|
|
|
veryfast = true,
|
|
|
|
|
faster = true,
|
|
|
|
|
fast = true,
|
|
|
|
|
medium = true,
|
|
|
|
|
slow = true,
|
|
|
|
|
slower = true,
|
|
|
|
|
veryslow = true,
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 04:19:06 +00:00
|
|
|
|
------------------------------------------------------------
|
2020-10-23 15:34:24 +00:00
|
|
|
|
-- Utility functions
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
2020-08-26 04:29:22 +00:00
|
|
|
|
local function remove_extension(filename)
|
2020-09-19 23:47:10 +00:00
|
|
|
|
return filename:gsub('%.%w+$', '')
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-08-26 04:29:22 +00:00
|
|
|
|
local function remove_text_in_brackets(str)
|
2020-09-19 23:47:10 +00:00
|
|
|
|
return str:gsub('%b[]', '')
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-08-26 04:29:22 +00:00
|
|
|
|
local function remove_special_characters(str)
|
2021-12-29 09:19:15 +00:00
|
|
|
|
return str:gsub('[%-_]', ' '):gsub('[%c%p]', ''):gsub('%s+', ' ')
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-09-03 00:13:17 +00:00
|
|
|
|
local function human_readable_time(seconds)
|
|
|
|
|
if type(seconds) ~= 'number' or seconds < 0 then
|
2020-08-21 04:19:06 +00:00
|
|
|
|
return 'empty'
|
|
|
|
|
end
|
|
|
|
|
|
2020-09-03 00:13:17 +00:00
|
|
|
|
local parts = {}
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
2020-09-19 23:47:10 +00:00
|
|
|
|
parts.h = math.floor(seconds / 3600)
|
|
|
|
|
parts.m = math.floor(seconds / 60) % 60
|
|
|
|
|
parts.s = math.floor(seconds % 60)
|
2020-09-03 00:13:17 +00:00
|
|
|
|
parts.ms = math.floor((seconds * 1000) % 1000)
|
|
|
|
|
|
|
|
|
|
local ret = string.format("%02dm%02ds%03dms", parts.m, parts.s, parts.ms)
|
|
|
|
|
|
|
|
|
|
if parts.h > 0 then
|
|
|
|
|
ret = string.format('%dh%s', parts.h, ret)
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-09-03 00:13:17 +00:00
|
|
|
|
return ret
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-08-26 04:29:22 +00:00
|
|
|
|
local function construct_filename()
|
2020-08-21 04:19:06 +00:00
|
|
|
|
local filename = mp.get_property("filename") -- filename without path
|
|
|
|
|
|
|
|
|
|
filename = remove_extension(filename)
|
2021-12-29 09:22:55 +00:00
|
|
|
|
|
|
|
|
|
if config.clean_filename then
|
|
|
|
|
filename = remove_text_in_brackets(filename)
|
|
|
|
|
filename = remove_special_characters(filename)
|
|
|
|
|
end
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
|
|
|
|
filename = string.format(
|
2023-05-03 18:50:48 +00:00
|
|
|
|
'%s_%s-%s',
|
2020-09-13 20:02:45 +00:00
|
|
|
|
filename,
|
2020-10-23 13:34:23 +00:00
|
|
|
|
human_readable_time(main_menu.timings['start']),
|
|
|
|
|
human_readable_time(main_menu.timings['end'])
|
2020-08-21 04:19:06 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return filename
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:19:36 +00:00
|
|
|
|
local function force_resolution(width, height, clip_fn, ...)
|
|
|
|
|
local cached_prefs = {
|
|
|
|
|
video_width = config.video_width,
|
|
|
|
|
video_height = config.video_height,
|
|
|
|
|
}
|
|
|
|
|
config.video_width = width
|
|
|
|
|
config.video_height = height
|
|
|
|
|
clip_fn(...)
|
|
|
|
|
config.video_width = cached_prefs.video_width
|
|
|
|
|
config.video_height = cached_prefs.video_height
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-08 09:44:04 +00:00
|
|
|
|
local function set_encoding_settings()
|
2020-10-23 13:17:08 +00:00
|
|
|
|
if config.video_format == 'mp4' then
|
|
|
|
|
config.video_codec = 'libx264'
|
|
|
|
|
config.video_extension = '.mp4'
|
2020-10-23 15:34:24 +00:00
|
|
|
|
elseif config.video_format == 'vp9' then
|
2020-10-23 13:17:08 +00:00
|
|
|
|
config.video_codec = 'libvpx-vp9'
|
|
|
|
|
config.video_extension = '.webm'
|
2020-10-23 15:34:24 +00:00
|
|
|
|
else
|
|
|
|
|
config.video_codec = 'libvpx'
|
|
|
|
|
config.video_extension = '.webm'
|
2020-10-23 13:17:08 +00:00
|
|
|
|
end
|
2021-07-08 09:44:04 +00:00
|
|
|
|
|
|
|
|
|
if config.audio_format == 'aac' then
|
|
|
|
|
config.audio_codec = 'aac'
|
2021-07-10 20:56:35 +00:00
|
|
|
|
config.audio_extension = '.aac'
|
2021-07-08 09:44:04 +00:00
|
|
|
|
else
|
|
|
|
|
config.audio_codec = 'libopus'
|
2021-07-10 20:56:35 +00:00
|
|
|
|
config.audio_extension = '.opus'
|
2021-07-08 09:44:04 +00:00
|
|
|
|
end
|
2020-10-23 13:17:08 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-24 09:02:32 +00:00
|
|
|
|
local function validate_config()
|
|
|
|
|
if not config.audio_bitrate:match('^%d+[kK]$') then
|
|
|
|
|
config.audio_bitrate = (tonumber(config.audio_bitrate) or 32) .. 'k'
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if not config.video_bitrate:match('^%d+[kKmM]$') then
|
|
|
|
|
config.video_bitrate = '1M'
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
if not allowed_presets[config.preset] then
|
|
|
|
|
config.preset = 'faster'
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-08 09:44:04 +00:00
|
|
|
|
set_encoding_settings()
|
2020-10-24 09:02:32 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-06-25 19:23:02 +00:00
|
|
|
|
local function upload_to_catbox(outfile)
|
|
|
|
|
local endpoint = config.litterbox and 'https://litterbox.catbox.moe/resources/internals/api.php' or 'https://catbox.moe/user/api.php'
|
|
|
|
|
h.notify("Uploading to " .. (config.litterbox and "litterbox.catbox.moe..." or "catbox.moe..."), "info", 9999)
|
|
|
|
|
|
|
|
|
|
-- This uses cURL to send a request to the cat-/litterbox API.
|
|
|
|
|
-- cURL is included on Windows 10 and up, most Linux distributions and macOS.
|
|
|
|
|
|
|
|
|
|
local r = h.subprocess({ -- This is technically blocking, but I don't think it has any real consequences ..?
|
|
|
|
|
p.curl_exe, '-s',
|
|
|
|
|
'-F', 'reqtype=fileupload',
|
|
|
|
|
'-F', 'time=' .. config['litterbox_expire'],
|
|
|
|
|
'-F', 'fileToUpload=@"' .. outfile .. '"',
|
|
|
|
|
endpoint
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
-- Exit codes in the range [0, 99] are returned by cURL itself.
|
|
|
|
|
-- Any other exit code means the shell failed to execute cURL.
|
|
|
|
|
if r.status < 0 or r.status > 99 then
|
|
|
|
|
h.notify("Error: Failed to upload. Make sure cURL is installed and in your PATH.", "error", 3)
|
|
|
|
|
return
|
|
|
|
|
elseif r.status ~= 0 then
|
|
|
|
|
h.notify("Error: Failed to upload to " .. (config.litterbox and "litterbox.catbox.moe" or "catbox.moe"), "error", 2)
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
mp.msg.info("Catbox URL: " .. r.stdout)
|
|
|
|
|
-- Copy to clipboard
|
|
|
|
|
p.copy_or_open_url(r.stdout)
|
2021-07-10 22:21:49 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-09-19 23:46:12 +00:00
|
|
|
|
------------------------------------------------------------
|
2020-10-23 15:34:24 +00:00
|
|
|
|
-- Provides interface for creating audio/video clips
|
2020-09-19 23:46:12 +00:00
|
|
|
|
|
|
|
|
|
encoder = {}
|
|
|
|
|
|
2023-02-03 07:58:16 +00:00
|
|
|
|
function encoder.get_ext_subs_path()
|
2021-04-14 20:08:12 +00:00
|
|
|
|
local track_list = mp.get_property_native('track-list')
|
|
|
|
|
for _, track in pairs(track_list) do
|
2023-02-03 07:58:16 +00:00
|
|
|
|
if track.type == 'sub' and track.selected == true and track.external == true then
|
|
|
|
|
return track['external-filename']
|
2021-04-14 20:08:12 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function encoder.append_embed_subs_args(args)
|
2023-02-03 07:58:16 +00:00
|
|
|
|
local ext_subs_path = encoder.get_ext_subs_path()
|
|
|
|
|
if ext_subs_path then
|
|
|
|
|
table.insert(args, #args, table.concat { '--sub-files-append=', ext_subs_path, })
|
2021-04-14 20:08:12 +00:00
|
|
|
|
end
|
2023-02-03 07:58:16 +00:00
|
|
|
|
table.insert(args, #args, table.concat { '--sid=', ext_subs_path and 'auto' or mp.get_property("sid") })
|
2021-04-14 20:08:12 +00:00
|
|
|
|
table.insert(args, #args, table.concat { '--sub-delay=', mp.get_property("sub-delay") })
|
|
|
|
|
return args
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-14 22:17:31 +00:00
|
|
|
|
encoder.mkargs_video = function(clip_filename)
|
2020-10-23 13:22:43 +00:00
|
|
|
|
local clip_path = utils.join_path(config.video_folder_path, clip_filename .. config.video_extension)
|
2021-04-14 20:08:12 +00:00
|
|
|
|
local args = {
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'mpv',
|
|
|
|
|
mp.get_property('path'),
|
2020-09-26 22:35:36 +00:00
|
|
|
|
'--loop-file=no',
|
2023-06-22 18:07:36 +00:00
|
|
|
|
'--keep-open=no',
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'--no-ocopy-metadata',
|
|
|
|
|
'--no-sub',
|
2020-09-26 22:33:32 +00:00
|
|
|
|
'--audio-channels=2',
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'--oacopts-add=vbr=on',
|
|
|
|
|
'--oacopts-add=application=voip',
|
|
|
|
|
'--oacopts-add=compression_level=10',
|
2021-08-18 06:16:24 +00:00
|
|
|
|
'--vf-add=format=yuv420p',
|
2023-07-02 02:59:26 +00:00
|
|
|
|
'--sub-font-provider=auto',
|
|
|
|
|
'--embeddedfonts=yes',
|
2023-10-10 23:07:47 +00:00
|
|
|
|
table.concat { '--sub-font=', config.sub_font },
|
2020-10-23 13:22:43 +00:00
|
|
|
|
table.concat { '--ovc=', config.video_codec },
|
2021-07-08 09:44:04 +00:00
|
|
|
|
table.concat { '--oac=', config.audio_codec },
|
2020-10-23 13:34:23 +00:00
|
|
|
|
table.concat { '--start=', main_menu.timings['start'] },
|
|
|
|
|
table.concat { '--end=', main_menu.timings['end'] },
|
2021-07-08 09:44:04 +00:00
|
|
|
|
table.concat { '--aid=', mp.get_property("aid") }, -- track number
|
|
|
|
|
table.concat { '--mute=', mp.get_property("mute") },
|
2020-10-16 01:42:55 +00:00
|
|
|
|
table.concat { '--volume=', mp.get_property('volume') },
|
2020-10-23 15:34:45 +00:00
|
|
|
|
table.concat { '--ovcopts-add=b=', config.video_bitrate },
|
2020-09-19 23:46:12 +00:00
|
|
|
|
table.concat { '--oacopts-add=b=', config.audio_bitrate },
|
|
|
|
|
table.concat { '--ovcopts-add=crf=', config.video_quality },
|
|
|
|
|
table.concat { '--ovcopts-add=preset=', config.preset },
|
|
|
|
|
table.concat { '--vf-add=scale=', config.video_width, ':', config.video_height },
|
2021-04-14 16:11:15 +00:00
|
|
|
|
table.concat { '--ytdl-format=', mp.get_property("ytdl-format") },
|
2020-09-19 23:46:12 +00:00
|
|
|
|
table.concat { '-o=', clip_path }
|
2020-08-21 04:19:06 +00:00
|
|
|
|
}
|
2021-04-14 20:08:12 +00:00
|
|
|
|
|
2021-12-17 11:43:57 +00:00
|
|
|
|
if config.video_fps ~= 'auto' then
|
|
|
|
|
table.insert(args, #args, table.concat { '--vf-add=fps=', config.video_fps })
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-05 17:54:47 +00:00
|
|
|
|
if mp.get_property_bool("sub-visibility") == true then
|
2021-04-14 20:08:12 +00:00
|
|
|
|
args = encoder.append_embed_subs_args(args)
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-14 22:17:31 +00:00
|
|
|
|
return args
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2021-04-14 22:17:31 +00:00
|
|
|
|
encoder.mkargs_audio = function(clip_filename)
|
2021-07-10 20:56:35 +00:00
|
|
|
|
local clip_path = utils.join_path(config.audio_folder_path, clip_filename .. config.audio_extension)
|
2021-04-14 22:17:31 +00:00
|
|
|
|
return {
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'mpv',
|
|
|
|
|
mp.get_property('path'),
|
|
|
|
|
'--loop-file=no',
|
2023-06-22 18:07:36 +00:00
|
|
|
|
'--keep-open=no',
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'--no-ocopy-metadata',
|
|
|
|
|
'--no-sub',
|
2020-09-26 22:33:32 +00:00
|
|
|
|
'--audio-channels=2',
|
2020-09-26 22:35:36 +00:00
|
|
|
|
'--video=no',
|
2020-09-19 23:46:12 +00:00
|
|
|
|
'--oacopts-add=vbr=on',
|
|
|
|
|
'--oacopts-add=application=voip',
|
|
|
|
|
'--oacopts-add=compression_level=10',
|
2021-07-08 09:44:04 +00:00
|
|
|
|
table.concat { '--oac=', config.audio_codec },
|
2020-10-23 13:34:23 +00:00
|
|
|
|
table.concat { '--start=', main_menu.timings['start'] },
|
|
|
|
|
table.concat { '--end=', main_menu.timings['end'] },
|
2020-10-16 01:42:55 +00:00
|
|
|
|
table.concat { '--volume=', mp.get_property('volume') },
|
2020-09-19 23:46:12 +00:00
|
|
|
|
table.concat { '--aid=', mp.get_property("aid") }, -- track number
|
|
|
|
|
table.concat { '--oacopts-add=b=', config.audio_bitrate },
|
2021-04-14 16:11:15 +00:00
|
|
|
|
table.concat { '--ytdl-format=', mp.get_property("ytdl-format") },
|
2020-09-19 23:46:12 +00:00
|
|
|
|
table.concat { '-o=', clip_path }
|
2020-08-22 00:44:37 +00:00
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-18 13:20:57 +00:00
|
|
|
|
encoder.create_clip = function(clip_type, on_complete)
|
2020-10-23 13:34:23 +00:00
|
|
|
|
main_menu:close();
|
2020-09-19 23:46:12 +00:00
|
|
|
|
if clip_type == nil then
|
|
|
|
|
return
|
|
|
|
|
end
|
2020-08-22 00:44:37 +00:00
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
if not main_menu.timings:validate() then
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify("Wrong timings. Aborting.", "warn", 2)
|
2020-08-22 00:44:37 +00:00
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local clip_filename = construct_filename()
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify("Please wait...", "info", 9999)
|
2020-08-27 03:29:23 +00:00
|
|
|
|
|
2021-04-14 22:17:31 +00:00
|
|
|
|
local args
|
2020-08-27 07:12:30 +00:00
|
|
|
|
local location
|
2020-08-27 03:29:23 +00:00
|
|
|
|
|
2020-10-23 13:26:07 +00:00
|
|
|
|
if clip_type == 'video' then
|
2021-04-14 22:17:31 +00:00
|
|
|
|
args = encoder.mkargs_video(clip_filename)
|
2020-08-27 07:12:30 +00:00
|
|
|
|
location = config.video_folder_path
|
2020-10-23 07:26:32 +00:00
|
|
|
|
else
|
2021-04-14 22:17:31 +00:00
|
|
|
|
args = encoder.mkargs_audio(clip_filename)
|
2020-08-27 07:12:30 +00:00
|
|
|
|
location = config.audio_folder_path
|
2020-08-27 03:29:23 +00:00
|
|
|
|
end
|
|
|
|
|
|
2021-04-14 22:17:31 +00:00
|
|
|
|
local process_result = function(_, ret, _)
|
|
|
|
|
if ret.status ~= 0 or string.match(ret.stdout, "could not open") then
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify(string.format("Error: couldn't create the clip.\nDoes %s exist?", location), "error", 5)
|
2021-04-14 22:17:31 +00:00
|
|
|
|
else
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify(string.format("Clip saved to %s.", location), "info", 2)
|
2023-06-18 13:20:57 +00:00
|
|
|
|
if on_complete then
|
|
|
|
|
on_complete(utils.join_path(config.video_folder_path, clip_filename .. config.video_extension))
|
|
|
|
|
end
|
2021-04-14 22:17:31 +00:00
|
|
|
|
end
|
2020-08-22 00:44:37 +00:00
|
|
|
|
end
|
2021-04-14 22:17:31 +00:00
|
|
|
|
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.subprocess_async(args, process_result)
|
2020-10-23 13:34:23 +00:00
|
|
|
|
main_menu.timings:reset()
|
2020-08-22 00:44:37 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:10:29 +00:00
|
|
|
|
------------------------------------------------------------
|
|
|
|
|
-- Menu interface
|
|
|
|
|
|
|
|
|
|
local Menu = {}
|
|
|
|
|
Menu.__index = Menu
|
|
|
|
|
|
|
|
|
|
function Menu:new(parent)
|
|
|
|
|
local o = {
|
|
|
|
|
parent = parent,
|
|
|
|
|
overlay = parent and parent.overlay or mp.create_osd_overlay('ass-events'),
|
2020-10-23 13:39:53 +00:00
|
|
|
|
keybindings = { },
|
2020-10-23 13:10:29 +00:00
|
|
|
|
}
|
|
|
|
|
return setmetatable(o, self)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Menu:overlay_draw(text)
|
|
|
|
|
self.overlay.data = text
|
|
|
|
|
self.overlay:update()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Menu:open()
|
|
|
|
|
if self.parent then
|
|
|
|
|
self.parent:close()
|
|
|
|
|
end
|
2020-10-23 13:39:53 +00:00
|
|
|
|
for _, val in pairs(self.keybindings) do
|
2020-10-23 13:40:09 +00:00
|
|
|
|
mp.add_forced_key_binding(val.key, val.key, val.fn)
|
2020-10-23 13:10:29 +00:00
|
|
|
|
end
|
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Menu:close()
|
2020-10-23 13:39:53 +00:00
|
|
|
|
for _, val in pairs(self.keybindings) do
|
2020-10-23 13:10:29 +00:00
|
|
|
|
mp.remove_key_binding(val.key)
|
|
|
|
|
end
|
|
|
|
|
if self.parent then
|
|
|
|
|
self.parent:open()
|
|
|
|
|
else
|
|
|
|
|
self.overlay:remove()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Menu:update()
|
|
|
|
|
local osd = OSD:new():size(config.font_size):align(4)
|
|
|
|
|
osd:append('Dummy menu.'):newline()
|
|
|
|
|
self:overlay_draw(osd:get_text())
|
|
|
|
|
end
|
|
|
|
|
|
2020-08-21 04:19:06 +00:00
|
|
|
|
------------------------------------------------------------
|
2020-10-23 15:34:24 +00:00
|
|
|
|
-- Main menu
|
2020-08-21 04:19:06 +00:00
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
main_menu = Menu:new()
|
2020-09-20 00:01:38 +00:00
|
|
|
|
|
2020-10-23 13:39:53 +00:00
|
|
|
|
main_menu.keybindings = {
|
2020-10-23 13:34:23 +00:00
|
|
|
|
{ key = 's', fn = function() main_menu:set_time('start') end },
|
|
|
|
|
{ key = 'e', fn = function() main_menu:set_time('end') end },
|
|
|
|
|
{ key = 'S', fn = function() main_menu:set_time_sub('start') end },
|
|
|
|
|
{ key = 'E', fn = function() main_menu:set_time_sub('end') end },
|
|
|
|
|
{ key = 'r', fn = function() main_menu:reset_timings() end },
|
2020-09-28 10:40:07 +00:00
|
|
|
|
{ key = 'c', fn = function() encoder.create_clip('video') end },
|
2020-10-23 13:32:33 +00:00
|
|
|
|
{ key = 'C', fn = function() force_resolution(1920, -2, encoder.create_clip, 'video') end },
|
2020-09-28 10:40:07 +00:00
|
|
|
|
{ key = 'a', fn = function() encoder.create_clip('audio') end },
|
2023-06-25 19:23:02 +00:00
|
|
|
|
{ key = 'x', fn = function() main_menu:create_clip_and_upload_to_catbox() end },
|
|
|
|
|
{ key = 'X', fn = function() force_resolution(1920, -2, main_menu.create_clip_and_upload_to_catbox) end },
|
2020-10-23 13:32:33 +00:00
|
|
|
|
{ key = 'p', fn = function() pref_menu:open() end },
|
2023-06-25 19:23:02 +00:00
|
|
|
|
{ key = 'o', fn = function() p.open('https://streamable.com/') end },
|
2020-10-23 13:34:23 +00:00
|
|
|
|
{ key = 'ESC', fn = function() main_menu:close() end },
|
2020-08-21 04:19:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
function main_menu:set_time(property)
|
2023-06-17 12:59:39 +00:00
|
|
|
|
self.timings[property] = math.max(0, mp.get_property_number('time-pos'))
|
2020-10-23 13:32:33 +00:00
|
|
|
|
self:update()
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
function main_menu:set_time_sub(property)
|
2020-08-21 04:19:06 +00:00
|
|
|
|
local sub_delay = mp.get_property_native("sub-delay")
|
|
|
|
|
local time_pos = mp.get_property_number(string.format("sub-%s", property))
|
|
|
|
|
|
|
|
|
|
if time_pos == nil then
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify("Warning: No subtitles visible.", "warn", 2)
|
2020-08-21 04:19:06 +00:00
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-17 12:59:39 +00:00
|
|
|
|
self.timings[property] = math.max(0, time_pos + sub_delay)
|
2020-10-23 13:32:33 +00:00
|
|
|
|
self:update()
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
function main_menu:reset_timings()
|
2020-10-23 13:32:33 +00:00
|
|
|
|
self.timings = Timings:new()
|
|
|
|
|
self:update()
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
main_menu.open = function()
|
|
|
|
|
main_menu.timings = main_menu.timings or Timings:new()
|
|
|
|
|
Menu.open(main_menu)
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:34:23 +00:00
|
|
|
|
function main_menu:update()
|
2020-10-23 13:32:33 +00:00
|
|
|
|
local osd = OSD:new():size(config.font_size):align(4)
|
2021-04-14 20:58:06 +00:00
|
|
|
|
osd:submenu('Clip creator'):newline()
|
|
|
|
|
osd:tab():item('Start time: '):append(human_readable_time(self.timings['start'])):newline()
|
|
|
|
|
osd:tab():item('End time: '):append(human_readable_time(self.timings['end'])):newline()
|
|
|
|
|
osd:submenu('Timings '):italics('(+shift use sub timings)'):newline()
|
|
|
|
|
osd:tab():item('s: '):append('Set start'):newline()
|
|
|
|
|
osd:tab():item('e: '):append('Set end'):newline()
|
|
|
|
|
osd:tab():item('r: '):append('Reset'):newline()
|
|
|
|
|
osd:submenu('Create clip '):italics('(+shift to force fullHD preset)'):newline()
|
|
|
|
|
osd:tab():item('c: '):append('video clip'):newline()
|
|
|
|
|
osd:tab():item('a: '):append('audio clip'):newline()
|
2023-06-18 13:20:57 +00:00
|
|
|
|
osd:tab():item('x: '):append('video clip to ' .. (config.litterbox and 'litterbox.catbox.moe (' .. config.litterbox_expire .. ')' or 'catbox.moe')):newline()
|
2021-04-14 20:58:06 +00:00
|
|
|
|
osd:submenu('Options '):newline()
|
|
|
|
|
osd:tab():item('p: '):append('Open preferences'):newline()
|
|
|
|
|
osd:tab():item('o: '):append('Open streamable.com'):newline()
|
|
|
|
|
osd:tab():item('ESC: '):append('Close'):newline()
|
2020-10-23 13:32:33 +00:00
|
|
|
|
|
|
|
|
|
self:overlay_draw(osd:get_text())
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
2023-06-25 19:23:02 +00:00
|
|
|
|
function main_menu:create_clip_and_upload_to_catbox()
|
|
|
|
|
encoder.create_clip('video', upload_to_catbox)
|
2023-06-18 13:20:57 +00:00
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:33:10 +00:00
|
|
|
|
------------------------------------------------------------
|
|
|
|
|
-- Preferences
|
|
|
|
|
|
|
|
|
|
pref_menu = Menu:new(main_menu)
|
|
|
|
|
|
2020-10-23 13:39:53 +00:00
|
|
|
|
pref_menu.keybindings = {
|
2020-10-23 15:34:24 +00:00
|
|
|
|
{ key = 'f', fn = function() pref_menu:cycle_video_formats() end },
|
2021-07-08 09:44:04 +00:00
|
|
|
|
{ key = 'a', fn = function() pref_menu:cycle_audio_formats() end },
|
2020-10-23 13:33:10 +00:00
|
|
|
|
{ key = 'm', fn = function() pref_menu:toggle_mute_audio() end },
|
2020-10-23 15:34:24 +00:00
|
|
|
|
{ key = 'r', fn = function() pref_menu:cycle_resolutions() end },
|
2021-07-10 20:43:24 +00:00
|
|
|
|
{ key = 'b', fn = function() pref_menu:cycle_audio_bitrates() end },
|
2021-07-08 09:44:04 +00:00
|
|
|
|
{ key = 'e', fn = function() pref_menu:toggle_embed_subtitles() end },
|
2023-06-18 13:39:59 +00:00
|
|
|
|
{ key = 'x', fn = function() pref_menu:toggle_catbox() end },
|
|
|
|
|
{ key = 'z', fn = function() pref_menu:cycle_litterbox_expiration() end },
|
2021-07-10 22:28:48 +00:00
|
|
|
|
{ key = 's', fn = function() pref_menu:save() end },
|
2021-04-14 22:53:43 +00:00
|
|
|
|
{ key = 'c', fn = function() end },
|
2020-10-23 13:33:10 +00:00
|
|
|
|
{ key = 'ESC', fn = function() pref_menu:close() end },
|
2022-04-05 11:26:26 +00:00
|
|
|
|
{ key = 'q', fn = function() pref_menu:close() end },
|
2020-10-23 13:33:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 15:34:24 +00:00
|
|
|
|
pref_menu.resolutions = {
|
|
|
|
|
{ w = config.video_width, h = config.video_height, },
|
2021-12-07 20:54:38 +00:00
|
|
|
|
{ w = -2, h = -2, },
|
2020-10-23 15:34:24 +00:00
|
|
|
|
{ w = -2, h = 240, },
|
|
|
|
|
{ w = -2, h = 360, },
|
|
|
|
|
{ w = -2, h = 480, },
|
|
|
|
|
{ w = -2, h = 720, },
|
|
|
|
|
{ w = -2, h = 1080, },
|
|
|
|
|
{ w = -2, h = 1440, },
|
|
|
|
|
{ w = -2, h = 2160, },
|
|
|
|
|
selected = 1,
|
|
|
|
|
}
|
2021-07-10 20:43:24 +00:00
|
|
|
|
pref_menu.audio_bitrates = {
|
|
|
|
|
config.audio_bitrate,
|
|
|
|
|
'32k',
|
|
|
|
|
'64k',
|
|
|
|
|
'128k',
|
|
|
|
|
'256k',
|
|
|
|
|
'384k',
|
|
|
|
|
selected = 1,
|
|
|
|
|
}
|
2020-10-23 13:33:10 +00:00
|
|
|
|
|
2021-07-08 09:44:04 +00:00
|
|
|
|
pref_menu.vid_formats = { 'mp4', 'vp9', 'vp8', }
|
|
|
|
|
pref_menu.aud_formats = { 'aac', 'opus', }
|
2023-06-18 13:20:57 +00:00
|
|
|
|
pref_menu.litterbox_expirations = { '1h', '12h', '24h', '72h', }
|
2020-10-23 15:34:24 +00:00
|
|
|
|
|
|
|
|
|
function pref_menu:get_selected_resolution()
|
2023-06-25 19:23:02 +00:00
|
|
|
|
return string.format(
|
|
|
|
|
'%s x %s',
|
|
|
|
|
config.video_width == -2 and 'auto' or config.video_width,
|
|
|
|
|
config.video_height == -2 and 'auto' or config.video_height
|
|
|
|
|
)
|
2020-10-23 15:34:24 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function pref_menu:cycle_resolutions()
|
|
|
|
|
self.resolutions.selected = self.resolutions.selected + 1 > #self.resolutions and 1 or self.resolutions.selected + 1
|
|
|
|
|
local res = self.resolutions[self.resolutions.selected]
|
|
|
|
|
config.video_width = res.w
|
|
|
|
|
config.video_height = res.h
|
|
|
|
|
self:update()
|
2020-10-23 13:33:10 +00:00
|
|
|
|
end
|
|
|
|
|
|
2021-07-10 20:43:24 +00:00
|
|
|
|
function pref_menu:cycle_audio_bitrates()
|
|
|
|
|
self.audio_bitrates.selected = self.audio_bitrates.selected + 1 > #self.audio_bitrates and 1 or self.audio_bitrates.selected + 1
|
|
|
|
|
config.audio_bitrate = self.audio_bitrates[self.audio_bitrates.selected]
|
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-08 09:44:04 +00:00
|
|
|
|
function pref_menu:cycle_formats(config_type)
|
|
|
|
|
local formats
|
|
|
|
|
if config_type == 'video_format' then
|
|
|
|
|
formats = pref_menu.vid_formats
|
|
|
|
|
else
|
|
|
|
|
formats = pref_menu.aud_formats
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 15:34:24 +00:00
|
|
|
|
local selected = 1
|
2021-07-08 09:44:04 +00:00
|
|
|
|
for i, format in ipairs(formats) do
|
|
|
|
|
if config[config_type] == format then
|
2020-10-23 15:34:24 +00:00
|
|
|
|
selected = i
|
2021-07-08 09:44:04 +00:00
|
|
|
|
break
|
2020-10-23 15:34:24 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
2021-07-08 09:44:04 +00:00
|
|
|
|
config[config_type] = formats[selected + 1] or formats[1]
|
|
|
|
|
set_encoding_settings()
|
2020-10-23 13:33:10 +00:00
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-08 09:44:04 +00:00
|
|
|
|
function pref_menu:cycle_video_formats()
|
|
|
|
|
pref_menu:cycle_formats('video_format')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function pref_menu:cycle_audio_formats()
|
|
|
|
|
pref_menu:cycle_formats('audio_format')
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 13:33:10 +00:00
|
|
|
|
function pref_menu:toggle_mute_audio()
|
2021-07-08 09:44:04 +00:00
|
|
|
|
mp.commandv("cycle", "mute")
|
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function pref_menu:toggle_embed_subtitles()
|
|
|
|
|
mp.commandv("cycle", "sub-visibility")
|
2020-10-23 13:33:10 +00:00
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
2023-06-18 13:20:57 +00:00
|
|
|
|
function pref_menu:toggle_catbox()
|
|
|
|
|
config['litterbox'] = not config['litterbox']
|
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function pref_menu:cycle_litterbox_expiration()
|
|
|
|
|
if not config['litterbox'] then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
local expirations = pref_menu.litterbox_expirations
|
|
|
|
|
|
|
|
|
|
local selected = 1
|
|
|
|
|
for i, expiration in ipairs(expirations) do
|
|
|
|
|
if config['litterbox_expire'] == expiration then
|
|
|
|
|
selected = i
|
|
|
|
|
break
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
config['litterbox_expire'] = expirations[selected + 1] or expirations[1]
|
|
|
|
|
self:update()
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-23 15:34:24 +00:00
|
|
|
|
function pref_menu:update()
|
|
|
|
|
local osd = OSD:new():size(config.font_size):align(4)
|
2021-04-14 20:58:06 +00:00
|
|
|
|
osd:submenu('Preferences'):newline()
|
2021-07-08 09:44:04 +00:00
|
|
|
|
osd:tab():item('r: Video resolution: '):append(self:get_selected_resolution()):newline()
|
|
|
|
|
osd:tab():item('f: Video format: '):append(config.video_format):newline()
|
|
|
|
|
osd:tab():item('a: Audio format: '):append(config.audio_format):newline()
|
|
|
|
|
osd:tab():item('b: Audio bitrate: '):append(config.audio_bitrate):newline()
|
|
|
|
|
osd:tab():item('m: Mute audio: '):append(mp.get_property("mute")):newline()
|
|
|
|
|
osd:tab():item('e: Embed subtitles: '):append(mp.get_property("sub-visibility")):newline()
|
2023-06-18 13:20:57 +00:00
|
|
|
|
osd:submenu('Catbox'):newline()
|
2023-06-18 13:39:59 +00:00
|
|
|
|
osd:tab():item('x: Using: '):append(config.litterbox and 'Litterbox (temporary)' or 'Catbox (permanent)'):newline()
|
2023-06-18 13:20:57 +00:00
|
|
|
|
if config.litterbox then
|
2023-06-18 13:39:59 +00:00
|
|
|
|
osd:tab():item('z: Litterbox expires after: '):append(config.litterbox_expire):newline()
|
2023-06-18 13:20:57 +00:00
|
|
|
|
else
|
|
|
|
|
osd:tab():color("b0b0b0"):text('x: Litterbox expires after: '):append("N/A"):newline()
|
|
|
|
|
end
|
|
|
|
|
osd:submenu('Save'):newline()
|
2021-07-10 22:28:48 +00:00
|
|
|
|
osd:tab():item('s: Save preferences'):newline()
|
2020-10-23 15:34:24 +00:00
|
|
|
|
self:overlay_draw(osd:get_text())
|
|
|
|
|
end
|
|
|
|
|
|
2021-07-10 22:28:48 +00:00
|
|
|
|
function pref_menu:save()
|
2022-07-11 10:01:07 +00:00
|
|
|
|
local function lua_to_mpv(config_value)
|
|
|
|
|
if type(config_value) == 'boolean' then
|
|
|
|
|
return config_value and 'yes' or 'no'
|
|
|
|
|
else
|
|
|
|
|
return config_value
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-07-10 22:28:48 +00:00
|
|
|
|
local ignore_list = {
|
|
|
|
|
video_extension = true,
|
|
|
|
|
audio_extension = true,
|
|
|
|
|
video_codec = true,
|
|
|
|
|
audio_codec = true,
|
|
|
|
|
}
|
|
|
|
|
local mpv_dirpath = string.gsub(mp.get_script_directory(), "scripts/%w+", "")
|
2022-04-04 15:14:17 +00:00
|
|
|
|
local config_filepath = utils.join_path(mpv_dirpath, string.format('script-opts/%s.conf', NAME))
|
2021-07-10 22:28:48 +00:00
|
|
|
|
local handle = io.open(config_filepath, 'w')
|
|
|
|
|
if handle ~= nil then
|
2022-04-04 15:14:17 +00:00
|
|
|
|
handle:write(string.format("# Written by %s on %s.\n", NAME, os.date()))
|
2021-07-10 22:28:48 +00:00
|
|
|
|
for key, value in pairs(config) do
|
|
|
|
|
if ignore_list[key] == nil then
|
2022-07-11 10:01:07 +00:00
|
|
|
|
handle:write(string.format('%s=%s\n', key, lua_to_mpv(value)))
|
2021-07-10 22:28:48 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
handle:close()
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify("Settings saved.", "info", 2)
|
2021-07-10 22:28:48 +00:00
|
|
|
|
else
|
2023-06-25 19:23:02 +00:00
|
|
|
|
h.notify(string.format("Couldn't open %s.", config_filepath), "error", 4)
|
2021-07-10 22:28:48 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-08-21 04:19:06 +00:00
|
|
|
|
------------------------------------------------------------
|
|
|
|
|
-- Timings class
|
|
|
|
|
|
|
|
|
|
Timings = {
|
|
|
|
|
['start'] = -1,
|
|
|
|
|
['end'] = -1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Timings:new(o)
|
|
|
|
|
o = o or {}
|
|
|
|
|
setmetatable(o, self)
|
|
|
|
|
self.__index = self
|
|
|
|
|
return o
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Timings:reset()
|
|
|
|
|
self['start'] = -1
|
2020-09-19 23:47:10 +00:00
|
|
|
|
self['end'] = -1
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Timings:validate()
|
2020-09-06 12:58:47 +00:00
|
|
|
|
return self['start'] >= 0 and self['start'] < self['end']
|
2020-08-21 04:19:06 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
------------------------------------------------------------
|
|
|
|
|
-- Finally, set an 'entry point' in mpv
|
|
|
|
|
|
2020-10-24 09:02:32 +00:00
|
|
|
|
validate_config()
|
2020-10-23 13:34:23 +00:00
|
|
|
|
mp.add_key_binding('c', 'videoclip-menu-open', main_menu.open)
|
2023-09-10 14:02:07 +00:00
|
|
|
|
mp.msg.warn("Press 'c' to open the videoclip menu.")
|