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.
This commit is contained in:
wm4 2020-03-06 18:20:11 +01:00
parent c6822b853d
commit 7a76b577d8
10 changed files with 161 additions and 8 deletions

View File

@ -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.

View File

@ -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

35
TOOLS/lua/osd-test.lua Normal file
View File

@ -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)

View File

@ -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,
},

View File

@ -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)

View File

@ -23,6 +23,7 @@
#include <stdarg.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include <ass/ass.h>
#include <ass/ass_types.h>
@ -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;
}
}

View File

@ -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 */

View File

@ -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

View File

@ -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,

View File

@ -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 {