This adds a command to escape ASS tags to remove code duplication between sub/osd_libass.c, console.lua, osc.lua, stats.lua and any user script that calls mp.create_osd_overlay(). A command is used instead of scripting functions so that all clients can use this and not just use Lua and JS ones. osd_mangle_ass() also interprets osd-sym-cc and osd-ass-cc/{0,1}, but since they use invalid UTF-8 characters there is no risk of escape-ass users using them by accident, like with any OSD message. Always replacing \n with \\N in mangle_ass() even when it is not called by escape-ass doesn't seem to cause any issue, but I made it conditional anyway to avoid changing how all OSD messages are treated unnecessarily.
Unified · Split
| @@ -0,0 +1 @@ | |||
| 1 | add the `escape-ass` command | ||
| @@ -1259,6 +1259,18 @@ Input Commands that are Possibly Subject to Change · 1258 unmodified lines | |||
| 1259 | use the ``mp.create_osd_overlay()`` helper instead of invoking this | 1259 | use the ``mp.create_osd_overlay()`` helper instead of invoking this |
| 1260 | command directly. | 1260 | command directly. |
| 1261 | 1261 | ||
| 1262 | ``escape-ass <text>`` | ||
| 1263 | Modify ``text`` so that commands and functions that interpret ASS tags, | ||
| 1264 | such as ``osd-overlay`` and ``mp.create_osd_overlay``, will display it | ||
| 1265 | verbatim, and return it. This can only be used through the client API or | ||
| 1266 | from a script using ``mp.command_native``. | ||
| 1267 | |||
| 1268 | .. admonition:: Example | ||
| 1269 | |||
| 1270 | ``mp.osd_message(mp.command_native({"escape-ass", "foo {bar}"}))`` | ||
| 1271 | |||
| 1272 | This line of Lua prints "foo \\{bar}" on the OSD. | ||
| 1273 | |||
| 1262 | ``script-message [<arg1> [<arg2> [...]]]`` | 1274 | ``script-message [<arg1> [<arg2> [...]]]`` |
| 1263 | Send a message to all clients, and pass it the following list of arguments. | 1275 | Send a message to all clients, and pass it the following list of arguments. |
| 1264 | What this message means, how many arguments it takes, and what the arguments | 1276 | What this message means, how many arguments it takes, and what the arguments |
| @@ -5582,6 +5582,19 @@ static void cmd_expand_path(void *p) · 5581 unmodified lines | |||
| 5582 | }; | 5582 | }; |
| 5583 | } | 5583 | } |
| 5584 | 5584 | ||
| 5585 | static void cmd_escape_ass(void *p) | ||
| 5586 | { | ||
| 5587 | struct mp_cmd_ctx *cmd = p; | ||
| 5588 | bstr dst = {0}; | ||
| 5589 | |||
| 5590 | osd_mangle_ass(&dst, cmd->args[0].v.s, true); | ||
| 5591 | |||
| 5592 | cmd->result = (mpv_node){ | ||
| 5593 | .format = MPV_FORMAT_STRING, | ||
| 5594 | .u.string = dst.len ? (char *)dst.start : talloc_strdup(NULL, ""), | ||
| 5595 | }; | ||
| 5596 | } | ||
| 5597 | |||
| 5585 | static struct load_action get_load_action(struct MPContext *mpctx, int action_flag) | 5598 | static struct load_action get_load_action(struct MPContext *mpctx, int action_flag) |
| 5586 | { | 5599 | { |
| 5587 | switch (action_flag) { | 5600 | switch (action_flag) { |
| @@ -6679,6 +6692,8 @@ const struct mp_cmd_def mp_cmds[] = { · 1091 unmodified lines | |||
| 6679 | .is_noisy = true }, | 6692 | .is_noisy = true }, |
| 6680 | { "expand-path", cmd_expand_path, { {"text", OPT_STRING(v.s)} }, | 6693 | { "expand-path", cmd_expand_path, { {"text", OPT_STRING(v.s)} }, |
| 6681 | .is_noisy = true }, | 6694 | .is_noisy = true }, |
| 6695 | { "escape-ass", cmd_escape_ass, { {"text", OPT_STRING(v.s)} }, | ||
| 6696 | .is_noisy = true }, | ||
| 6682 | { "show-progress", cmd_show_progress, .allow_auto_repeat = true, | 6697 | { "show-progress", cmd_show_progress, .allow_auto_repeat = true, |
| 6683 | .is_noisy = true }, | 6698 | .is_noisy = true }, |
| 6684 | 6699 | ||
| @@ -229,19 +229,7 @@ end · 228 unmodified lines | |||
| 229 | 229 | ||
| 230 | -- Escape a string for verbatim display on the OSD | 230 | -- Escape a string for verbatim display on the OSD |
| 231 | function ass_escape(str) | 231 | function ass_escape(str) |
| 232 | -- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if | 232 | return mp.command_native({'escape-ass', str}) |
| 233 | -- it isn't followed by a recognised character, so add a zero-width | ||
| 234 | -- non-breaking space | ||
| 235 | str = str:gsub('\\', '\\\239\187\191') | ||
| 236 | str = str:gsub('{', '\\{') | ||
| 237 | str = str:gsub('}', '\\}') | ||
| 238 | -- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of | ||
| 239 | -- consecutive newlines | ||
| 240 | str = str:gsub('\n', '\239\187\191\\N') | ||
| 241 | -- Turn leading spaces into hard spaces to prevent ASS from stripping them | ||
| 242 | str = str:gsub('\\N ', '\\N\\h') | ||
| 243 | str = str:gsub('^ ', '\\h') | ||
| 244 | return str | ||
| 245 | end | 233 | end |
| 246 | 234 | ||
| 247 | -- Takes a list of strings, a max width in characters and | 235 | -- Takes a list of strings, a max width in characters and |
| @@ -129,9 +129,6 @@ local function graph_add_value(graph, value) · 128 unmodified lines | |||
| 129 | graph.max = max(graph.max, value) | 129 | graph.max = max(graph.max, value) |
| 130 | end | 130 | end |
| 131 | 131 | ||
| 132 | -- "\\<U+2060>" in UTF-8 (U+2060 is WORD-JOINER) | ||
| 133 | local ESC_BACKSLASH = "\\" .. string.char(0xE2, 0x81, 0xA0) | ||
| 134 | |||
| 135 | local function no_ASS(t) | 132 | local function no_ASS(t) |
| 136 | if not o.use_ass then | 133 | if not o.use_ass then |
| 137 | return t | 134 | return t |
| @@ -139,16 +136,7 @@ local function no_ASS(t) · 1 unmodified line | |||
| 139 | -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1} | 136 | -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1} |
| 140 | return ass_stop .. t .. ass_start | 137 | return ass_stop .. t .. ass_start |
| 141 | else | 138 | else |
| 142 | -- mp.set_osd_ass doesn't support ass-escape. roll our own. | 139 | return mp.command_native({"escape-ass", tostring(t)}) |
| 143 | -- similar to mpv's sub/osd_libass.c:mangle_ass(...), excluding | ||
| 144 | -- space after newlines because no_ASS is not used with multi-line. | ||
| 145 | -- space at the beginning is replaced with "\\h" because it matters | ||
| 146 | -- at the beginning of a line, and we can't know where our output | ||
| 147 | -- ends up. no issue if it ends up at the middle of a line. | ||
| 148 | return tostring(t) | ||
| 149 | :gsub("\\", ESC_BACKSLASH) | ||
| 150 | :gsub("{", "\\{") | ||
| 151 | :gsub("^ ", "\\h") | ||
| 152 | end | 140 | end |
| 153 | end | 141 | end |
| 154 | 142 | ||
| @@ -245,5 +245,6 @@ void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov); · 244 unmodified lines | |||
| 245 | void osd_set_external_remove_owner(struct osd_state *osd, void *owner); | 245 | void osd_set_external_remove_owner(struct osd_state *osd, void *owner); |
| 246 | void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h); | 246 | void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h); |
| 247 | void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function); | 247 | void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function); |
| 248 | void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines); | ||
| 248 | 249 | ||
| 249 | #endif /* MPLAYER_SUB_H */ | 250 | #endif /* MPLAYER_SUB_H */ |
| @@ -193,7 +193,7 @@ void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function) · 192 unmodified lines | |||
| 193 | snprintf(buffer, buffer_size, "\xFF%c", osd_function); | 193 | snprintf(buffer, buffer_size, "\xFF%c", osd_function); |
| 194 | } | 194 | } |
| 195 | 195 | ||
| 196 | static void mangle_ass(bstr *dst, const char *in) | 196 | void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines) |
| 197 | { | 197 | { |
| 198 | const char *start = in; | 198 | const char *start = in; |
| 199 | bool escape_ass = true; | 199 | bool escape_ass = true; |
| @@ -213,6 +213,14 @@ static void mangle_ass(bstr *dst, const char *in) · 13 unmodified lines | |||
| 213 | } | 213 | } |
| 214 | if (escape_ass && *in == '{') | 214 | if (escape_ass && *in == '{') |
| 215 | bstr_xappend(NULL, dst, bstr0("\\")); | 215 | bstr_xappend(NULL, dst, bstr0("\\")); |
| 216 | // Replace newlines with \N for escape-ass. This is necessary to apply | ||
| 217 | // ASS tags past newlines and to preserve consecutive newlines with | ||
| 218 | // osd-overlay because update_external() adds a ASS event per line. | ||
| 219 | if (replace_newlines && *in == '\n') { | ||
| 220 | bstr_xappend(NULL, dst, bstr0("\\N")); | ||
| 221 | in += 1; | ||
| 222 | continue; | ||
| 223 | } | ||
| 216 | // Libass will strip leading whitespace | 224 | // Libass will strip leading whitespace |
| 217 | if (in[0] == ' ' && (in == start || in[-1] == '\n')) { | 225 | if (in[0] == ' ' && (in == start || in[-1] == '\n')) { |
| 218 | bstr_xappend(NULL, dst, bstr0("\\h")); | 226 | bstr_xappend(NULL, dst, bstr0("\\h")); |
| @@ -231,7 +239,7 @@ static ASS_Event *add_osd_ass_event_escaped(ASS_Track *track, const char *style, · 12 unmodified lines | |||
| 231 | const char *text) | 239 | const char *text) |
| 232 | { | 240 | { |
| 233 | bstr buf = {0}; | 241 | bstr buf = {0}; |
| 234 | mangle_ass(&buf, text); | 242 | osd_mangle_ass(&buf, text, false); |
| 235 | ASS_Event *e = add_osd_ass_event(track, style, buf.start); | 243 | ASS_Event *e = add_osd_ass_event(track, style, buf.start); |
| 236 | talloc_free(buf.start); | 244 | talloc_free(buf.start); |
| 237 | return e; | 245 | return e; |