mirror of
https://github.com/mpv-player/mpv
synced 2024-12-16 03:45:23 +00:00
5a89150a46
This was used with --no-sub-ass (aka --no-ass). This option (which is not yet removed) strips all styling from the subtitles, and renders them as plaintext only. For some reason, it originally seemed convenient to reuse all the OSD text rendering code (osd_libass.c). While this was indeed simple, it had a bad influence on the rest of the code. For example, it had to decide whether to go through the OSD code path, or the proper subtitle renderer in sd_ass.c. Kill the OSD subtitle renderer. Reimplement --no-sub-ass and also "secondary" subtitles in sd_ass.c. fill_plaintext() contains some rather minor code duplication with osd_libass.c for setting up a dummy ASS_Event and escaping the stripped text. Since sd_ass.c already has to handle "normal" text subtitles, and has code for stripping ASS tags, this remains all relatively simple. Remove all the unnecessary crap from the rest of the code.
467 lines
14 KiB
C
467 lines
14 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include <libavutil/common.h>
|
|
|
|
#include "config.h"
|
|
|
|
#include "talloc.h"
|
|
#include "misc/bstr.h"
|
|
#include "common/common.h"
|
|
#include "common/msg.h"
|
|
#include "osd.h"
|
|
#include "osd_state.h"
|
|
|
|
static const char osd_font_pfb[] =
|
|
#include "sub/osd_font.h"
|
|
;
|
|
|
|
#include "sub/ass_mp.h"
|
|
#include "options/options.h"
|
|
|
|
|
|
#define ASS_USE_OSD_FONT "{\\fnmpv-osd-symbols}"
|
|
|
|
void osd_init_backend(struct osd_state *osd)
|
|
{
|
|
}
|
|
|
|
static void create_ass_renderer(struct osd_state *osd, struct osd_object *obj)
|
|
{
|
|
if (obj->osd_render)
|
|
return;
|
|
|
|
struct mp_log *ass_log = mp_log_new(obj, osd->log, "libass");
|
|
obj->osd_ass_library = mp_ass_init(osd->global, ass_log);
|
|
ass_add_font(obj->osd_ass_library, "mpv-osd-symbols", (void *)osd_font_pfb,
|
|
sizeof(osd_font_pfb) - 1);
|
|
|
|
obj->osd_render = ass_renderer_init(obj->osd_ass_library);
|
|
if (!obj->osd_render)
|
|
abort();
|
|
|
|
mp_ass_configure_fonts(obj->osd_render, osd->opts->osd_style,
|
|
osd->global, ass_log);
|
|
ass_set_aspect_ratio(obj->osd_render, 1.0, 1.0);
|
|
}
|
|
|
|
void osd_destroy_backend(struct osd_state *osd)
|
|
{
|
|
for (int n = 0; n < MAX_OSD_PARTS; n++) {
|
|
struct osd_object *obj = osd->objs[n];
|
|
if (obj->osd_track)
|
|
ass_free_track(obj->osd_track);
|
|
obj->osd_track = NULL;
|
|
if (obj->osd_render)
|
|
ass_renderer_done(obj->osd_render);
|
|
obj->osd_render = NULL;
|
|
if (obj->osd_ass_library)
|
|
ass_library_done(obj->osd_ass_library);
|
|
obj->osd_ass_library = NULL;
|
|
}
|
|
}
|
|
|
|
static void create_ass_track(struct osd_state *osd, struct osd_object *obj,
|
|
int res_x, int res_y)
|
|
{
|
|
create_ass_renderer(osd, obj);
|
|
|
|
ASS_Track *track = obj->osd_track;
|
|
if (!track)
|
|
track = ass_new_track(obj->osd_ass_library);
|
|
|
|
int old_res_x = track->PlayResX;
|
|
int old_res_y = track->PlayResY;
|
|
|
|
double aspect = 1.0 * obj->vo_res.w / FFMAX(obj->vo_res.h, 1) /
|
|
obj->vo_res.display_par;
|
|
|
|
track->track_type = TRACK_TYPE_ASS;
|
|
track->Timer = 100.;
|
|
track->PlayResY = res_y ? res_y : MP_ASS_FONT_PLAYRESY;
|
|
track->PlayResX = res_x ? res_x : track->PlayResY * aspect;
|
|
track->WrapStyle = 1; // end-of-line wrapping instead of smart wrapping
|
|
track->Kerning = true;
|
|
|
|
// Force libass to clear its internal cache - it doesn't check for
|
|
// PlayRes changes itself.
|
|
if (old_res_x != track->PlayResX || old_res_y != track->PlayResY)
|
|
ass_set_frame_size(obj->osd_render, 1, 1);
|
|
|
|
if (track->n_styles < 2) {
|
|
int sid = ass_alloc_style(track);
|
|
track->default_style = sid;
|
|
ASS_Style *style = track->styles + sid;
|
|
style->Name = strdup("OSD");
|
|
// Set to neutral base direction, as opposed to VSFilter LTR default
|
|
style->Encoding = -1;
|
|
|
|
sid = ass_alloc_style(track);
|
|
assert(sid == track->default_style + 1);
|
|
style = track->styles + sid;
|
|
style->Name = strdup("Default");
|
|
style->Encoding = -1;
|
|
}
|
|
|
|
ASS_Style *s_osd = track->styles + track->default_style;
|
|
mp_ass_set_style(s_osd, track->PlayResY, osd->opts->osd_style);
|
|
|
|
ASS_Style *s_def = track->styles + track->default_style + 1;
|
|
const struct osd_style_opts *def = osd_style_conf.defaults;
|
|
mp_ass_set_style(s_def, track->PlayResY, def);
|
|
|
|
obj->osd_track = track;
|
|
}
|
|
|
|
static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *text)
|
|
{
|
|
int n = ass_alloc_event(track);
|
|
ASS_Event *event = track->events + n;
|
|
event->Start = 0;
|
|
event->Duration = 100;
|
|
event->Style = track->default_style;
|
|
event->ReadOrder = n;
|
|
assert(event->Text == NULL);
|
|
if (text)
|
|
event->Text = strdup(text);
|
|
return event;
|
|
}
|
|
|
|
static void clear_obj(struct osd_object *obj)
|
|
{
|
|
if (obj->osd_track)
|
|
ass_flush_events(obj->osd_track);
|
|
}
|
|
|
|
void osd_get_function_sym(char *buffer, size_t buffer_size, int osd_function)
|
|
{
|
|
// 0xFF is never valid UTF-8, so we can use it to escape OSD symbols.
|
|
snprintf(buffer, buffer_size, "\xFF%c", osd_function);
|
|
}
|
|
|
|
// Same trick as above: never valid UTF-8, so we expect it's free for use.
|
|
const char *const osd_ass_0 = "\xFD";
|
|
const char *const osd_ass_1 = "\xFE";
|
|
|
|
static void mangle_ass(bstr *dst, const char *in)
|
|
{
|
|
bool escape_ass = true;
|
|
while (*in) {
|
|
// As used by osd_get_function_sym().
|
|
if (in[0] == '\xFF' && in[1]) {
|
|
bstr_xappend(NULL, dst, bstr0(ASS_USE_OSD_FONT));
|
|
mp_append_utf8_bstr(NULL, dst, OSD_CODEPOINTS + in[1]);
|
|
bstr_xappend(NULL, dst, bstr0("{\\r}"));
|
|
in += 2;
|
|
continue;
|
|
}
|
|
if (*in == '\xFD' || *in == '\xFE') {
|
|
escape_ass = *in == '\xFE';
|
|
in += 1;
|
|
continue;
|
|
}
|
|
if (escape_ass && *in == '{')
|
|
bstr_xappend(NULL, dst, bstr0("\\"));
|
|
bstr_xappend(NULL, dst, (bstr){(char *)in, 1});
|
|
// Break ASS escapes with U+2060 WORD JOINER
|
|
if (escape_ass && *in == '\\')
|
|
mp_append_utf8_bstr(NULL, dst, 0x2060);
|
|
in++;
|
|
}
|
|
}
|
|
|
|
static void add_osd_ass_event_escaped(ASS_Track *track, const char *text)
|
|
{
|
|
bstr buf = {0};
|
|
mangle_ass(&buf, text);
|
|
add_osd_ass_event(track, buf.start);
|
|
talloc_free(buf.start);
|
|
}
|
|
|
|
static void update_osd(struct osd_state *osd, struct osd_object *obj)
|
|
{
|
|
struct MPOpts *opts = osd->opts;
|
|
|
|
clear_obj(obj);
|
|
if (!obj->text[0])
|
|
return;
|
|
|
|
create_ass_track(osd, obj, 0, 0);
|
|
|
|
struct osd_style_opts font = *opts->osd_style;
|
|
font.font_size *= opts->osd_scale;
|
|
|
|
double playresy = obj->osd_track->PlayResY;
|
|
// Compensate for libass and mp_ass_set_style scaling the font etc.
|
|
if (!opts->osd_scale_by_window)
|
|
playresy *= 720.0 / obj->vo_res.h;
|
|
|
|
ASS_Style *style = obj->osd_track->styles + obj->osd_track->default_style;
|
|
mp_ass_set_style(style, playresy, &font);
|
|
|
|
add_osd_ass_event_escaped(obj->osd_track, obj->text);
|
|
}
|
|
|
|
// align: -1 .. +1
|
|
// frame: size of the containing area
|
|
// obj: size of the object that should be positioned inside the area
|
|
// margin: min. distance from object to frame (as long as -1 <= align <= +1)
|
|
static float get_align(float align, float frame, float obj, float margin)
|
|
{
|
|
frame -= margin * 2;
|
|
return margin + frame / 2 - obj / 2 + (frame - obj) / 2 * align;
|
|
}
|
|
|
|
struct ass_draw {
|
|
int scale;
|
|
char *text;
|
|
};
|
|
|
|
static void ass_draw_start(struct ass_draw *d)
|
|
{
|
|
d->scale = FFMAX(d->scale, 1);
|
|
d->text = talloc_asprintf_append(d->text, "{\\p%d}", d->scale);
|
|
}
|
|
|
|
static void ass_draw_stop(struct ass_draw *d)
|
|
{
|
|
d->text = talloc_strdup_append(d->text, "{\\p0}");
|
|
}
|
|
|
|
static void ass_draw_c(struct ass_draw *d, float x, float y)
|
|
{
|
|
int ix = round(x * (1 << (d->scale - 1)));
|
|
int iy = round(y * (1 << (d->scale - 1)));
|
|
d->text = talloc_asprintf_append(d->text, " %d %d", ix, iy);
|
|
}
|
|
|
|
static void ass_draw_append(struct ass_draw *d, const char *t)
|
|
{
|
|
d->text = talloc_strdup_append(d->text, t);
|
|
}
|
|
|
|
static void ass_draw_move_to(struct ass_draw *d, float x, float y)
|
|
{
|
|
ass_draw_append(d, " m");
|
|
ass_draw_c(d, x, y);
|
|
}
|
|
|
|
static void ass_draw_line_to(struct ass_draw *d, float x, float y)
|
|
{
|
|
ass_draw_append(d, " l");
|
|
ass_draw_c(d, x, y);
|
|
}
|
|
|
|
static void ass_draw_rect_ccw(struct ass_draw *d, float x0, float y0,
|
|
float x1, float y1)
|
|
{
|
|
ass_draw_move_to(d, x0, y0);
|
|
ass_draw_line_to(d, x0, y1);
|
|
ass_draw_line_to(d, x1, y1);
|
|
ass_draw_line_to(d, x1, y0);
|
|
}
|
|
|
|
static void ass_draw_rect_cw(struct ass_draw *d, float x0, float y0,
|
|
float x1, float y1)
|
|
{
|
|
ass_draw_move_to(d, x0, y0);
|
|
ass_draw_line_to(d, x1, y0);
|
|
ass_draw_line_to(d, x1, y1);
|
|
ass_draw_line_to(d, x0, y1);
|
|
}
|
|
|
|
static void ass_draw_reset(struct ass_draw *d)
|
|
{
|
|
talloc_free(d->text);
|
|
d->text = NULL;
|
|
}
|
|
|
|
static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
|
|
float *o_x, float *o_y, float *o_w, float *o_h,
|
|
float *o_border)
|
|
{
|
|
struct MPOpts *opts = osd->opts;
|
|
|
|
create_ass_track(osd, obj, 0, 0);
|
|
ASS_Track *track = obj->osd_track;
|
|
ASS_Style *style = track->styles + track->default_style;
|
|
|
|
*o_w = track->PlayResX * (opts->osd_bar_w / 100.0);
|
|
*o_h = track->PlayResY * (opts->osd_bar_h / 100.0);
|
|
|
|
float base_size = 0.03125;
|
|
style->Outline *= *o_h / track->PlayResY / base_size;
|
|
// So that the chapter marks have space between them
|
|
style->Outline = FFMIN(style->Outline, *o_h / 5.2);
|
|
// So that the border is not 0
|
|
style->Outline = FFMAX(style->Outline, *o_h / 32.0);
|
|
// Rendering with shadow is broken (because there's more than one shape)
|
|
style->Shadow = 0;
|
|
|
|
style->Alignment = 5;
|
|
|
|
*o_border = style->Outline;
|
|
|
|
*o_x = get_align(opts->osd_bar_align_x, track->PlayResX, *o_w, *o_border);
|
|
*o_y = get_align(opts->osd_bar_align_y, track->PlayResY, *o_h, *o_border);
|
|
}
|
|
|
|
static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
|
{
|
|
clear_obj(obj);
|
|
|
|
if (obj->progbar_state.type < 0)
|
|
return;
|
|
|
|
float px, py, width, height, border;
|
|
get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border);
|
|
|
|
float sx = px - border * 2 - height / 4; // includes additional spacing
|
|
float sy = py + height / 2;
|
|
|
|
bstr buf = bstr0(talloc_asprintf(NULL, "{\\an6\\pos(%f,%f)}", sx, sy));
|
|
|
|
if (obj->progbar_state.type == 0 || obj->progbar_state.type >= 256) {
|
|
// no sym
|
|
} else if (obj->progbar_state.type >= 32) {
|
|
mp_append_utf8_bstr(NULL, &buf, obj->progbar_state.type);
|
|
} else {
|
|
bstr_xappend(NULL, &buf, bstr0(ASS_USE_OSD_FONT));
|
|
mp_append_utf8_bstr(NULL, &buf, OSD_CODEPOINTS + obj->progbar_state.type);
|
|
bstr_xappend(NULL, &buf, bstr0("{\\r}"));
|
|
}
|
|
|
|
add_osd_ass_event(obj->osd_track, buf.start);
|
|
talloc_free(buf.start);
|
|
|
|
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
|
|
// filled area
|
|
d->text = talloc_asprintf_append(d->text, "{\\bord0\\pos(%f,%f)}", px, py);
|
|
ass_draw_start(d);
|
|
float pos = obj->progbar_state.value * width - border / 2;
|
|
ass_draw_rect_cw(d, 0, 0, pos, height);
|
|
ass_draw_stop(d);
|
|
add_osd_ass_event(obj->osd_track, d->text);
|
|
ass_draw_reset(d);
|
|
|
|
// position marker
|
|
d->text = talloc_asprintf_append(d->text, "{\\bord%f\\pos(%f,%f)}",
|
|
border / 2, px, py);
|
|
ass_draw_start(d);
|
|
ass_draw_move_to(d, pos + border / 2, 0);
|
|
ass_draw_line_to(d, pos + border / 2, height);
|
|
ass_draw_stop(d);
|
|
add_osd_ass_event(obj->osd_track, d->text);
|
|
ass_draw_reset(d);
|
|
|
|
d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py);
|
|
ass_draw_start(d);
|
|
|
|
// the box
|
|
ass_draw_rect_cw(d, -border, -border, width + border, height + border);
|
|
|
|
// the "hole"
|
|
ass_draw_rect_ccw(d, 0, 0, width, height);
|
|
|
|
// chapter marks
|
|
for (int n = 0; n < obj->progbar_state.num_stops; n++) {
|
|
float s = obj->progbar_state.stops[n] * width;
|
|
float dent = border * 1.3;
|
|
|
|
if (s > dent && s < width - dent) {
|
|
ass_draw_move_to(d, s + dent, 0);
|
|
ass_draw_line_to(d, s, dent);
|
|
ass_draw_line_to(d, s - dent, 0);
|
|
|
|
ass_draw_move_to(d, s - dent, height);
|
|
ass_draw_line_to(d, s, height - dent);
|
|
ass_draw_line_to(d, s + dent, height);
|
|
}
|
|
}
|
|
|
|
ass_draw_stop(d);
|
|
add_osd_ass_event(obj->osd_track, d->text);
|
|
ass_draw_reset(d);
|
|
}
|
|
|
|
static void update_external(struct osd_state *osd, struct osd_object *obj)
|
|
{
|
|
clear_obj(obj);
|
|
|
|
bstr t = bstr0(obj->text);
|
|
if (!t.len)
|
|
return;
|
|
create_ass_track(osd, obj, obj->external_res_x, obj->external_res_y);
|
|
|
|
while (t.len) {
|
|
bstr line;
|
|
bstr_split_tok(t, "\n", &line, &t);
|
|
if (line.len) {
|
|
char *tmp = bstrdup0(NULL, line);
|
|
add_osd_ass_event(obj->osd_track, tmp);
|
|
talloc_free(tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_object(struct osd_state *osd, struct osd_object *obj)
|
|
{
|
|
switch (obj->type) {
|
|
case OSDTYPE_OSD:
|
|
update_osd(osd, obj);
|
|
break;
|
|
case OSDTYPE_PROGBAR:
|
|
update_progbar(osd, obj);
|
|
break;
|
|
case OSDTYPE_EXTERNAL:
|
|
update_external(osd, obj);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj,
|
|
struct sub_bitmaps *out_imgs)
|
|
{
|
|
if (obj->force_redraw)
|
|
update_object(osd, obj);
|
|
|
|
*out_imgs = (struct sub_bitmaps) {0};
|
|
if (!obj->osd_track)
|
|
return;
|
|
|
|
ass_set_frame_size(obj->osd_render, obj->vo_res.w, obj->vo_res.h);
|
|
ass_set_aspect_ratio(obj->osd_render, obj->vo_res.display_par, 1.0);
|
|
mp_ass_render_frame(obj->osd_render, obj->osd_track, 0,
|
|
&obj->parts_cache, out_imgs);
|
|
talloc_steal(obj, obj->parts_cache);
|
|
}
|
|
|
|
void osd_object_get_resolution(struct osd_state *osd, int obj,
|
|
int *out_w, int *out_h)
|
|
{
|
|
pthread_mutex_lock(&osd->lock);
|
|
struct osd_object *osd_obj = osd->objs[obj];
|
|
*out_w = osd_obj->osd_track ? osd_obj->osd_track->PlayResX : 0;
|
|
*out_h = osd_obj->osd_track ? osd_obj->osd_track->PlayResY : 0;
|
|
pthread_mutex_unlock(&osd->lock);
|
|
}
|