diff options
Diffstat (limited to 'player')
-rw-r--r-- | player/audio.c | 11 | ||||
-rw-r--r-- | player/command.c | 52 | ||||
-rw-r--r-- | player/configfiles.c | 2 | ||||
-rw-r--r-- | player/core.h | 3 | ||||
-rw-r--r-- | player/javascript/defaults.js | 11 | ||||
-rw-r--r-- | player/loadfile.c | 62 | ||||
-rw-r--r-- | player/lua.c | 3 | ||||
-rw-r--r-- | player/lua/auto_profiles.lua | 8 | ||||
-rw-r--r-- | player/lua/console.lua | 1162 | ||||
-rw-r--r-- | player/lua/defaults.lua | 17 | ||||
-rw-r--r-- | player/lua/input.lua | 5 | ||||
-rw-r--r-- | player/lua/meson.build | 2 | ||||
-rw-r--r-- | player/lua/options.lua | 22 | ||||
-rw-r--r-- | player/lua/osc.lua | 1317 | ||||
-rw-r--r-- | player/lua/select.lua | 386 | ||||
-rw-r--r-- | player/lua/stats.lua | 277 | ||||
-rw-r--r-- | player/lua/ytdl_hook.lua | 38 | ||||
-rw-r--r-- | player/main.c | 2 | ||||
-rw-r--r-- | player/misc.c | 25 | ||||
-rw-r--r-- | player/osd.c | 10 | ||||
-rw-r--r-- | player/playloop.c | 18 | ||||
-rw-r--r-- | player/scripting.c | 1 | ||||
-rw-r--r-- | player/sub.c | 14 | ||||
-rw-r--r-- | player/video.c | 6 |
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() } |