From 7a76b577d85ddc8f9e255b1a1c195ee88b76a7d8 Mon Sep 17 00:00:00 2001 From: wm4 Date: Fri, 6 Mar 2020 18:20:11 +0100 Subject: command: extend osd-overlay command with bounds reporting This is more or less a minimal hack to make _some_ text measurement functionality available to scripts. Since libass does not support such a thing, this simply uses the bounding box of the rendered text. This is far from ideal. Problems include: - using a bitmap bounding box - additional memory waste and/or flushing caches - dependency on window size - odd small deviations with different window sizes (run osd-test.lua and resize the window after each timer update; the bounding boxes aren't adjusted in an overly useful way) - inability to query the size _after_ actual rendering But I guess it's a start. Since I'm aware that it's crap, add a threat to the manpage that this may be changed/removed again. For now, I'm interested whether anyone will have use for it in its current form, as it's an often requested feature. --- DOCS/man/input.rst | 25 ++++++++++++++++++++++ DOCS/man/lua.rst | 1 + TOOLS/lua/osd-test.lua | 35 ++++++++++++++++++++++++++++++ player/command.c | 17 +++++++++++++++ player/lua/defaults.lua | 2 +- sub/ass_mp.c | 25 ++++++++++++++++++++++ sub/ass_mp.h | 2 ++ sub/osd.h | 3 +++ sub/osd_libass.c | 57 +++++++++++++++++++++++++++++++++++++++++++------ sub/osd_state.h | 2 ++ 10 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 TOOLS/lua/osd-test.lua diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index a6f77dff0a..651ae7f645 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -1019,6 +1019,31 @@ Input Commands that are Possibly Subject to Change It's possible that future mpv versions will randomly change how Z order between different OSD formats and builtin OSD is handled. + ``hidden`` + If set to ``yes``/true, do not display this (default: no). + + ``compute_bounds`` + If set to ``yes``/true, attempt to determine bounds and write them to + the command's result value as ``x0``, ``x1``, ``y0``, ``y1`` rectangle + (default: no). If the rectangle is empty, not known, or somehow + degenerate, it is not set. ``x1``/``y1`` is the coordinate of the bottom + exclusive corner of the rectangle. + + The result value may depend on the VO window size, and is based on the + last known window size at the time of the call. This means the results + may be different from what is actually rendered. + + For ``ass-events``, the result rectangle is recomputed to ``PlayRes`` + coordinates (``res_x``/``res_y``). If window size is not known, a + fallback is chosen. + + You should be aware that this mechanism is very inefficient, as it + renders the full result, and then uses the bounding box of the rendered + bitmap list (even if ``hidden`` is set). It will flush various caches. + Its results also depend on the used libass version. + + This feature is experimental, and may change in some way again. + Note: always use named arguments (``mpv_command_node()``). Scripts should use the ``mp.create_osd_overlay()`` helper instead of invoking this command directly. diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index a9fc02ed44..92debe8a75 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -599,6 +599,7 @@ are useful only in special situations. ``update()`` Commit the OSD overlay to the screen, or in other words, run the ``osd-overlay`` command with the current fields of the overlay table. + Returns the result of the ``osd-overlay`` command itself. ``remove()`` Remove the overlay from the screen. A ``update()`` call will add it diff --git a/TOOLS/lua/osd-test.lua b/TOOLS/lua/osd-test.lua new file mode 100644 index 0000000000..1b1781956d --- /dev/null +++ b/TOOLS/lua/osd-test.lua @@ -0,0 +1,35 @@ +local assdraw = require 'mp.assdraw' +local utils = require 'mp.utils' + +things = {} +for i = 1, 2 do + things[i] = { + osd1 = mp.create_osd_overlay("ass-events"), + osd2 = mp.create_osd_overlay("ass-events") + } +end +things[1].text = "{\\an5}hello\\Nworld" +things[2].text = "{\\pos(400, 200)}something something" + +mp.add_periodic_timer(2, function() + for i, thing in ipairs(things) do + thing.osd1.data = thing.text + thing.osd1.compute_bounds = true + --thing.osd1.hidden = true + local res = thing.osd1:update() + print("res " .. i .. ": " .. utils.to_string(res)) + + thing.osd2.hidden = true + if res ~= nil and res.x0 ~= nil then + local draw = assdraw.ass_new() + draw:append("{\\alpha&H80}") + draw:draw_start() + draw:pos(0, 0) + draw:rect_cw(res.x0, res.y0, res.x1, res.y1) + draw:draw_stop() + thing.osd2.hidden = false + thing.osd2.data = draw.text + end + thing.osd2:update() + end +end) diff --git a/player/command.c b/player/command.c index 2ac6add009..334a25c087 100644 --- a/player/command.c +++ b/player/command.c @@ -4106,6 +4106,7 @@ static void cmd_osd_overlay(void *p) { struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; + double rc[4] = {0}; struct osd_external_ass ov = { .owner = cmd->cmd->sender, @@ -4115,9 +4116,23 @@ static void cmd_osd_overlay(void *p) .res_x = cmd->args[3].v.i, .res_y = cmd->args[4].v.i, .z = cmd->args[5].v.i, + .hidden = cmd->args[6].v.i, + .out_rc = cmd->args[7].v.i ? rc : NULL, }; osd_set_external(mpctx->osd, &ov); + + struct mpv_node *res = &cmd->result; + node_init(res, MPV_FORMAT_NODE_MAP, NULL); + + // (An empty rc uses INFINITY, avoid in JSON, just leave it unset.) + if (rc[0] < rc[2] && rc[1] < rc[3]) { + node_map_add_double(res, "x0", rc[0]); + node_map_add_double(res, "y0", rc[1]); + node_map_add_double(res, "x1", rc[2]); + node_map_add_double(res, "y1", rc[3]); + } + mp_wakeup_core(mpctx); } @@ -5927,6 +5942,8 @@ const struct mp_cmd_def mp_cmds[] = { OPT_INT("res_x", v.i, 0, OPTDEF_INT(0)), OPT_INT("res_y", v.i, 0, OPTDEF_INT(720)), OPT_INT("z", v.i, 0, OPTDEF_INT(0)), + OPT_FLAG("hidden", v.i, 0, OPTDEF_INT(0)), + OPT_FLAG("compute_bounds", v.i, 0, OPTDEF_INT(0)), }, .is_noisy = true, }, diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index 7681ca173e..395ca87e03 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -614,7 +614,7 @@ function overlay_mt.update(ov) cmd.name = "osd-overlay" cmd.res_x = math.floor(cmd.res_x) cmd.res_y = math.floor(cmd.res_y) - mp.command_native(cmd) + return mp.command_native(cmd) end function overlay_mt.remove(ov) diff --git a/sub/ass_mp.c b/sub/ass_mp.c index d5d9e3e7d6..d214375b61 100644 --- a/sub/ass_mp.c +++ b/sub/ass_mp.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -385,3 +386,27 @@ void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, p->cached_subs.change_id = 0; p->cached_subs_valid = true; } + +// Set *out_rc to [x0, y0, x1, y1] of the graphical bounding box in script +// coordinates. +// Set it to [inf, inf, -inf, -inf] if empty. +void mp_ass_get_bb(ASS_Image *image_list, ASS_Track *track, + struct mp_osd_res *res, double *out_rc) +{ + double rc[4] = {INFINITY, INFINITY, -INFINITY, -INFINITY}; + + for (ASS_Image *img = image_list; img; img = img->next) { + if (img->w == 0 || img->h == 0) + continue; + rc[0] = MPMIN(rc[0], img->dst_x); + rc[1] = MPMIN(rc[1], img->dst_y); + rc[2] = MPMAX(rc[2], img->dst_x + img->w); + rc[3] = MPMAX(rc[3], img->dst_y + img->h); + } + + double scale = track->PlayResY / (double)MPMAX(res->h, 1); + if (scale > 0) { + for (int i = 0; i < 4; i++) + out_rc[i] = rc[i] * scale; + } +} diff --git a/sub/ass_mp.h b/sub/ass_mp.h index 4ebb7f39a6..f4488bf8c7 100644 --- a/sub/ass_mp.h +++ b/sub/ass_mp.h @@ -55,5 +55,7 @@ struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent); void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists, int num_image_lists, bool changed, int preferred_osd_format, struct sub_bitmaps *out); +void mp_ass_get_bb(ASS_Image *image_list, ASS_Track *track, + struct mp_osd_res *res, double *out_rc); #endif /* MPLAYER_ASS_MP_H */ diff --git a/sub/osd.h b/sub/osd.h index 31b3dd532b..5a6809817f 100644 --- a/sub/osd.h +++ b/sub/osd.h @@ -209,6 +209,9 @@ struct osd_external_ass { char *data; int res_x, res_y; int z; + bool hidden; + + double *out_rc; // hack to pass boundary rect, [x0, y0, x1, y1] }; // defined in osd_libass.c and osd_dummy.c diff --git a/sub/osd_libass.c b/sub/osd_libass.c index 01c0337eb4..bf527e17f3 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -40,6 +40,9 @@ static const char osd_font_pfb[] = #define ASS_USE_OSD_FONT "{\\fnmpv-osd-symbols}" +static void append_ass(struct ass_state *ass, struct mp_osd_res *res, + ASS_Image **img_list, bool *changed); + void osd_init_backend(struct osd_state *osd) { } @@ -101,6 +104,8 @@ static void update_playres(struct ass_state *ass, struct mp_osd_res *vo_res) int old_res_x = track->PlayResX; int old_res_y = track->PlayResY; + ass->vo_res = *vo_res; + double aspect = 1.0 * vo_res->w / MPMAX(vo_res->h, 1); if (vo_res->display_par > 0) aspect = aspect / vo_res->display_par; @@ -531,10 +536,12 @@ void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov) struct osd_external *entry = obj->externals[index]; if (!ov->format) { + if (!entry->ov.hidden) { + obj->changed = true; + osd->want_redraw_notification = true; + } destroy_external(entry); MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index); - obj->changed = true; - osd->want_redraw_notification = true; goto done; } @@ -547,17 +554,42 @@ void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov) entry->ov.res_y = ov->res_y; zorder_changed |= entry->ov.z != ov->z; entry->ov.z = ov->z; + entry->ov.hidden = ov->hidden; update_external(osd, obj, entry); - obj->changed = true; - osd->want_redraw_notification = true; + if (!entry->ov.hidden) { + obj->changed = true; + osd->want_redraw_notification = true; + } if (zorder_changed) { qsort(obj->externals, obj->num_externals, sizeof(obj->externals[0]), cmp_zorder); } + if (ov->out_rc) { + struct mp_osd_res vo_res = entry->ass.vo_res; + // Defined fallback if VO has not drawn this yet + if (vo_res.w < 1 || vo_res.h < 1) { + vo_res = (struct mp_osd_res){ + .w = entry->ov.res_x, + .h = entry->ov.res_y, + .display_par = 1, + }; + // According to osd-overlay command description. + if (vo_res.w < 1) + vo_res.w = 1280; + if (vo_res.h < 1) + vo_res.h = 720; + } + + ASS_Image *img_list = NULL; + append_ass(&entry->ass, &vo_res, &img_list, NULL); + + mp_ass_get_bb(img_list, entry->ass.track, &vo_res, ov->out_rc); + } + done: pthread_mutex_unlock(&osd->lock); } @@ -593,7 +625,13 @@ static void append_ass(struct ass_state *ass, struct mp_osd_res *res, int ass_changed; *img_list = ass_render_frame(ass->render, ass->track, 0, &ass_changed); - *changed |= ass_changed; + + ass->changed |= ass_changed; + + if (changed) { + *changed |= ass->changed; + ass->changed = false; + } } void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, @@ -609,8 +647,13 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, append_ass(&obj->ass, &obj->vo_res, &obj->ass_imgs[0], &obj->changed); for (int n = 0; n < obj->num_externals; n++) { - append_ass(&obj->externals[n]->ass, &obj->vo_res, - &obj->ass_imgs[n + 1], &obj->changed); + if (obj->externals[n]->ov.hidden) { + update_playres(&obj->externals[n]->ass, &obj->vo_res); + obj->ass_imgs[n + 1] = NULL; + } else { + append_ass(&obj->externals[n]->ass, &obj->vo_res, + &obj->ass_imgs[n + 1], &obj->changed); + } } mp_ass_packer_pack(obj->ass_packer, obj->ass_imgs, obj->num_externals + 1, diff --git a/sub/osd_state.h b/sub/osd_state.h index 8207cf0dda..b563502fd6 100644 --- a/sub/osd_state.h +++ b/sub/osd_state.h @@ -23,6 +23,8 @@ struct ass_state { struct ass_renderer *render; struct ass_library *library; int res_x, res_y; + bool changed; + struct mp_osd_res vo_res; // last known value }; struct osd_object { -- cgit v1.2.3