diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index d3e49fc83d..1d66dc47bc 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -937,6 +937,86 @@ Input Commands that are Possibly Subject to Change Remove an overlay added with ``overlay-add`` and the same ID. Does nothing if no overlay with this ID exists. +``osd-overlay`` + Add/update/remove an OSD overlay. + + (Although this sounds similar to ``overlay-add``, ``osd-overlay`` is for + text overlays, while ``overlay-add`` is for bitmaps. Maybe ``overlay-add`` + will be merged into ``osd-overlay`` to remove this oddity.) + + You can use this to add text overlays in ASS format. ASS has advanced + positioning and rendering tags, which can be used to render almost any kind + of vector graphics. + + This command accepts the following parameters: + + ``id`` + Arbitrary integer that identifies the overlay. Multiple overlays can be + added by calling this command with different ``id`` parameters. Calling + this command with the same ``id`` replaces the previously set overlay. + + There is a separate namespace for each libmpv client (i.e. IPC + connection, script), so IDs can be made up and assigned by the API user + without conflicting with other API users. + + If the libmpv client is destroyed, all overlays associated with it are + also deleted. In particular, connecting via ``--input-ipc-server``, + adding an overlay, and disconnecting will remove the overlay immediately + again. + + ``format`` + String that gives the type of the overlay. Accepts the following values: + + ``ass-events`` + The ``data`` parameter is a string. The string is split on the + newline character. Every line is turned into the ``Text`` part of + a ``Dialogue`` ASS event. Timing is unused (but behavior of timing + dependent ASS tags may change in future mpv versions). + + Note that it's better to put multiple lines into ``data``, instead + of adding multiple OSD overlays. + + This provides 2 ASS ``Styles``. ``OSD`` contains the text style as + defined by the current ``--osd-...`` options. ``Default`` is + similar, and contains style that ``OSD`` would have if all options + were set to the default. + + In addition, the ``res_x`` and ``res_y`` options specify the value + of the ASS ``PlayResX`` and ``PlayResY`` header fields. If ``res_y`` + is set to 0, ``PlayResY`` is initialized to an arbitrary default + value (but note that the default for this command is 720, not 0). + If ``res_x`` is set to 0, ``PlayResX`` is set based on ``res_y`` + such that a virtual ASS pixel has a square pixel aspect ratio. + + ``none`` + Special value that causes the overlay to be removed. Most parameters + other than ``id`` and ``format`` are mostly ignored. + + ``data`` + String defining the overlay contents according to the ``format`` + parameter. + + ``res_x``, ``res_y`` + Used if ``format`` is set to ``ass-events`` (see description there). + Optional, defaults to 0/720. + + ``z`` + The Z order of the overlay. Optional, defaults to 0. + + Note that Z order between different overlays of different formats is + static, and cannot be changed (currently, this means that bitmap + overlays added by ``overlay-add`` are always on top of the ASS overlays + added by ``osd-overlay``). In addition, the builtin OSD components are + always below any of the custom OSD. (This includes subtitles of any kind + as well as text rendered by ``show-text``.) + + It's possible that future mpv versions will randomly change how Z order + between different OSD formats and builtin OSD is handled. + + Note: always use named arguments (``mpv_command_node()``). Scripts should + use the ``mp.create_osd_overlay()`` helper instead of invoking this command + directly. + ``script-message [ [ [...]]]`` Send a message to all clients, and pass it the following list of arguments. What this message means, how many arguments it takes, and what the arguments diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst index d98edf38a9..0829e8be4b 100644 --- a/DOCS/man/lua.rst +++ b/DOCS/man/lua.rst @@ -536,6 +536,41 @@ are useful only in special situations. Undo a previous registration with ``mp.register_script_message``. Does nothing if the ``name`` wasn't registered. +``mp.create_osd_overlay(format)`` + Create an OSD overlay. This is a very thin wrapper around the ``osd-overlay`` + command. The function returns a table, which mostly contains fields that + will be passed to ``osd-overlay``. The ``format`` parameter is used to + initialize the ``format`` field. The ``data`` field contains the text to + be used as overlay. For details, see the ``osd-overlay`` command. + + In addition, it provides the following methods: + + ``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. + + ``remove()`` + Remove the overlay from the screen. A ``update()`` call will add it + again. + + Example: + + :: + + ov = mp.create_osd_overlay("ass-events") + ov.data = "{\\an5}{\\b1}hello world!" + ov:update() + + The advantage of using this wrapper (as opposed to running ``osd-overlay`` + directly) is that the ``id`` field is allocated automatically. + +``mp.get_osd_size()`` + Returns a tuple of ``osd_width, osd_height, osd_par``. The first two give + the size of the OSD in pixels (for video ouputs like ``--vo=xv``, this may + be "scaled" pixels). The third is the display pixel aspect ratio. + + May return invalid/nonsense values if OSD is not initialized yet. + mp.msg functions ---------------- diff --git a/player/client.c b/player/client.c index e44f3cacea..97e43a15ce 100644 --- a/player/client.c +++ b/player/client.c @@ -457,7 +457,7 @@ static void mp_destroy_client(mpv_handle *ctx, bool terminate) // causes a crash, block until all asynchronous requests were served. mpv_wait_async_requests(ctx); - osd_set_external(mpctx->osd, ctx, 0, 0, NULL); + osd_set_external_remove_owner(mpctx->osd, ctx); mp_input_remove_sections_by_owner(mpctx->input, ctx->name); pthread_mutex_lock(&clients->lock); diff --git a/player/command.c b/player/command.c index 86715c8896..a230ad460c 100644 --- a/player/command.c +++ b/player/command.c @@ -4085,6 +4085,25 @@ static void overlay_uninit(struct MPContext *mpctx) mp_image_unrefp(&cmd->overlay_osd[n].packed); } +static void cmd_osd_overlay(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + + struct osd_external_ass ov = { + .owner = cmd->cmd->sender, + .id = cmd->args[0].v.i64, + .format = cmd->args[1].v.i, + .data = cmd->args[2].v.s, + .res_x = cmd->args[3].v.i, + .res_y = cmd->args[4].v.i, + .z = cmd->args[5].v.i, + }; + + osd_set_external(mpctx->osd, &ov); + mp_wakeup_core(mpctx); +} + static struct track *find_track_with_url(struct MPContext *mpctx, int type, const char *url) { @@ -5877,6 +5896,19 @@ const struct mp_cmd_def mp_cmds[] = { OPT_INT("stride", v.i, 0), }}, { "overlay-remove", cmd_overlay_remove, { OPT_INT("id", v.i, 0) } }, + { "osd-overlay", cmd_osd_overlay, + { + OPT_INT64("id", v.i64, 0), + OPT_CHOICE("format", v.i, 0, ({"none", 0}, + {"ass-events", 1})), + OPT_STRING("data", v.s, 0), + 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)), + }, + .is_noisy = true, + }, + { "write-watch-later-config", cmd_write_watch_later_config }, { "hook-add", cmd_hook_add, { OPT_STRING("arg0", v.s, 0), diff --git a/player/javascript.c b/player/javascript.c index d478a326a8..5981eb848d 100644 --- a/player/javascript.c +++ b/player/javascript.c @@ -738,7 +738,7 @@ static void script_set_osd_ass(js_State *J) int res_x = jsL_checkint(J, 1); int res_y = jsL_checkint(J, 2); const char *text = js_tostring(J, 3); - osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text); + //osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text); mp_wakeup_core(ctx->mpctx); push_success(J); } diff --git a/player/lua.c b/player/lua.c index 69977dfd42..aec9908b69 100644 --- a/player/lua.c +++ b/player/lua.c @@ -998,31 +998,6 @@ static int script_raw_abort_async_command(lua_State *L) return 0; } -static int script_set_osd_ass(lua_State *L) -{ - struct script_ctx *ctx = get_ctx(L); - int res_x = luaL_checkinteger(L, 1); - int res_y = luaL_checkinteger(L, 2); - const char *text = luaL_checkstring(L, 3); - if (!text[0]) - text = " "; // force external OSD initialization - osd_set_external(ctx->mpctx->osd, ctx->client, res_x, res_y, (char *)text); - mp_wakeup_core(ctx->mpctx); - return 0; -} - -static int script_get_osd_size(lua_State *L) -{ - struct MPContext *mpctx = get_mpctx(L); - struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); - double aspect = 1.0 * vo_res.w / MPMAX(vo_res.h, 1) / - (vo_res.display_par ? vo_res.display_par : 1); - lua_pushnumber(L, vo_res.w); - lua_pushnumber(L, vo_res.h); - lua_pushnumber(L, aspect); - return 3; -} - static int script_get_osd_margins(lua_State *L) { struct MPContext *mpctx = get_mpctx(L); @@ -1275,8 +1250,6 @@ static const struct fn_entry main_fns[] = { FN_ENTRY(set_property_native), FN_ENTRY(raw_observe_property), FN_ENTRY(raw_unobserve_property), - FN_ENTRY(set_osd_ass), - FN_ENTRY(get_osd_size), FN_ENTRY(get_osd_margins), FN_ENTRY(get_mouse_pos), FN_ENTRY(get_time), diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua index ba59653828..22ffa086d1 100644 --- a/player/lua/defaults.lua +++ b/player/lua/defaults.lua @@ -589,6 +589,65 @@ function mp.abort_async_command(t) end end +local overlay_mt = {} +overlay_mt.__index = overlay_mt +local overlay_new_id = 0 + +function mp.create_osd_overlay(format) + overlay_new_id = overlay_new_id + 1 + local overlay = { + format = format, + id = overlay_new_id, + data = "", + res_x = 0, + res_y = 720, + } + setmetatable(overlay, overlay_mt) + return overlay +end + +function overlay_mt.update(ov) + local cmd = {} + for k, v in pairs(ov) do + cmd[k] = v + end + cmd.name = "osd-overlay" + mp.command_native(cmd) +end + +function overlay_mt.remove(ov) + mp.command_native { + name = "osd-overlay", + id = ov.id, + format = "none", + data = "", + } +end + +-- legacy API +function mp.set_osd_ass(res_x, res_y, data) + if not mp._legacy_overlay then + mp._legacy_overlay = mp.create_osd_overlay("ass-events") + end + mp._legacy_overlay.res_x = res_x + mp._legacy_overlay.res_y = res_y + mp._legacy_overlay.data = data + mp._legacy_overlay:update() +end + +function mp.get_osd_size() + local w = mp.get_property_number("osd-width", 0) + local h = mp.get_property_number("osd-height", 0) + local par = mp.get_property_number("osd-par", 0) + if par == 0 then + par = 1 + end + + local aspect = 1.0 * w / math.max(h) / par + return w, h, aspect +end + + local mp_utils = package.loaded["mp.utils"] function mp_utils.format_table(t, set) diff --git a/sub/osd.h b/sub/osd.h index 660a828767..31b3dd532b 100644 --- a/sub/osd.h +++ b/sub/osd.h @@ -202,9 +202,18 @@ struct mp_osd_res osd_get_vo_res(struct osd_state *osd); void osd_rescale_bitmaps(struct sub_bitmaps *imgs, int frame_w, int frame_h, struct mp_osd_res res, double compensate_par); +struct osd_external_ass { + void *owner; // unique pointer (NULL is also allowed) + int64_t id; + int format; + char *data; + int res_x, res_y; + int z; +}; + // defined in osd_libass.c and osd_dummy.c -void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, - char *text); +void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov); +void osd_set_external_remove_owner(struct osd_state *osd, void *owner); void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h); void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function); diff --git a/sub/osd_dummy.c b/sub/osd_dummy.c index 0e6b802cef..db032ec5b1 100644 --- a/sub/osd_dummy.c +++ b/sub/osd_dummy.c @@ -24,8 +24,11 @@ void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, *out_imgs = (struct sub_bitmaps) {0}; } -void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, - char *text) +void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov) +{ +} + +void osd_set_external_remove_owner(struct osd_state *osd, void *owner) { } diff --git a/sub/osd_libass.c b/sub/osd_libass.c index d5f7bb82bf..01c0337eb4 100644 --- a/sub/osd_libass.c +++ b/sub/osd_libass.c @@ -80,8 +80,8 @@ static void destroy_ass_renderer(struct ass_state *ass) static void destroy_external(struct osd_external *ext) { - talloc_free(ext->text); destroy_ass_renderer(&ext->ass); + talloc_free(ext); } void osd_destroy_backend(struct osd_state *osd) @@ -90,7 +90,7 @@ void osd_destroy_backend(struct osd_state *osd) struct osd_object *obj = osd->objs[n]; destroy_ass_renderer(&obj->ass); for (int i = 0; i < obj->num_externals; i++) - destroy_external(&obj->externals[i]); + destroy_external(obj->externals[i]); obj->num_externals = 0; } } @@ -470,11 +470,9 @@ static void update_osd(struct osd_state *osd, struct osd_object *obj) static void update_external(struct osd_state *osd, struct osd_object *obj, struct osd_external *ext) { - bstr t = bstr0(ext->text); - if (!t.len) - return; - ext->ass.res_x = ext->res_x; - ext->ass.res_y = ext->res_y; + bstr t = bstr0(ext->ov.data); + ext->ass.res_x = ext->ov.res_x; + ext->ass.res_y = ext->ov.res_y; create_ass_track(osd, obj, &ext->ass); clear_ass(&ext->ass); @@ -497,29 +495,42 @@ static void update_external(struct osd_state *osd, struct osd_object *obj, } } -void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, - char *text) +static int cmp_zorder(const void *pa, const void *pb) +{ + const struct osd_external *a = *(struct osd_external **)pa; + const struct osd_external *b = *(struct osd_external **)pb; + return a->ov.z == b->ov.z ? 0 : (a->ov.z > b->ov.z ? 1 : -1); +} + +void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov) { pthread_mutex_lock(&osd->lock); struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; - struct osd_external *entry = 0; + bool zorder_changed = false; + int index = -1; + for (int n = 0; n < obj->num_externals; n++) { - if (obj->externals[n].id == id) { - entry = &obj->externals[n]; + struct osd_external *e = obj->externals[n]; + if (e->ov.id == ov->id && e->ov.owner == ov->owner) { + index = n; break; } } - if (!entry && !text) - goto done; - if (!entry) { - struct osd_external new = { .id = id }; + if (index < 0) { + if (!ov->format) + goto done; + struct osd_external *new = talloc_zero(NULL, struct osd_external); + new->ov.owner = ov->owner; + new->ov.id = ov->id; MP_TARRAY_APPEND(obj, obj->externals, obj->num_externals, new); - entry = &obj->externals[obj->num_externals - 1]; + index = obj->num_externals - 1; + zorder_changed = true; } - if (!text) { - int index = entry - &obj->externals[0]; + struct osd_external *entry = obj->externals[index]; + + if (!ov->format) { destroy_external(entry); MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index); obj->changed = true; @@ -527,22 +538,46 @@ void osd_set_external(struct osd_state *osd, void *id, int res_x, int res_y, goto done; } - if (!entry->text || strcmp(entry->text, text) != 0 || - entry->res_x != res_x || entry->res_y != res_y) - { - talloc_free(entry->text); - entry->text = talloc_strdup(NULL, text); - entry->res_x = res_x; - entry->res_y = res_y; - update_external(osd, obj, entry); - obj->changed = true; - osd->want_redraw_notification = true; + entry->ov.format = ov->format; + if (!entry->ov.data) + entry->ov.data = talloc_strdup(entry, ""); + entry->ov.data[0] = '\0'; // reuse memory allocation + entry->ov.data = talloc_strdup_append(entry->ov.data, ov->data); + entry->ov.res_x = ov->res_x; + entry->ov.res_y = ov->res_y; + zorder_changed |= entry->ov.z != ov->z; + entry->ov.z = ov->z; + + update_external(osd, obj, entry); + + obj->changed = true; + osd->want_redraw_notification = true; + + if (zorder_changed) { + qsort(obj->externals, obj->num_externals, sizeof(obj->externals[0]), + cmp_zorder); } done: pthread_mutex_unlock(&osd->lock); } +void osd_set_external_remove_owner(struct osd_state *osd, void *owner) +{ + pthread_mutex_lock(&osd->lock); + struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; + for (int n = obj->num_externals - 1; n >= 0; n--) { + struct osd_external *e = obj->externals[n]; + if (e->ov.owner == owner) { + destroy_external(e); + MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, n); + obj->changed = true; + osd->want_redraw_notification = true; + } + } + pthread_mutex_unlock(&osd->lock); +} + static void append_ass(struct ass_state *ass, struct mp_osd_res *res, ASS_Image **img_list, bool *changed) { @@ -574,8 +609,8 @@ 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); + 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 ac8befe3ba..8207cf0dda 100644 --- a/sub/osd_state.h +++ b/sub/osd_state.h @@ -38,7 +38,7 @@ struct osd_object { struct dec_sub *sub; // OSDTYPE_EXTERNAL - struct osd_external *externals; + struct osd_external **externals; int num_externals; // OSDTYPE_EXTERNAL2 @@ -56,9 +56,7 @@ struct osd_object { }; struct osd_external { - void *id; - char *text; - int res_x, res_y; + struct osd_external_ass ov; struct ass_state ass; };