summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
Diffstat (limited to 'player')
-rw-r--r--player/audio.c11
-rw-r--r--player/command.c52
-rw-r--r--player/configfiles.c2
-rw-r--r--player/core.h3
-rw-r--r--player/javascript/defaults.js11
-rw-r--r--player/loadfile.c62
-rw-r--r--player/lua.c3
-rw-r--r--player/lua/auto_profiles.lua8
-rw-r--r--player/lua/console.lua1162
-rw-r--r--player/lua/defaults.lua17
-rw-r--r--player/lua/input.lua5
-rw-r--r--player/lua/meson.build2
-rw-r--r--player/lua/options.lua22
-rw-r--r--player/lua/osc.lua1317
-rw-r--r--player/lua/select.lua386
-rw-r--r--player/lua/stats.lua277
-rw-r--r--player/lua/ytdl_hook.lua38
-rw-r--r--player/main.c2
-rw-r--r--player/misc.c25
-rw-r--r--player/osd.c10
-rw-r--r--player/playloop.c18
-rw-r--r--player/scripting.c1
-rw-r--r--player/sub.c14
-rw-r--r--player/video.c6
24 files changed, 2116 insertions, 1338 deletions
diff --git a/player/audio.c b/player/audio.c
index da91dd4340..e74add8cf8 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -612,13 +612,14 @@ double written_audio_pts(struct MPContext *mpctx)
return mpctx->ao_chain ? mpctx->ao_chain->last_out_pts : MP_NOPTS_VALUE;
}
-// Return pts value corresponding to currently playing audio.
+// Return pts value corresponding to currently playing audio adjusted for AO delay
+// and playback speed.
double playing_audio_pts(struct MPContext *mpctx)
{
double pts = written_audio_pts(mpctx);
if (pts == MP_NOPTS_VALUE || !mpctx->ao)
return pts;
- return pts - ao_get_delay(mpctx->ao);
+ return pts - mpctx->audio_speed * ao_get_delay(mpctx->ao);
}
// This garbage is needed for untimed AOs. These consume audio infinitely fast,
@@ -829,8 +830,7 @@ void audio_start_ao(struct MPContext *mpctx)
double pts = MP_NOPTS_VALUE;
if (!get_sync_pts(mpctx, &pts))
return;
- double apts = written_audio_pts(mpctx);
- apts -= apts != MP_NOPTS_VALUE ? mpctx->audio_speed * ao_get_delay(mpctx->ao) : 0;
+ double apts = playing_audio_pts(mpctx);
if (pts != MP_NOPTS_VALUE && apts != MP_NOPTS_VALUE && pts < apts &&
mpctx->video_status != STATUS_EOF)
{
@@ -967,8 +967,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (mpctx->audio_status == STATUS_DRAINING) {
// Wait until the AO has played all queued data. In the gapless case,
// we trigger EOF immediately, and let it play asynchronously.
- if (!ao_c->ao || (!ao_is_playing(ao_c->ao) ||
- (opts->gapless_audio && !ao_untimed(ao_c->ao))))
+ if (!ao_c->ao || (!ao_is_playing(ao_c->ao) || opts->gapless_audio))
{
MP_VERBOSE(mpctx, "audio EOF reached\n");
mpctx->audio_status = STATUS_EOF;
diff --git a/player/command.c b/player/command.c
index 65469988d9..37753480ef 100644
--- a/player/command.c
+++ b/player/command.c
@@ -388,7 +388,7 @@ static char *cut_osd_list(struct MPContext *mpctx, char *text, int pos)
static char *format_delay(double time)
{
- return talloc_asprintf(NULL, "%d ms", (int)lrint(time * 1000));
+ return talloc_asprintf(NULL, "%.f ms", time * 1000);
}
// Property-option bridge. (Maps the property to the option with the same name.)
@@ -791,10 +791,10 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop,
};
return M_PROPERTY_OK;
case M_PROPERTY_PRINT: {
- int pos = get_percent_pos(mpctx);
+ double pos = get_current_pos_ratio(mpctx, false);
if (pos < 0)
return M_PROPERTY_UNAVAILABLE;
- *(char **)arg = talloc_asprintf(NULL, "%d", pos);
+ *(char **)arg = talloc_asprintf(NULL, "%.f", pos * 100);
return M_PROPERTY_OK;
}
}
@@ -820,7 +820,7 @@ static int mp_property_time_pos(void *ctx, struct m_property *prop,
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0);
return M_PROPERTY_OK;
}
- return property_time(action, arg, get_current_time(mpctx));
+ return property_time(action, arg, get_playback_time(mpctx));
}
/// Current audio pts in seconds (R)
@@ -870,20 +870,6 @@ static int mp_property_playtime_remaining(void *ctx, struct m_property *prop,
return property_time(action, arg, remaining / speed);
}
-static int mp_property_playback_time(void *ctx, struct m_property *prop,
- int action, void *arg)
-{
- MPContext *mpctx = ctx;
- if (!mpctx->playback_initialized)
- return M_PROPERTY_UNAVAILABLE;
-
- if (action == M_PROPERTY_SET) {
- queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0);
- return M_PROPERTY_OK;
- }
- return property_time(action, arg, get_playback_time(mpctx));
-}
-
/// Current chapter (RW)
static int mp_property_chapter(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -1670,7 +1656,7 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
};
return M_PROPERTY_OK;
case M_PROPERTY_PRINT:
- *(char **)arg = talloc_asprintf(NULL, "%i", (int)opts->softvol_volume);
+ *(char **)arg = talloc_asprintf(NULL, "%.f", opts->softvol_volume);
return M_PROPERTY_OK;
}
@@ -3262,8 +3248,10 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
for (int n = 0; n < pl->num_entries; n++) {
struct playlist_entry *e = pl->entries[n];
+ res = talloc_strdup_append(res, pl->current == e ? list_current
+ : list_normal);
char *p = e->title;
- if (!p) {
+ if (!p || mpctx->opts->playlist_entry_name > 0) {
p = e->filename;
if (!mp_is_url(bstr0(p))) {
char *s = mp_basename(e->filename);
@@ -3271,8 +3259,11 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
p = s;
}
}
- const char *m = pl->current == e ? list_current : list_normal;
- res = talloc_asprintf_append(res, "%s%s\n", m, p);
+ if (!e->title || p == e->title || mpctx->opts->playlist_entry_name == 1) {
+ res = talloc_asprintf_append(res, "%s\n", p);
+ } else {
+ res = talloc_asprintf_append(res, "%s (%s)\n", e->title, p);
+ }
}
*(char **)arg =
@@ -3372,7 +3363,7 @@ static int mp_property_packet_bitrate(void *ctx, struct m_property *prop,
if (action == M_PROPERTY_PRINT) {
rate /= 1000;
if (rate < 1000) {
- *(char **)arg = talloc_asprintf(NULL, "%d kbps", (int)rate);
+ *(char **)arg = talloc_asprintf(NULL, "%.f kbps", rate);
} else {
*(char **)arg = talloc_asprintf(NULL, "%.3f Mbps", rate / 1000.0);
}
@@ -3650,6 +3641,7 @@ static int mp_property_option_info(void *ctx, struct m_property *prop,
{"type", SUB_PROP_STR(opt->type->name)},
{"set-from-commandline", SUB_PROP_BOOL(co->is_set_from_cmdline)},
{"set-locally", SUB_PROP_BOOL(co->is_set_locally)},
+ {"expects-file", SUB_PROP_BOOL(opt->flags & M_OPT_FILE)},
{"default-value", *opt, def},
{"min", SUB_PROP_DOUBLE(opt->min),
.unavailable = !(has_minmax && opt->min != DBL_MIN)},
@@ -4003,7 +3995,7 @@ static const struct m_property mp_properties_base[] = {
{"time-remaining", mp_property_remaining},
{"audio-pts", mp_property_audio_pts},
{"playtime-remaining", mp_property_playtime_remaining},
- {"playback-time", mp_property_playback_time},
+ M_PROPERTY_ALIAS("playback-time", "time-pos"),
{"chapter", mp_property_chapter},
{"edition", mp_property_edition},
{"current-edition", mp_property_current_edition},
@@ -6341,7 +6333,8 @@ static void cmd_script_binding(void *p)
target = space;
name = sep + 1;
}
- char state[3] = {'p', incmd->is_mouse_button ? 'm' : '-'};
+ char state[4] = {'p', incmd->is_mouse_button ? 'm' : '-',
+ incmd->canceled ? 'c' : '-'};
if (incmd->is_up_down)
state[0] = incmd->repeated ? 'r' : (incmd->is_up ? 'u' : 'd');
event.num_args = 5;
@@ -7440,16 +7433,11 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
if (opt_ptr == &opts->play_dir) {
if (mpctx->play_dir != opts->play_dir) {
- // Some weird things for play_dir if we're at EOF.
- // 1. The option must be set before we seek.
- // 2. queue_seek can change the stop_play value; always keep the old one.
- int old_stop_play = mpctx->stop_play;
- if (old_stop_play == AT_END_OF_FILE)
+ // The option must be set before we seek if we're at EOF.
+ if (mpctx->stop_play == AT_END_OF_FILE)
mpctx->play_dir = opts->play_dir;
queue_seek(mpctx, MPSEEK_ABSOLUTE, get_current_time(mpctx),
MPSEEK_EXACT, 0);
- if (old_stop_play == AT_END_OF_FILE)
- mpctx->stop_play = old_stop_play;
}
}
diff --git a/player/configfiles.c b/player/configfiles.c
index 5eaf5b197e..3ecb5a19c8 100644
--- a/player/configfiles.c
+++ b/player/configfiles.c
@@ -335,7 +335,7 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
write_filename(mpctx, file, path);
bool write_start = true;
- double pos = get_current_time(mpctx);
+ double pos = get_playback_time(mpctx);
if ((demux && (!demux->seekable || demux->partially_seekable)) ||
pos == MP_NOPTS_VALUE)
diff --git a/player/core.h b/player/core.h
index c44868cecd..35d6348e39 100644
--- a/player/core.h
+++ b/player/core.h
@@ -436,7 +436,7 @@ typedef struct MPContext {
struct mp_ipc_ctx *ipc_ctx;
- int64_t builtin_script_ids[5];
+ int64_t builtin_script_ids[6];
mp_mutex abort_lock;
@@ -591,7 +591,6 @@ double get_time_length(struct MPContext *mpctx);
double get_start_time(struct MPContext *mpctx, int dir);
double get_current_time(struct MPContext *mpctx);
double get_playback_time(struct MPContext *mpctx);
-int get_percent_pos(struct MPContext *mpctx);
double get_current_pos_ratio(struct MPContext *mpctx, bool use_range);
int get_current_chapter(struct MPContext *mpctx);
char *chapter_display_name(struct MPContext *mpctx, int chapter);
diff --git a/player/javascript/defaults.js b/player/javascript/defaults.js
index 9150cc8fa3..e49bc80614 100644
--- a/player/javascript/defaults.js
+++ b/player/javascript/defaults.js
@@ -311,6 +311,7 @@ function add_binding(forced, key, name, fn, opts) {
fn({
event: KEY_STATES[state[0]] || "unknown",
is_mouse: state[1] == "m",
+ canceled: state[2] == "c",
key_name: key_name || undefined,
key_text: key_text || undefined
});
@@ -321,7 +322,10 @@ function add_binding(forced, key, name, fn, opts) {
// Emulate the semantics at input.c: mouse emits on up, kb on down.
// Also, key repeat triggers the binding again.
var e = state[0],
- emit = (state[1] == "m") ? (e == "u") : (e == "d");
+ emit = (state[1] == "m") ? (e == "u") : (e == "d"),
+ canceled = state[2] == "c";
+ if (canceled)
+ return;
if (emit || e == "p" || e == "r" && key_data.repeatable)
fn();
}
@@ -647,9 +651,10 @@ mp.options = { read_options: read_options };
* input
*********************************************************************/
function register_event_handler(t) {
- mp.register_script_message("input-event", function (type, text, cursor_position) {
+ mp.register_script_message("input-event", function (type, args) {
if (t[type]) {
- var result = t[type](text, cursor_position);
+ args = JSON.parse(args)
+ var result = t[type](args[0], args[1]);
if (type == "complete" && result) {
mp.commandv("script-message-to", "console", "complete",
diff --git a/player/loadfile.c b/player/loadfile.c
index 95afd13550..f12ee95a80 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -235,7 +235,10 @@ static void uninit_demuxer(struct MPContext *mpctx)
talloc_free(demuxers);
}
+#define BLACK_CIRCLE "\xe2\x97\x8f"
+#define WHITE_CIRCLE "\xe2\x97\x8b"
#define APPEND(s, ...) mp_snprintf_cat(s, sizeof(s), __VA_ARGS__)
+#define FILL(s, n) mp_snprintf_cat(s, sizeof(s), "%*s", n, "")
static void print_stream(struct MPContext *mpctx, struct track *t)
{
@@ -245,7 +248,7 @@ static void print_stream(struct MPContext *mpctx, struct track *t)
const char *langopt = "?";
switch (t->type) {
case STREAM_VIDEO:
- tname = "Video"; selopt = "vid"; langopt = NULL;
+ tname = "Video"; selopt = "vid"; langopt = "vlang";
break;
case STREAM_AUDIO:
tname = "Audio"; selopt = "aid"; langopt = "alang";
@@ -255,24 +258,32 @@ static void print_stream(struct MPContext *mpctx, struct track *t)
break;
}
char b[2048] = {0};
- bool forced_only = false;
- if (t->type == STREAM_SUB) {
- bool forced_opt = mpctx->opts->subs_rend->sub_forced_events_only;
- if (forced_opt)
- forced_only = t->selected;
- }
- APPEND(b, " %3s %-5s", t->selected ? (forced_only ? "(*)" : "(+)") : "", tname);
- APPEND(b, " --%s=%d", selopt, t->user_tid);
- if (t->lang && langopt)
- APPEND(b, " --%s=%s", langopt, t->lang);
+
+ bool tracks_have_lang = false;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ if (mpctx->tracks[n]->lang) {
+ tracks_have_lang = true;
+ break;
+ }
+ }
+
+ if (!isatty(STDOUT_FILENO)) {
+ APPEND(b, "%s ", t->selected ? BLACK_CIRCLE : WHITE_CIRCLE);
+ } else if (!t->selected) {
+ APPEND(b, "%s", TERM_ESC_GREY);
+ }
+ APPEND(b, "%-5s --%s=%-2d", tname, selopt, t->user_tid);
+ if (t->lang) {
+ APPEND(b, " --%s=%-7s", langopt, t->lang);
+ } else if (tracks_have_lang) {
+ FILL(b, 16);
+ }
if (t->default_track)
APPEND(b, " (*)");
if (t->forced_track)
APPEND(b, " (f)");
if (t->attached_picture)
APPEND(b, " [P]");
- if (forced_only)
- APPEND(b, " [F]");
if (t->title)
APPEND(b, " '%s'", t->title);
const char *codec = s ? s->codec->codec : NULL;
@@ -282,13 +293,20 @@ static void print_stream(struct MPContext *mpctx, struct track *t)
if (t->type == STREAM_VIDEO) {
if (s && s->codec->disp_w)
APPEND(b, " %dx%d", s->codec->disp_w, s->codec->disp_h);
- if (s && s->codec->fps)
- APPEND(b, " %.3ffps", s->codec->fps);
+ if (s && s->codec->fps && !t->image) {
+ char *fps = mp_format_double(NULL, s->codec->fps, 4, false, false, true);
+ APPEND(b, " %s fps", fps);
+ talloc_free(fps);
+ }
} else if (t->type == STREAM_AUDIO) {
if (s && s->codec->channels.num)
- APPEND(b, " %dch", s->codec->channels.num);
- if (s && s->codec->samplerate)
- APPEND(b, " %dHz", s->codec->samplerate);
+ APPEND(b, " %d ch", s->codec->channels.num);
+ if (s && s->codec->samplerate) {
+ char *samplerate = mp_format_double(NULL, s->codec->samplerate / 1000.0,
+ 4, false, false, true);
+ APPEND(b, " %s kHz", samplerate);
+ talloc_free(samplerate);
+ }
}
APPEND(b, ")");
if (s && s->hls_bitrate > 0)
@@ -320,8 +338,12 @@ void update_demuxer_properties(struct MPContext *mpctx)
for (int n = 0; n < demuxer->num_editions; n++) {
struct demux_edition *edition = &demuxer->editions[n];
char b[128] = {0};
- APPEND(b, " %3s --edition=%d",
- n == demuxer->edition ? "(+)" : "", n);
+ if (!isatty(STDOUT_FILENO)) {
+ APPEND(b, "%s ", n == demuxer->edition ? BLACK_CIRCLE : WHITE_CIRCLE);
+ } else if (n != demuxer->edition) {
+ APPEND(b, "%s", TERM_ESC_GREY);
+ }
+ APPEND(b, "--edition=%d", n);
char *name = mp_tags_get_str(edition->metadata, "title");
if (name)
APPEND(b, " '%s'", name);
diff --git a/player/lua.c b/player/lua.c
index d51ea5c8a7..1be3428b99 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -82,6 +82,9 @@ static const char * const builtin_lua_scripts[][2] = {
{"@auto_profiles.lua",
# include "player/lua/auto_profiles.lua.inc"
},
+ {"@select.lua",
+# include "player/lua/select.lua.inc"
+ },
{0}
};
diff --git a/player/lua/auto_profiles.lua b/player/lua/auto_profiles.lua
index a0f580298b..167724344c 100644
--- a/player/lua/auto_profiles.lua
+++ b/player/lua/auto_profiles.lua
@@ -1,6 +1,5 @@
-- Note: anything global is accessible by profile condition expressions.
-local utils = require 'mp.utils'
local msg = require 'mp.msg'
local profiles = {}
@@ -128,7 +127,7 @@ end
local evil_magic = {}
setmetatable(evil_magic, {
- __index = function(table, key)
+ __index = function(_, key)
-- interpret everything as property, unless it already exists as
-- a non-nil global value
local v = _G[key]
@@ -141,7 +140,7 @@ setmetatable(evil_magic, {
p = {}
setmetatable(p, {
- __index = function(table, key)
+ __index = function(_, key)
return magic_get(key)
end,
})
@@ -149,6 +148,8 @@ setmetatable(p, {
local function compile_cond(name, s)
local code, chunkname = "return " .. s, "profile " .. name .. " condition"
local chunk, err
+ -- luacheck: push
+ -- luacheck: ignore setfenv loadstring
if setfenv then -- lua 5.1
chunk, err = loadstring(code, chunkname)
if chunk then
@@ -157,6 +158,7 @@ local function compile_cond(name, s)
else -- lua 5.2
chunk, err = load(code, chunkname, "t", evil_magic)
end
+ -- luacheck: pop
if not chunk then
msg.error("Profile '" .. name .. "' condition: " .. err)
chunk = function() return false end
diff --git a/player/lua/console.lua b/player/lua/console.lua
index f29901c35a..686d04ff3b 100644
--- a/player/lua/console.lua
+++ b/player/lua/console.lua
@@ -36,7 +36,7 @@ local opts = {
font_hw_ratio = 'auto',
}
-function detect_platform()
+local function detect_platform()
local platform = mp.get_property_native('platform')
if platform == 'darwin' or platform == 'windows' then
return platform
@@ -107,9 +107,8 @@ local input_caller
local suggestion_buffer = {}
local selected_suggestion_index
-local completion_start_position
+local completion_pos
local completion_append
-local file_commands = {}
local path_separator = platform == 'windows' and '\\' or '/'
local completion_old_line
local completion_old_cursor
@@ -119,123 +118,133 @@ local matches = {}
local selected_match = 1
local first_match_to_print = 1
-local update_timer = nil
-update_timer = mp.add_periodic_timer(0.05, function()
- if pending_update then
- update()
- else
- update_timer:kill()
- end
-end)
-update_timer:kill()
+local set_active
-mp.observe_property("user-data/osc/margins", "native", function(_, val)
- if val then
- global_margins = val
- else
- global_margins = { t = 0, b = 0 }
- end
- update()
-end)
-do
- local width_length_ratio = 0.5
- local osd_width, osd_height = 100, 100
+-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
+-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
+local function next_utf8(str, pos)
+ if pos > str:len() then return pos end
+ repeat
+ pos = pos + 1
+ until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
- ---Update osd resolution if valid
- local function update_osd_resolution()
- local dim = mp.get_property_native('osd-dimensions')
- if not dim or dim.w == 0 or dim.h == 0 then
- return
- end
- osd_width = dim.w
- osd_height = dim.h
- end
+-- As above, but finds the previous UTF-8 character in 'str' before 'pos'
+local function prev_utf8(str, pos)
+ if pos <= 1 then return pos end
+ repeat
+ pos = pos - 1
+ until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
+ return pos
+end
- local text_osd = mp.create_osd_overlay('ass-events')
- text_osd.compute_bounds, text_osd.hidden = true, true
+local function len_utf8(str)
+ local len = 0
+ local pos = 1
+ while pos <= str:len() do
+ pos = next_utf8(str, pos)
+ len = len + 1
+ end
+ return len
+end
- local function measure_bounds(ass_text)
- update_osd_resolution()
- text_osd.res_x, text_osd.res_y = osd_width, osd_height
- text_osd.data = ass_text
- local res = text_osd:update()
- return res.x0, res.y0, res.x1, res.y1
- end
-
- ---Measure text width and normalize to a font size of 1
- ---text has to be ass safe
- local function normalized_text_width(text, size, horizontal)
- local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90
- local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s'
- local x1, y1 = nil, nil
- size = size / 0.8
- -- prevent endless loop
- local repetitions_left = 5
- repeat
- size = size * 0.8
- local ass = assdraw.ass_new()
- ass.text = template:format(align, size, opts.font, rotation, text)
- _, _, x1, y1 = measure_bounds(ass.text)
- repetitions_left = repetitions_left - 1
- -- make sure nothing got clipped
- until (x1 and x1 < osd_width and y1 < osd_height) or repetitions_left == 0
- local width = (repetitions_left == 0 and not x1) and 0 or (horizontal and x1 or y1)
- return width / size, horizontal and osd_width or osd_height
- end
-
- local function fit_on_osd(text)
- local estimated_width = #text * width_length_ratio
- if osd_width >= osd_height then
- -- Fill the osd as much as possible, bigger is more accurate.
- return math.min(osd_width / estimated_width, osd_height), true
- else
- return math.min(osd_height / estimated_width, osd_width), false
+local function truncate_utf8(str, max_length)
+ local len = 0
+ local pos = 1
+ while pos <= #str do
+ pos = next_utf8(str, pos)
+ len = len + 1
+ if len == max_length - 1 then
+ return str:sub(1, pos - 1) .. '⋯'
end
end
+ return str
+end
- local measured_font_hw_ratio = nil
- function get_font_hw_ratio()
- local font_hw_ratio = tonumber(opts.font_hw_ratio)
- if font_hw_ratio then
- return font_hw_ratio
+
+-- Functions to calculate the font width.
+local width_length_ratio = 0.5
+local osd_width, osd_height = 100, 100
+
+---Update osd resolution if valid
+local function update_osd_resolution()
+ local dim = mp.get_property_native('osd-dimensions')
+ if not dim or dim.w == 0 or dim.h == 0 then
+ return
+ end
+ osd_width = dim.w
+ osd_height = dim.h
+end
+
+local text_osd = mp.create_osd_overlay('ass-events')
+text_osd.compute_bounds, text_osd.hidden = true, true
+
+local function measure_bounds(ass_text)
+ update_osd_resolution()
+ text_osd.res_x, text_osd.res_y = osd_width, osd_height
+ text_osd.data = ass_text
+ local res = text_osd:update()
+ return res.x0, res.y0, res.x1, res.y1
+end
+
+---Measure text width and normalize to a font size of 1
+---text has to be ass safe
+local function normalized_text_width(text, size, horizontal)
+ local align, rotation = horizontal and 7 or 1, horizontal and 0 or -90
+ local template = '{\\pos(0,0)\\rDefault\\blur0\\bord0\\shad0\\q2\\an%s\\fs%s\\fn%s\\frz%s}%s'
+ size = size / 0.8
+ local width
+ -- Limit to 5 iterations
+ local repetitions_left = 5
+ for i = 1, repetitions_left do
+ size = size * 0.8
+ local ass = assdraw.ass_new()
+ ass.text = template:format(align, size, opts.font, rotation, text)
+ local _, _, x1, y1 = measure_bounds(ass.text)
+ -- Check if nothing got clipped
+ if x1 and x1 < osd_width and y1 < osd_height then
+ width = horizontal and x1 or y1
+ break
end
- if not measured_font_hw_ratio then
- local alphabet = 'abcdefghijklmnopqrstuvwxyz'
- local text = alphabet:rep(3)
- update_osd_resolution()
- local size, horizontal = fit_on_osd(text)
- local normalized_width = normalized_text_width(text, size * 0.9, horizontal)
- measured_font_hw_ratio = #text / normalized_width * 0.95
+ if i == repetitions_left then
+ width = 0
end
- return measured_font_hw_ratio
end
+ return width / size, horizontal and osd_width or osd_height
end
--- Add a line to the log buffer (which is limited to 100 lines)
-function log_add(text, style, terminal_style)
- local log_buffer = log_buffers[id]
- log_buffer[#log_buffer + 1] = {
- text = text,
- style = style or '',
- terminal_style = terminal_style or '',
- }
- if #log_buffer > 100 then
- table.remove(log_buffer, 1)
+local function fit_on_osd(text)
+ local estimated_width = #text * width_length_ratio
+ if osd_width >= osd_height then
+ -- Fill the osd as much as possible, bigger is more accurate.
+ return math.min(osd_width / estimated_width, osd_height), true
+ else
+ return math.min(osd_height / estimated_width, osd_width), false
end
+end
- if repl_active then
- if not update_timer:is_enabled() then
- update()
- update_timer:resume()
- else
- pending_update = true
- end
+local measured_font_hw_ratio = nil
+local function get_font_hw_ratio()
+ local font_hw_ratio = tonumber(opts.font_hw_ratio)
+ if font_hw_ratio then
+ return font_hw_ratio
+ end
+ if not measured_font_hw_ratio then
+ local alphabet = 'abcdefghijklmnopqrstuvwxyz'
+ local text = alphabet:rep(3)
+ update_osd_resolution()
+ local size, horizontal = fit_on_osd(text)
+ local normalized_width = normalized_text_width(text, size * 0.9, horizontal)
+ measured_font_hw_ratio = #text / normalized_width * 0.95
end
+ return measured_font_hw_ratio
end
+
-- Escape a string for verbatim display on the OSD
-function ass_escape(str)
+local function ass_escape(str)
return mp.command_native({'escape-ass', str})
end
@@ -247,13 +256,14 @@ local function calculate_max_log_lines()
select(2, mp.get_property('term-status-msg'):gsub('\\n', ''))
end
- -- Subtract 1.5 to account for the input line.
return math.floor(mp.get_property_native('osd-height')
/ mp.get_property_native('display-hidpi-scale', 1)
/ opts.scale
* (1 - global_margins.t - global_margins.b)
/ opts.font_size
- - 1.5)
+ -- Subtract 1 for the input line and 1 for the newline
+ -- between the log and the input line.
+ - 2)
end
-- Takes a list of strings, a max width in characters and
@@ -261,7 +271,7 @@ end
-- The result contains at least one column.
-- Rows are cut off from the top if rows_max is specified.
-- returns a string containing the formatted table and the row count
-function format_table(list, width_max, rows_max)
+local function format_table(list, width_max, rows_max)
if #list == 0 then
return '', 0
end
@@ -283,9 +293,10 @@ function format_table(list, width_max, rows_max)
-- use as many columns as possible
for columns = 2, list_size do
local rows_lower_bound = math.min(rows_max, math.ceil(list_size / columns))
- local rows_upper_bound = math.min(rows_max, list_size, math.ceil(list_size / (columns - 1) - 1))
+ local rows_upper_bound = math.min(rows_max, list_size,
+ math.ceil(list_size / (columns - 1) - 1))
for rows = rows_upper_bound, rows_lower_bound, -1 do
- cw = {}
+ local cw = {}
width_total = 0
-- find out width of each column
@@ -358,23 +369,64 @@ local function fuzzy_find(needle, haystacks)
return result
end
-local function populate_log_with_matches()
- if not selectable_items then
+local function populate_log_with_matches(max_width)
+ if not selectable_items or selected_match == 0 then
return
end
log_buffers[id] = {}
local log = log_buffers[id]
- -- Subtract 2 for the "(n hidden items)" lines.
- local max_log_lines = calculate_max_log_lines() - 2
+ local max_log_lines = calculate_max_log_lines()
if selected_match < first_match_to_print then
first_match_to_print = selected_match
- elseif selected_match > first_match_to_print + max_log_lines - 1 then
+ end
+
+ if first_match_to_print > 1 then
+ -- Reserve the first line for "n hidden items".
+ max_log_lines = max_log_lines - 1
+ end
+
+ if selected_match > first_match_to_print + max_log_lines - 1 then
+ -- Reserve the first line for "n hidden items" if it wasn't already.
+ if first_match_to_print == 1 then
+ max_log_lines = max_log_lines - 1
+ end
+
first_match_to_print = selected_match - max_log_lines + 1
end
+ local last_match_to_print = math.min(first_match_to_print + max_log_lines - 1,
+ #matches)
+
+ if last_match_to_print < #matches then
+ -- Reserve the last line for "n hidden items".
+ last_match_to_print = last_match_to_print - 1
+
+ -- After decrementing the last match to print, we need to check if the
+ -- selected match is beyond the last match to print again, and shift
+ -- both the first and last match to print when it is.
+ if selected_match > last_match_to_print then
+ if first_match_to_print == 1 then
+ -- Reserve the first line for "2 hidden items".
+ first_match_to_print = first_match_to_print + 1
+ end
+
+ first_match_to_print = first_match_to_print + 1
+ last_match_to_print = last_match_to_print + 1
+ end
+ end
+
+ -- When there is only 1 hidden item, print it in the previously reserved
+ -- line instead of printing "1 hidden items".
+ if first_match_to_print == 2 then
+ first_match_to_print = 1
+ end
+ if last_match_to_print == #matches - 1 then
+ last_match_to_print = #matches
+ end
+
if first_match_to_print > 1 then
log[1] = {
text = '↑ (' .. (first_match_to_print - 1) .. ' hidden items)' .. '\n',
@@ -383,12 +435,9 @@ local function populate_log_with_matches()
}