/src/mpv/sub/osd_libass.c
Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <math.h> |
19 | | #include <stdio.h> |
20 | | #include <stdlib.h> |
21 | | #include <string.h> |
22 | | #include <assert.h> |
23 | | |
24 | | #include "mpv_talloc.h" |
25 | | #include "misc/bstr.h" |
26 | | #include "common/common.h" |
27 | | #include "common/msg.h" |
28 | | #include "osd.h" |
29 | | #include "osd_state.h" |
30 | | |
31 | | static const char osd_font_pfb[] = |
32 | | #include "sub/osd_font.otf.inc" |
33 | | ; |
34 | | |
35 | | #include "sub/ass_mp.h" |
36 | | #include "sub/packer.h" |
37 | | #include "options/options.h" |
38 | | |
39 | | |
40 | 536 | #define ASS_USE_OSD_FONT "{\\fnmpv-osd-symbols}" |
41 | | |
42 | | static void append_ass(struct ass_state *ass, struct mp_osd_res *res, |
43 | | ASS_Image **img_list, bool *changed); |
44 | | |
45 | | static void create_ass_renderer(struct osd_state *osd, struct ass_state *ass) |
46 | 1.05k | { |
47 | 1.05k | if (ass->render) |
48 | 670 | return; |
49 | | |
50 | 386 | ass->log = mp_log_new(NULL, osd->log, "libass"); |
51 | 386 | ass->library = mp_ass_init(osd->global, osd->opts->osd_style, ass->log); |
52 | 386 | ass_add_font(ass->library, "mpv-osd-symbols", (void *)osd_font_pfb, |
53 | 386 | sizeof(osd_font_pfb) - 1); |
54 | | |
55 | 386 | ass->render = ass_renderer_init(ass->library); |
56 | 386 | if (!ass->render) |
57 | 0 | abort(); |
58 | | |
59 | 386 | mp_ass_configure_fonts(ass->render, osd->opts->osd_style, |
60 | 386 | osd->global, ass->log); |
61 | 386 | ass_set_pixel_aspect(ass->render, 1.0); |
62 | 386 | } |
63 | | |
64 | | static void destroy_ass_renderer(struct ass_state *ass) |
65 | 716k | { |
66 | 716k | if (ass->track) |
67 | 386 | ass_free_track(ass->track); |
68 | 716k | ass->track = NULL; |
69 | 716k | if (ass->render) |
70 | 386 | ass_renderer_done(ass->render); |
71 | 716k | ass->render = NULL; |
72 | 716k | if (ass->library) |
73 | 386 | ass_library_done(ass->library); |
74 | 716k | ass->library = NULL; |
75 | 716k | talloc_free(ass->log); |
76 | 716k | ass->log = NULL; |
77 | 716k | } |
78 | | |
79 | | static void destroy_external(struct osd_external *ext) |
80 | 0 | { |
81 | 0 | destroy_ass_renderer(&ext->ass); |
82 | 0 | talloc_free(ext); |
83 | 0 | } |
84 | | |
85 | | void osd_destroy_backend(struct osd_state *osd) |
86 | 143k | { |
87 | 859k | for (int n = 0; n < MAX_OSD_PARTS; n++) { |
88 | 716k | struct osd_object *obj = osd->objs[n]; |
89 | 716k | destroy_ass_renderer(&obj->ass); |
90 | 716k | for (int i = 0; i < obj->num_externals; i++) |
91 | 0 | destroy_external(obj->externals[i]); |
92 | 716k | obj->num_externals = 0; |
93 | 716k | } |
94 | 143k | } |
95 | | |
96 | | static void update_playres(struct ass_state *ass, struct mp_osd_res *vo_res) |
97 | 1.32k | { |
98 | 1.32k | ASS_Track *track = ass->track; |
99 | 1.32k | int old_res_x = track->PlayResX; |
100 | 1.32k | int old_res_y = track->PlayResY; |
101 | | |
102 | 1.32k | ass->vo_res = *vo_res; |
103 | | |
104 | 1.32k | double aspect = 1.0 * vo_res->w / MPMAX(vo_res->h, 1); |
105 | 1.32k | if (vo_res->display_par > 0) |
106 | 587 | aspect = aspect / vo_res->display_par; |
107 | | |
108 | 1.32k | track->PlayResY = ass->res_y ? ass->res_y : MP_ASS_FONT_PLAYRESY; |
109 | 1.32k | track->PlayResX = ass->res_x ? ass->res_x : track->PlayResY * aspect; |
110 | | |
111 | | // Force libass to clear its internal cache - it doesn't check for |
112 | | // PlayRes changes itself. |
113 | 1.32k | if (old_res_x != track->PlayResX || old_res_y != track->PlayResY) |
114 | 386 | ass_set_frame_size(ass->render, 1, 1); |
115 | 1.32k | } |
116 | | |
117 | | static void create_ass_track(struct osd_state *osd, struct osd_object *obj, |
118 | | struct ass_state *ass) |
119 | 1.05k | { |
120 | 1.05k | create_ass_renderer(osd, ass); |
121 | | |
122 | 1.05k | ASS_Track *track = ass->track; |
123 | 1.05k | struct mp_osd_render_opts *opts = osd->opts; |
124 | 1.05k | if (!track) |
125 | 386 | track = ass->track = ass_new_track(ass->library); |
126 | | |
127 | 1.05k | track->track_type = TRACK_TYPE_ASS; |
128 | 1.05k | track->Timer = 100.; |
129 | 1.05k | track->WrapStyle = 1; // end-of-line wrapping instead of smart wrapping |
130 | 1.05k | track->Kerning = true; |
131 | 1.05k | track->ScaledBorderAndShadow = true; |
132 | 1.05k | ass_set_shaper(ass->render, opts->osd_shaper); |
133 | 1.05k | #if LIBASS_VERSION >= 0x01600010 |
134 | 1.05k | ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1); |
135 | 1.05k | #endif |
136 | 1.05k | #if LIBASS_VERSION >= 0x01703010 |
137 | 1.05k | ass_configure_prune(track, opts->osd_ass_prune_delay * 1000.0); |
138 | 1.05k | #endif |
139 | 1.05k | ass_set_cache_limits(ass->render, opts->osd_glyph_limit, opts->osd_bitmap_max_size); |
140 | 1.05k | update_playres(ass, &obj->vo_res); |
141 | 1.05k | } |
142 | | |
143 | | static int find_style(ASS_Track *track, const char *name, int def) |
144 | 1.32k | { |
145 | 2.65k | for (int n = 0; n < track->n_styles; n++) { |
146 | 2.26k | if (track->styles[n].Name && strcmp(track->styles[n].Name, name) == 0) |
147 | 941 | return n; |
148 | 2.26k | } |
149 | 386 | return def; |
150 | 1.32k | } |
151 | | |
152 | | // Find a given style, or add it if it's missing. |
153 | | static ASS_Style *get_style(struct ass_state *ass, char *name) |
154 | 1.05k | { |
155 | 1.05k | ASS_Track *track = ass->track; |
156 | 1.05k | if (!track) |
157 | 0 | return NULL; |
158 | | |
159 | 1.05k | int sid = find_style(track, name, -1); |
160 | 1.05k | if (sid >= 0) |
161 | 670 | return &track->styles[sid]; |
162 | | |
163 | 386 | sid = ass_alloc_style(track); |
164 | 386 | ASS_Style *style = &track->styles[sid]; |
165 | 386 | style->Name = strdup(name); |
166 | | // Set to neutral base direction, as opposed to VSFilter LTR default |
167 | 386 | style->Encoding = -1; |
168 | 386 | return style; |
169 | 1.05k | } |
170 | | |
171 | | static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *style, |
172 | | bstr text) |
173 | 271 | { |
174 | 271 | int n = ass_alloc_event(track); |
175 | 271 | ASS_Event *event = track->events + n; |
176 | 271 | event->Start = 0; |
177 | 271 | event->Duration = 100; |
178 | 271 | event->Style = find_style(track, style, 0); |
179 | 271 | event->ReadOrder = n; |
180 | 271 | mp_assert(event->Text == NULL); |
181 | 271 | if (text.start) { |
182 | 270 | event->Text = malloc(text.len + 1); |
183 | 270 | MP_HANDLE_OOM(event->Text); |
184 | 270 | memcpy(event->Text, text.start, text.len); |
185 | 270 | event->Text[text.len] = '\0'; |
186 | 270 | } |
187 | 271 | return event; |
188 | 271 | } |
189 | | |
190 | | static void clear_ass(struct ass_state *ass) |
191 | 346 | { |
192 | 346 | if (ass->track) |
193 | 12 | ass_flush_events(ass->track); |
194 | 346 | } |
195 | | |
196 | | void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function) |
197 | 493 | { |
198 | 493 | snprintf(buffer, buffer_size, OSD_CODEPOINTS_ESCAPE "%c", osd_function); |
199 | 493 | } |
200 | | |
201 | | void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines) |
202 | 271 | { |
203 | 271 | const char *start = in; |
204 | 271 | bool escape_ass = true; |
205 | 220k | while (*in) { |
206 | | // As used by osd_get_function_sym(). |
207 | 220k | size_t len = strlen(OSD_CODEPOINTS_ESCAPE); |
208 | 220k | if (!strncmp(in, OSD_CODEPOINTS_ESCAPE, len) && in[len]) { |
209 | 536 | bstr_xappend(NULL, dst, bstr0(ASS_USE_OSD_FONT)); |
210 | 536 | mp_append_utf8_bstr(NULL, dst, OSD_CODEPOINTS + in[len]); |
211 | 536 | bstr_xappend(NULL, dst, bstr0("{\\r}")); |
212 | 536 | in += len + 1; |
213 | 536 | continue; |
214 | 536 | } |
215 | 219k | len = strlen(OSD_ASS_0); |
216 | 219k | if (!strncmp(in, OSD_ASS_0, len)) { |
217 | 131 | escape_ass = false; |
218 | 131 | in += len; |
219 | 131 | continue; |
220 | 131 | } |
221 | 219k | len = strlen(OSD_ASS_1); |
222 | 219k | if (!strncmp(in, OSD_ASS_1, len)) { |
223 | 11 | escape_ass = true; |
224 | 11 | in += len; |
225 | 11 | continue; |
226 | 11 | } |
227 | 219k | len = strlen(TERM_MSG_0); |
228 | 219k | if (!strncmp(in, TERM_MSG_0, len)) { |
229 | 48 | in += len; |
230 | 48 | continue; |
231 | 48 | } |
232 | 219k | if (escape_ass && *in == '{') |
233 | 1.05k | bstr_xappend(NULL, dst, bstr0("\\")); |
234 | | // Replace newlines with \N for escape-ass. This is necessary to apply |
235 | | // ASS tags past newlines and to preserve consecutive newlines with |
236 | | // osd-overlay because update_external() adds a ASS event per line. |
237 | 219k | if (replace_newlines && *in == '\n') { |
238 | 0 | bstr_xappend(NULL, dst, bstr0("\\N")); |
239 | 0 | in += 1; |
240 | 0 | continue; |
241 | 0 | } |
242 | | // Libass will strip leading whitespace |
243 | 219k | if (in[0] == ' ' && (in == start || in[-1] == '\n')) { |
244 | 8.28k | bstr_xappend(NULL, dst, bstr0("\\h")); |
245 | 8.28k | in += 1; |
246 | 8.28k | continue; |
247 | 8.28k | } |
248 | 211k | bstr_xappend(NULL, dst, (bstr){(char *)in, 1}); |
249 | | // Break ASS escapes with U+2060 WORD JOINER |
250 | 211k | if (escape_ass && *in == '\\') |
251 | 577 | mp_append_utf8_bstr(NULL, dst, 0x2060); |
252 | 211k | in++; |
253 | 211k | } |
254 | 271 | } |
255 | | |
256 | | static ASS_Event *add_osd_ass_event_escaped(ASS_Track *track, const char *style, |
257 | | const char *text) |
258 | 271 | { |
259 | 271 | bstr buf = {0}; |
260 | 271 | osd_mangle_ass(&buf, text, false); |
261 | 271 | ASS_Event *e = add_osd_ass_event(track, style, buf); |
262 | 271 | talloc_free(buf.start); |
263 | 271 | return e; |
264 | 271 | } |
265 | | |
266 | | static ASS_Style *prepare_osd_ass(struct osd_state *osd, struct osd_object *obj) |
267 | 1.05k | { |
268 | 1.05k | struct mp_osd_render_opts *opts = osd->opts; |
269 | | |
270 | 1.05k | create_ass_track(osd, obj, &obj->ass); |
271 | | |
272 | 1.05k | struct osd_style_opts font = *opts->osd_style; |
273 | 1.05k | font.font_size *= opts->osd_scale; |
274 | | |
275 | 1.05k | double playresy = obj->ass.track->PlayResY; |
276 | | // Compensate for libass and mp_ass_set_style scaling the font etc. |
277 | 1.05k | if (!opts->osd_scale_by_window && obj->vo_res.h) |
278 | 0 | playresy *= 720.0 / obj->vo_res.h; |
279 | | |
280 | 1.05k | ASS_Style *style = get_style(&obj->ass, "OSD"); |
281 | 1.05k | mp_ass_set_style(style, playresy, &font); |
282 | 1.05k | return style; |
283 | 1.05k | } |
284 | | |
285 | | static void update_osd_text(struct osd_state *osd, struct osd_object *obj) |
286 | 346 | { |
287 | | |
288 | 346 | if (!obj->text[0]) |
289 | 75 | return; |
290 | | |
291 | 271 | prepare_osd_ass(osd, obj); |
292 | 271 | add_osd_ass_event_escaped(obj->ass.track, "OSD", obj->text); |
293 | 271 | } |
294 | | |
295 | | void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h) |
296 | 785 | { |
297 | 785 | mp_mutex_lock(&osd->lock); |
298 | 785 | struct osd_object *obj = osd->objs[OSDTYPE_OSD]; |
299 | 785 | ASS_Style *style = prepare_osd_ass(osd, obj); |
300 | 785 | *out_screen_h = obj->ass.track->PlayResY - style->MarginV; |
301 | 785 | *out_font_h = style->FontSize; |
302 | 785 | mp_mutex_unlock(&osd->lock); |
303 | 785 | } |
304 | | |
305 | | // align: -1 .. +1 |
306 | | // frame: size of the containing area |
307 | | // obj: size of the object that should be positioned inside the area |
308 | | // margin: min. distance from object to frame (as long as -1 <= align <= +1) |
309 | | static float get_align(float align, float frame, float obj, float margin) |
310 | 0 | { |
311 | 0 | frame -= margin * 2; |
312 | 0 | return margin + frame / 2 - obj / 2 + (frame - obj) / 2 * align; |
313 | 0 | } |
314 | | |
315 | | struct ass_draw { |
316 | | int scale; |
317 | | char *text; |
318 | | }; |
319 | | |
320 | | static void ass_draw_start(struct ass_draw *d) |
321 | 0 | { |
322 | 0 | d->scale = MPMAX(d->scale, 1); |
323 | 0 | d->text = talloc_asprintf_append(d->text, "{\\p%d}", d->scale); |
324 | 0 | } |
325 | | |
326 | | static void ass_draw_stop(struct ass_draw *d) |
327 | 0 | { |
328 | 0 | d->text = talloc_strdup_append(d->text, "{\\p0}"); |
329 | 0 | } |
330 | | |
331 | | static void ass_draw_c(struct ass_draw *d, float x, float y) |
332 | 0 | { |
333 | 0 | int ix = round(x * (1 << (d->scale - 1))); |
334 | 0 | int iy = round(y * (1 << (d->scale - 1))); |
335 | 0 | d->text = talloc_asprintf_append(d->text, " %d %d", ix, iy); |
336 | 0 | } |
337 | | |
338 | | static void ass_draw_append(struct ass_draw *d, const char *t) |
339 | 0 | { |
340 | 0 | d->text = talloc_strdup_append(d->text, t); |
341 | 0 | } |
342 | | |
343 | | static void ass_draw_move_to(struct ass_draw *d, float x, float y) |
344 | 0 | { |
345 | 0 | ass_draw_append(d, " m"); |
346 | 0 | ass_draw_c(d, x, y); |
347 | 0 | } |
348 | | |
349 | | static void ass_draw_line_to(struct ass_draw *d, float x, float y) |
350 | 0 | { |
351 | 0 | ass_draw_append(d, " l"); |
352 | 0 | ass_draw_c(d, x, y); |
353 | 0 | } |
354 | | |
355 | | static void ass_draw_rect_ccw(struct ass_draw *d, float x0, float y0, |
356 | | float x1, float y1) |
357 | 0 | { |
358 | 0 | ass_draw_move_to(d, x0, y0); |
359 | 0 | ass_draw_line_to(d, x0, y1); |
360 | 0 | ass_draw_line_to(d, x1, y1); |
361 | 0 | ass_draw_line_to(d, x1, y0); |
362 | 0 | } |
363 | | |
364 | | static void ass_draw_rect_cw(struct ass_draw *d, float x0, float y0, |
365 | | float x1, float y1) |
366 | 0 | { |
367 | 0 | ass_draw_move_to(d, x0, y0); |
368 | 0 | ass_draw_line_to(d, x1, y0); |
369 | 0 | ass_draw_line_to(d, x1, y1); |
370 | 0 | ass_draw_line_to(d, x0, y1); |
371 | 0 | } |
372 | | |
373 | | static void ass_draw_reset(struct ass_draw *d) |
374 | 0 | { |
375 | 0 | talloc_free(d->text); |
376 | 0 | d->text = NULL; |
377 | 0 | } |
378 | | |
379 | | static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj, |
380 | | float *o_x, float *o_y, float *o_w, float *o_h, |
381 | | float *o_border) |
382 | 0 | { |
383 | 0 | struct mp_osd_render_opts *opts = osd->opts; |
384 | 0 | struct osd_bar_style_opts *bar_opts = opts->osd_bar_style; |
385 | |
|
386 | 0 | create_ass_track(osd, obj, &obj->ass); |
387 | 0 | ASS_Track *track = obj->ass.track; |
388 | |
|
389 | 0 | ASS_Style *style = get_style(&obj->ass, "progbar"); |
390 | 0 | if (!style) { |
391 | 0 | *o_x = *o_y = *o_w = *o_h = *o_border = 0; |
392 | 0 | return; |
393 | 0 | } |
394 | | |
395 | 0 | mp_ass_set_style(style, track->PlayResY, opts->osd_style); |
396 | | |
397 | | // override the default osd opaque-box into plain outline. Otherwise |
398 | | // the opaque box is not aligned with the bar (even without shadow), |
399 | | // and each bar ass event gets its own opaque box - breaking the bar. |
400 | 0 | style->BorderStyle = 1; // outline |
401 | |
|
402 | 0 | *o_w = track->PlayResX * (bar_opts->w / 100.0); |
403 | 0 | *o_h = track->PlayResY * (bar_opts->h / 100.0); |
404 | |
|
405 | 0 | style->Outline = bar_opts->outline_size; |
406 | | // Rendering with shadow is broken (because there's more than one shape) |
407 | 0 | style->Shadow = 0; |
408 | 0 | style->Blur = 0; |
409 | |
|
410 | 0 | style->Alignment = 5; |
411 | |
|
412 | 0 | *o_border = style->Outline; |
413 | |
|
414 | 0 | *o_x = get_align(bar_opts->align_x, track->PlayResX, *o_w, *o_border); |
415 | 0 | *o_y = get_align(bar_opts->align_y, track->PlayResY, *o_h, *o_border); |
416 | 0 | } |
417 | | |
418 | | static void update_progbar(struct osd_state *osd, struct osd_object *obj) |
419 | 346 | { |
420 | 346 | if (obj->progbar_state.type < 0) |
421 | 346 | return; |
422 | | |
423 | 0 | float px, py, width, height, border; |
424 | 0 | get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border); |
425 | |
|
426 | 0 | ASS_Track *track = obj->ass.track; |
427 | |
|
428 | 0 | float sx = px - border * 2 - height / 4; // includes additional spacing |
429 | 0 | float sy = py + height / 2; |
430 | |
|
431 | 0 | bstr buf = bstr0(talloc_asprintf(NULL, "{\\an6\\pos(%f,%f)}", sx, sy)); |
432 | |
|
433 | 0 | if (obj->progbar_state.type == 0 || obj->progbar_state.type >= 256) { |
434 | | // no sym |
435 | 0 | } else if (obj->progbar_state.type >= 32) { |
436 | 0 | mp_append_utf8_bstr(NULL, &buf, obj->progbar_state.type); |
437 | 0 | } else { |
438 | 0 | bstr_xappend(NULL, &buf, bstr0(ASS_USE_OSD_FONT)); |
439 | 0 | mp_append_utf8_bstr(NULL, &buf, OSD_CODEPOINTS + obj->progbar_state.type); |
440 | 0 | bstr_xappend(NULL, &buf, bstr0("{\\r}")); |
441 | 0 | } |
442 | |
|
443 | 0 | add_osd_ass_event(track, "progbar", buf); |
444 | 0 | talloc_free(buf.start); |
445 | |
|
446 | 0 | struct ass_draw *d = &(struct ass_draw) { .scale = 4 }; |
447 | |
|
448 | 0 | if (osd->opts->osd_style->back_color.a && osd->opts->osd_style->border_style != 1) { |
449 | | // the bar style always ignores the --osd-back-color config - it messes |
450 | | // up the bar. draw an artificial box at the original back color. |
451 | 0 | struct m_color bc = osd->opts->osd_style->back_color; |
452 | 0 | d->text = talloc_asprintf_append(d->text, |
453 | 0 | "{\\pos(%f,%f)\\bord0\\1a&H%02X\\1c&H%02X%02X%02X&}", |
454 | 0 | px, py, 255 - bc.a, (int)bc.b, (int)bc.g, (int)bc.r); |
455 | |
|
456 | 0 | ass_draw_start(d); |
457 | 0 | ass_draw_rect_cw(d, -border, -border, width + border, height + border); |
458 | 0 | ass_draw_stop(d); |
459 | 0 | add_osd_ass_event(track, "progbar", bstr0(d->text)); |
460 | 0 | ass_draw_reset(d); |
461 | 0 | } |
462 | | |
463 | | // filled area |
464 | 0 | d->text = talloc_asprintf_append(d->text, "{\\bord0\\pos(%f,%f)}", px, py); |
465 | 0 | ass_draw_start(d); |
466 | 0 | float pos = obj->progbar_state.value * width - border / 2; |
467 | 0 | ass_draw_rect_cw(d, 0, 0, pos, height); |
468 | 0 | ass_draw_stop(d); |
469 | 0 | add_osd_ass_event(track, "progbar", bstr0(d->text)); |
470 | 0 | ass_draw_reset(d); |
471 | | |
472 | | // position marker |
473 | 0 | d->text = talloc_asprintf_append(d->text, "{\\bord%f\\pos(%f,%f)}", |
474 | 0 | border / 2, px, py); |
475 | 0 | ass_draw_start(d); |
476 | 0 | ass_draw_move_to(d, pos + border / 2, 0); |
477 | 0 | ass_draw_line_to(d, pos + border / 2, height); |
478 | 0 | ass_draw_stop(d); |
479 | 0 | add_osd_ass_event(track, "progbar", bstr0(d->text)); |
480 | 0 | ass_draw_reset(d); |
481 | |
|
482 | 0 | d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py); |
483 | 0 | ass_draw_start(d); |
484 | | |
485 | | // the box |
486 | 0 | ass_draw_rect_cw(d, -border, -border, width + border, height + border); |
487 | | |
488 | | // the "hole" |
489 | 0 | ass_draw_rect_ccw(d, 0, 0, width, height); |
490 | |
|
491 | 0 | struct osd_bar_style_opts *bar_opts = osd->opts->osd_bar_style; |
492 | | // chapter marks |
493 | 0 | if (bar_opts->marker_style) { |
494 | 0 | for (int n = 0; n < obj->progbar_state.num_stops; n++) { |
495 | 0 | float s = obj->progbar_state.stops[n] * width; |
496 | 0 | float size = MPMAX(border * bar_opts->marker_scale, |
497 | 0 | bar_opts->marker_min_size); |
498 | |
|
499 | 0 | if (bar_opts->marker_style == 2 && |
500 | 0 | s > size / 2 && s < width - size / 2) |
501 | 0 | { // line |
502 | 0 | ass_draw_rect_cw(d, s - size / 2, 0, s + size / 2, height); |
503 | 0 | } else if (s > size && s < width - size) { //triangle |
504 | 0 | ass_draw_move_to(d, s + size, 0); |
505 | 0 | ass_draw_line_to(d, s, size); |
506 | 0 | ass_draw_line_to(d, s - size, 0); |
507 | |
|
508 | 0 | ass_draw_move_to(d, s - size, height); |
509 | 0 | ass_draw_line_to(d, s, height - size); |
510 | 0 | ass_draw_line_to(d, s + size, height); |
511 | 0 | } |
512 | 0 | } |
513 | 0 | } |
514 | |
|
515 | 0 | ass_draw_stop(d); |
516 | 0 | add_osd_ass_event(track, "progbar", bstr0(d->text)); |
517 | 0 | ass_draw_reset(d); |
518 | 0 | } |
519 | | |
520 | | static void update_osd(struct osd_state *osd, struct osd_object *obj) |
521 | 346 | { |
522 | 346 | obj->osd_changed = false; |
523 | 346 | clear_ass(&obj->ass); |
524 | 346 | update_osd_text(osd, obj); |
525 | 346 | update_progbar(osd, obj); |
526 | 346 | } |
527 | | |
528 | | static void update_external(struct osd_state *osd, struct osd_object *obj, |
529 | | struct osd_external *ext) |
530 | 0 | { |
531 | 0 | bstr t = bstr0(ext->ov.data); |
532 | 0 | ext->ass.res_x = ext->ov.res_x; |
533 | 0 | ext->ass.res_y = ext->ov.res_y; |
534 | 0 | create_ass_track(osd, obj, &ext->ass); |
535 | |
|
536 | 0 | clear_ass(&ext->ass); |
537 | |
|
538 | 0 | int resy = ext->ass.track->PlayResY; |
539 | 0 | mp_ass_set_style(get_style(&ext->ass, "OSD"), resy, osd->opts->osd_style); |
540 | | |
541 | | // Some scripts will reference this style name with \r tags. |
542 | 0 | const struct osd_style_opts *def = osd_style_conf.defaults; |
543 | 0 | mp_ass_set_style(get_style(&ext->ass, "Default"), resy, def); |
544 | |
|
545 | 0 | while (t.len) { |
546 | 0 | bstr line; |
547 | 0 | bstr_split_tok(t, "\n", &line, &t); |
548 | 0 | if (line.len) |
549 | 0 | add_osd_ass_event(ext->ass.track, "OSD", line); |
550 | 0 | } |
551 | 0 | } |
552 | | |
553 | | static int cmp_zorder(const void *pa, const void *pb) |
554 | 0 | { |
555 | 0 | const struct osd_external *a = *(struct osd_external **)pa; |
556 | 0 | const struct osd_external *b = *(struct osd_external **)pb; |
557 | 0 | return a->ov.z == b->ov.z ? 0 : (a->ov.z > b->ov.z ? 1 : -1); |
558 | 0 | } |
559 | | |
560 | | void osd_set_external(struct osd_state *osd, struct osd_external_ass *ov) |
561 | 0 | { |
562 | 0 | mp_mutex_lock(&osd->lock); |
563 | 0 | struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; |
564 | 0 | bool zorder_changed = false; |
565 | 0 | int index = -1; |
566 | |
|
567 | 0 | for (int n = 0; n < obj->num_externals; n++) { |
568 | 0 | struct osd_external *e = obj->externals[n]; |
569 | 0 | if (e->ov.id == ov->id && e->ov.owner == ov->owner) { |
570 | 0 | index = n; |
571 | 0 | break; |
572 | 0 | } |
573 | 0 | } |
574 | |
|
575 | 0 | if (index < 0) { |
576 | 0 | if (!ov->format) |
577 | 0 | goto done; |
578 | 0 | struct osd_external *new = talloc_zero(NULL, struct osd_external); |
579 | 0 | new->ov.owner = ov->owner; |
580 | 0 | new->ov.id = ov->id; |
581 | 0 | MP_TARRAY_APPEND(obj, obj->externals, obj->num_externals, new); |
582 | 0 | index = obj->num_externals - 1; |
583 | 0 | zorder_changed = true; |
584 | 0 | } |
585 | | |
586 | 0 | struct osd_external *entry = obj->externals[index]; |
587 | |
|
588 | 0 | if (!ov->format) { |
589 | 0 | if (!entry->ov.hidden) { |
590 | 0 | obj->changed = true; |
591 | 0 | osd->want_redraw_notification = true; |
592 | 0 | } |
593 | 0 | destroy_external(entry); |
594 | 0 | MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index); |
595 | 0 | goto done; |
596 | 0 | } |
597 | | |
598 | 0 | if (!entry->ov.hidden || !ov->hidden) { |
599 | 0 | obj->changed = true; |
600 | 0 | osd->want_redraw_notification = true; |
601 | 0 | } |
602 | |
|
603 | 0 | entry->ov.format = ov->format; |
604 | 0 | if (!entry->ov.data) |
605 | 0 | entry->ov.data = talloc_strdup(entry, ""); |
606 | 0 | entry->ov.data[0] = '\0'; // reuse memory allocation |
607 | 0 | entry->ov.data = talloc_strdup_append(entry->ov.data, ov->data); |
608 | 0 | entry->ov.res_x = ov->res_x; |
609 | 0 | entry->ov.res_y = ov->res_y; |
610 | 0 | zorder_changed |= entry->ov.z != ov->z; |
611 | 0 | entry->ov.z = ov->z; |
612 | 0 | entry->ov.hidden = ov->hidden; |
613 | |
|
614 | 0 | update_external(osd, obj, entry); |
615 | |
|
616 | 0 | if (zorder_changed) { |
617 | 0 | qsort(obj->externals, obj->num_externals, sizeof(obj->externals[0]), |
618 | 0 | cmp_zorder); |
619 | 0 | } |
620 | |
|
621 | 0 | if (ov->out_rc) { |
622 | 0 | struct mp_osd_res vo_res = entry->ass.vo_res; |
623 | | // Defined fallback if VO has not drawn this yet |
624 | 0 | if (vo_res.w < 1 || vo_res.h < 1) { |
625 | 0 | vo_res = (struct mp_osd_res){ |
626 | 0 | .w = entry->ov.res_x, |
627 | 0 | .h = entry->ov.res_y, |
628 | 0 | .display_par = 1, |
629 | 0 | }; |
630 | | // According to osd-overlay command description. |
631 | 0 | if (vo_res.w < 1) |
632 | 0 | vo_res.w = 1280; |
633 | 0 | if (vo_res.h < 1) |
634 | 0 | vo_res.h = 720; |
635 | 0 | } |
636 | |
|
637 | 0 | ASS_Image *img_list = NULL; |
638 | 0 | append_ass(&entry->ass, &vo_res, &img_list, NULL); |
639 | |
|
640 | 0 | mp_ass_get_bb(img_list, entry->ass.track, &vo_res, ov->out_rc); |
641 | 0 | } |
642 | |
|
643 | 0 | done: |
644 | 0 | mp_mutex_unlock(&osd->lock); |
645 | 0 | } |
646 | | |
647 | | void osd_set_external_remove_owner(struct osd_state *osd, void *owner) |
648 | 274k | { |
649 | 274k | mp_mutex_lock(&osd->lock); |
650 | 274k | struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; |
651 | 274k | for (int n = obj->num_externals - 1; n >= 0; n--) { |
652 | 0 | struct osd_external *e = obj->externals[n]; |
653 | 0 | if (e->ov.owner == owner) { |
654 | 0 | destroy_external(e); |
655 | 0 | MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, n); |
656 | 0 | obj->changed = true; |
657 | 0 | osd->want_redraw_notification = true; |
658 | 0 | } |
659 | 0 | } |
660 | 274k | mp_mutex_unlock(&osd->lock); |
661 | 274k | } |
662 | | |
663 | | static void append_ass(struct ass_state *ass, struct mp_osd_res *res, |
664 | | ASS_Image **img_list, bool *changed) |
665 | 994 | { |
666 | 994 | if (!ass->render || !ass->track) { |
667 | 723 | *img_list = NULL; |
668 | 723 | return; |
669 | 723 | } |
670 | | |
671 | 271 | update_playres(ass, res); |
672 | | |
673 | 271 | ass_set_frame_size(ass->render, res->w, res->h); |
674 | 271 | ass_set_pixel_aspect(ass->render, res->display_par); |
675 | | |
676 | 271 | int ass_changed; |
677 | 271 | *img_list = ass_render_frame(ass->render, ass->track, 0, &ass_changed); |
678 | | |
679 | 271 | ass->changed |= ass_changed; |
680 | | |
681 | 271 | if (changed) { |
682 | 271 | *changed |= ass->changed; |
683 | 271 | ass->changed = false; |
684 | 271 | } |
685 | 271 | } |
686 | | |
687 | | struct sub_bitmaps *osd_object_get_bitmaps(struct osd_state *osd, |
688 | | struct osd_object *obj, int format) |
689 | 1.29k | { |
690 | 1.29k | if (obj->type == OSDTYPE_OSD) { |
691 | 648 | if (obj->osd_changed) { |
692 | 346 | update_osd(osd, obj); |
693 | 346 | } else { |
694 | 302 | mp_require(obj->sub_packer); |
695 | 302 | goto done; |
696 | 302 | } |
697 | 648 | } |
698 | | |
699 | 994 | if (!obj->sub_packer) |
700 | 660 | obj->sub_packer = mp_sub_packer_alloc(obj); |
701 | | |
702 | 994 | MP_TARRAY_GROW(obj, obj->ass_imgs, obj->num_externals + 1); |
703 | | |
704 | 994 | append_ass(&obj->ass, &obj->vo_res, &obj->ass_imgs[0], &obj->changed); |
705 | 994 | for (int n = 0; n < obj->num_externals; n++) { |
706 | 0 | if (obj->externals[n]->ov.hidden) { |
707 | 0 | update_playres(&obj->externals[n]->ass, &obj->vo_res); |
708 | 0 | obj->ass_imgs[n + 1] = NULL; |
709 | 0 | } else { |
710 | 0 | append_ass(&obj->externals[n]->ass, &obj->vo_res, |
711 | 0 | &obj->ass_imgs[n + 1], &obj->changed); |
712 | 0 | } |
713 | 0 | } |
714 | | |
715 | 1.29k | done:; |
716 | 1.29k | struct sub_bitmaps out_imgs = {0}; |
717 | 1.29k | mp_sub_packer_pack_ass(obj->sub_packer, obj->ass_imgs, obj->num_externals + 1, |
718 | 1.29k | obj->changed, false, format, &out_imgs); |
719 | | |
720 | 1.29k | obj->changed = false; |
721 | | |
722 | 1.29k | return sub_bitmaps_copy(&obj->copy_cache, &out_imgs); |
723 | 994 | } |