mirror of
https://github.com/mpv-player/mpv
synced 2025-01-30 19:52:14 +00:00
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:
parent
c6822b853d
commit
7a76b577d8
@ -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.
|
||||
|
@ -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
35
TOOLS/lua/osd-test.lua
Normal 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)
|
@ -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,
|
||||
},
|
||||
|
@ -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)
|
||||
|
25
sub/ass_mp.c
25
sub/ass_mp.c
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user