mirror of
https://github.com/mpv-player/mpv
synced 2025-02-16 20:27:23 +00:00
osd: draw the OSD bar with ASS vector drawings
Drawing the bar with vector drawings (instead with characters from the OSD font) offers more flexibility and looks better. This also adds chapter marks to the OSD bar, which are visible as small triangles on the top and bottom inner border of the bar. Change the default position of the OSD bar below the center of the screen. This is less annoying than putting the bar directly into the center of the view, where it obscures the video. The new position is not quite on the bottom of the screen to avoid collisions with subtitles. The old centered position can be forced with ``--osd-bar-align-y=0``. Also make it possible to change the OSD bar width/height with the new --osd-bar-w and --osd-bar-h options. It's possible that the new OSD bar renders much slower than the old one. There are two reasons for this: 1. the character based bar allowed libass to cache each character, while the vector drawing forces it to redraw every time the bar position changes. 2., the bar position is updated at a much higher granularity (the bar position is passed along as float instead of as integer in the range 0-100, so the bar will be updated on every single video frame).
This commit is contained in:
parent
d39b131bde
commit
ef3c0e6eda
@ -1315,12 +1315,19 @@
|
||||
prefixes, see ``Input command prefixes``. If you want to disable the OSD
|
||||
completely, use ``--osd-level=0``.
|
||||
|
||||
--osd-bar-align-x=<-1..1>
|
||||
--osd-bar-align-x=<-1-1>
|
||||
Position of the OSD bar. -1 is far left, 0 is centered, 1 is far right.
|
||||
|
||||
--osd-bar-align-y=<-1..1>
|
||||
--osd-bar-align-y=<-1-1>
|
||||
Position of the OSD bar. -1 is top, 0 is centered, 1 is bottom.
|
||||
|
||||
--osd-bar-w=<1-100>
|
||||
Width of the OSD bar, in percentage of the screen width (default: 75).
|
||||
A value of 0.5 means the bar is half the screen wide.
|
||||
|
||||
--osd-bar-h=<0.1-50>
|
||||
Height of the OSD bar, in percentage of the screen height (default: 3.125).
|
||||
|
||||
--osd-back-color=<#RRGGBB>, --sub-text-back-color=<#RRGGBB>
|
||||
See ``--osd-color``. Color used for OSD/sub text background.
|
||||
|
||||
|
@ -501,6 +501,8 @@ const m_option_t common_opts[] = {
|
||||
OPT_FLAG("osd-bar", osd_bar_visible, 0),
|
||||
OPT_FLOATRANGE("osd-bar-align-x", osd_bar_align_x, 0, -1.0, +1.0),
|
||||
OPT_FLOATRANGE("osd-bar-align-y", osd_bar_align_y, 0, -1.0, +1.0),
|
||||
OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
|
||||
OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
|
||||
OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
|
||||
OPT_SUBSTRUCT("sub-text", sub_text_style, osd_style_conf, 0),
|
||||
{NULL, NULL, 0, 0, 0, 0, NULL}
|
||||
|
@ -43,6 +43,9 @@ void set_default_mplayer_options(struct MPOpts *opts)
|
||||
.gamma_hue = 1000,
|
||||
.osd_level = 1,
|
||||
.osd_duration = 1000,
|
||||
.osd_bar_align_y = 0.5,
|
||||
.osd_bar_w = 75.0,
|
||||
.osd_bar_h = 3.125,
|
||||
.loop_times = -1,
|
||||
.ordered_chapters = 1,
|
||||
.chapter_merge_threshold = 100,
|
||||
|
@ -1330,7 +1330,8 @@ void set_osd_bar(struct MPContext *mpctx, int type, const char *name,
|
||||
if (mpctx->sh_video && opts->term_osd != 1) {
|
||||
mpctx->osd_visible = (GetTimerMS() + opts->osd_duration) | 1;
|
||||
mpctx->osd->progbar_type = type;
|
||||
mpctx->osd->progbar_value = 256 * (val - min) / (max - min);
|
||||
mpctx->osd->progbar_value = (val - min) / (max - min);
|
||||
mpctx->osd->progbar_num_stops = 0;
|
||||
vo_osd_changed(OSDTYPE_PROGBAR);
|
||||
return;
|
||||
}
|
||||
@ -1345,7 +1346,7 @@ static void update_osd_bar(struct MPContext *mpctx, int type,
|
||||
double min, double max, double val)
|
||||
{
|
||||
if (mpctx->osd->progbar_type == type) {
|
||||
int new_value = 256 * (val - min) / (max - min);
|
||||
float new_value = (val - min) / (max - min);
|
||||
if (new_value != mpctx->osd->progbar_value) {
|
||||
mpctx->osd->progbar_value = new_value;
|
||||
vo_osd_changed(OSDTYPE_PROGBAR);
|
||||
@ -1353,6 +1354,26 @@ static void update_osd_bar(struct MPContext *mpctx, int type,
|
||||
}
|
||||
}
|
||||
|
||||
static void set_osd_bar_chapters(struct MPContext *mpctx, int type)
|
||||
{
|
||||
struct osd_state *osd = mpctx->osd;
|
||||
osd->progbar_num_stops = 0;
|
||||
if (osd->progbar_type == type) {
|
||||
double len = get_time_length(mpctx);
|
||||
if (len > 0) {
|
||||
int num = get_chapter_count(mpctx);
|
||||
for (int n = 0; n < num; n++) {
|
||||
double time = chapter_start_time(mpctx, n);
|
||||
if (time >= 0) {
|
||||
float pos = time / len;
|
||||
MP_TARRAY_APPEND(osd, osd->progbar_stops,
|
||||
osd->progbar_num_stops, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_osd_function(struct MPContext *mpctx, int osd_function)
|
||||
{
|
||||
struct MPOpts *opts = &mpctx->opts;
|
||||
@ -1432,6 +1453,7 @@ static void add_seek_osd_messages(struct MPContext *mpctx)
|
||||
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_BAR) {
|
||||
set_osd_bar(mpctx, OSD_BAR_SEEK, "Position", 0, 1,
|
||||
av_clipf(get_current_pos_ratio(mpctx), 0, 1));
|
||||
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
|
||||
}
|
||||
if (mpctx->add_osd_seek_info & OSD_SEEK_INFO_TEXT) {
|
||||
mp_osd_msg_t *msg = add_osd_msg(mpctx, OSD_MSG_TEXT, 1,
|
||||
|
@ -161,6 +161,8 @@ typedef struct MPOpts {
|
||||
int osd_bar_visible;
|
||||
float osd_bar_align_x;
|
||||
float osd_bar_align_y;
|
||||
float osd_bar_w;
|
||||
float osd_bar_h;
|
||||
struct osd_style_opts *osd_style;
|
||||
struct osd_style_opts *sub_text_style;
|
||||
float sub_scale;
|
||||
|
232
sub/osd_libass.c
232
sub/osd_libass.c
@ -61,14 +61,19 @@ void osd_destroy_backend(struct osd_state *osd)
|
||||
osd->osd_ass_library = NULL;
|
||||
}
|
||||
|
||||
static ASS_Track *create_osd_ass_track(struct osd_state *osd)
|
||||
static void create_osd_ass_track(struct osd_state *osd, struct osd_object *obj)
|
||||
{
|
||||
ASS_Track *track = ass_new_track(osd->osd_ass_library);
|
||||
ASS_Track *track = obj->osd_track;
|
||||
if (!track)
|
||||
track = ass_new_track(osd->osd_ass_library);
|
||||
|
||||
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 = MP_ASS_FONT_PLAYRESY;
|
||||
track->PlayResX = track->PlayResY * 1.33333;
|
||||
track->PlayResX = track->PlayResY * aspect;
|
||||
track->WrapStyle = 1; // end-of-line wrapping instead of smart wrapping
|
||||
|
||||
if (track->n_styles == 0) {
|
||||
@ -83,18 +88,19 @@ static ASS_Track *create_osd_ass_track(struct osd_state *osd)
|
||||
style->Encoding = -1;
|
||||
}
|
||||
|
||||
return track;
|
||||
obj->osd_track = track;
|
||||
}
|
||||
|
||||
static ASS_Event *get_osd_ass_event(ASS_Track *track)
|
||||
static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *text)
|
||||
{
|
||||
ass_flush_events(track);
|
||||
ass_alloc_event(track);
|
||||
ASS_Event *event = track->events + 0;
|
||||
int n = ass_alloc_event(track);
|
||||
ASS_Event *event = track->events + n;
|
||||
event->Start = 0;
|
||||
event->Duration = 100;
|
||||
event->Style = track->default_style;
|
||||
assert(event->Text == NULL);
|
||||
if (text)
|
||||
event->Text = strdup(text);
|
||||
return event;
|
||||
}
|
||||
|
||||
@ -135,67 +141,135 @@ static char *mangle_ass(const char *in)
|
||||
|
||||
static void update_osd(struct osd_state *osd, struct osd_object *obj)
|
||||
{
|
||||
if (!osd->osd_text[0]) {
|
||||
clear_obj(obj);
|
||||
create_osd_ass_track(osd, obj);
|
||||
clear_obj(obj);
|
||||
if (!osd->osd_text[0])
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obj->osd_track)
|
||||
obj->osd_track = create_osd_ass_track(osd);
|
||||
ASS_Event *event = get_osd_ass_event(obj->osd_track);
|
||||
char *text = mangle_ass(osd->osd_text);
|
||||
event->Text = strdup(text);
|
||||
add_osd_ass_event(obj->osd_track, text);
|
||||
talloc_free(text);
|
||||
}
|
||||
|
||||
static int get_align(float val, int res, int *out_margin)
|
||||
// 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)
|
||||
{
|
||||
*out_margin = FFMAX(0, (1.0 - fabs(val)) * res / 2);
|
||||
if (fabs(val) < 0.1)
|
||||
return 1; // centered
|
||||
return val > 0 ? 2 : 0; // bottom / top (or right / left)
|
||||
frame -= margin * 2;
|
||||
return margin + frame / 2 - obj / 2 + (frame - obj) / 2 * align;
|
||||
}
|
||||
|
||||
static const int ass_align_x[3] = {1, 2, 3};
|
||||
static const int ass_align_y[3] = {4, 8, 0};
|
||||
struct ass_draw {
|
||||
int scale;
|
||||
char *text;
|
||||
};
|
||||
|
||||
#define OSDBAR_ELEMS 46
|
||||
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 update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
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;
|
||||
|
||||
if (osd->progbar_type < 0) {
|
||||
clear_obj(obj);
|
||||
return;
|
||||
bool new_track = !obj->osd_track;
|
||||
create_osd_ass_track(osd, obj);
|
||||
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);
|
||||
|
||||
if (new_track) {
|
||||
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;
|
||||
}
|
||||
|
||||
if (!obj->osd_track)
|
||||
obj->osd_track = create_osd_ass_track(osd);
|
||||
*o_border = style->Outline;
|
||||
|
||||
ASS_Style *style = obj->osd_track->styles + obj->osd_track->default_style;
|
||||
*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);
|
||||
}
|
||||
|
||||
int ax = get_align(opts->osd_bar_align_x, obj->osd_track->PlayResX,
|
||||
&style->MarginR);
|
||||
int ay = get_align(opts->osd_bar_align_y, obj->osd_track->PlayResY,
|
||||
&style->MarginV);
|
||||
style->Alignment = ass_align_x[ax] + ass_align_y[ay];
|
||||
style->MarginL = style->MarginR;
|
||||
static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
{
|
||||
float px, py, width, height, border;
|
||||
get_osd_bar_box(osd, obj, &px, &py, &width, &height, &border);
|
||||
|
||||
// We need a fixed font size with respect to the OSD width.
|
||||
// Assume the OSD bar takes 2/3 of the OSD width at PlayResY=288 and
|
||||
// FontSize=22 with an OSD aspect ratio of 16:9. Rescale as needed.
|
||||
// xxx can fail when unknown fonts are involved
|
||||
double asp = (double)obj->vo_res.w / obj->vo_res.h;
|
||||
double scale = (asp / 1.77777) * (obj->osd_track->PlayResY / 288.0);
|
||||
style->ScaleX = style->ScaleY = scale;
|
||||
style->FontSize = 22.0;
|
||||
style->Outline = style->FontSize / 16 * scale;
|
||||
clear_obj(obj);
|
||||
|
||||
int active = (osd->progbar_value * OSDBAR_ELEMS + 255) / 256;
|
||||
active = FFMIN(OSDBAR_ELEMS, FFMAX(active, 0));
|
||||
if (osd->progbar_type < 0)
|
||||
return;
|
||||
|
||||
char *text = talloc_strdup(NULL, "{\\q2}");
|
||||
float sx = px - border * 2 - height / 4; // includes additional spacing
|
||||
float sy = py + height / 2;
|
||||
|
||||
char *text = talloc_asprintf(NULL, "{\\an6\\pos(%f,%f)}", sx, sy);
|
||||
|
||||
if (osd->progbar_type == 0 || osd->progbar_type >= 256) {
|
||||
// no sym
|
||||
@ -207,30 +281,57 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
|
||||
text = talloc_strdup_append_buffer(text, "{\\r}");
|
||||
}
|
||||
|
||||
//xxx space in normal font, because OSD font doesn't have a space
|
||||
text = talloc_strdup_append_buffer(text, "\\h");
|
||||
text = talloc_strdup_append_buffer(text, ASS_USE_OSD_FONT);
|
||||
|
||||
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_START);
|
||||
for (int n = 0; n < active; n++)
|
||||
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_0);
|
||||
for (int n = 0; n < OSDBAR_ELEMS - active; n++)
|
||||
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_1);
|
||||
text = mp_append_utf8_buffer(text, OSD_CODEPOINTS + OSD_PB_END);
|
||||
|
||||
ASS_Event *event = get_osd_ass_event(obj->osd_track);
|
||||
event->Text = strdup(text);
|
||||
add_osd_ass_event(obj->osd_track, text);
|
||||
talloc_free(text);
|
||||
|
||||
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
|
||||
// filled area
|
||||
d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)}", px, py);
|
||||
ass_draw_start(d);
|
||||
float pos = osd->progbar_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);
|
||||
|
||||
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 < osd->progbar_num_stops; n++) {
|
||||
float s = osd->progbar_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_sub(struct osd_state *osd, struct osd_object *obj)
|
||||
{
|
||||
struct MPOpts *opts = osd->opts;
|
||||
|
||||
if (!(vo_sub && opts->sub_visibility)) {
|
||||
clear_obj(obj);
|
||||
clear_obj(obj);
|
||||
|
||||
if (!(vo_sub && opts->sub_visibility))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!obj->osd_track)
|
||||
obj->osd_track = mp_ass_default_track(osd->osd_ass_library, osd->opts);
|
||||
@ -250,9 +351,8 @@ static void update_sub(struct osd_state *osd, struct osd_object *obj)
|
||||
for (int n = 0; n < vo_sub->lines; n++)
|
||||
text = talloc_asprintf_append_buffer(text, "%s\n", vo_sub->text[n]);
|
||||
|
||||
ASS_Event *event = get_osd_ass_event(obj->osd_track);
|
||||
char *escaped_text = mangle_ass(text);
|
||||
event->Text = strdup(escaped_text);
|
||||
add_osd_ass_event(obj->osd_track, escaped_text);
|
||||
talloc_free(escaped_text);
|
||||
talloc_free(text);
|
||||
}
|
||||
|
@ -129,8 +129,13 @@ struct osd_state {
|
||||
|
||||
bool want_redraw;
|
||||
|
||||
char *osd_text; // OSDTYPE_OSD
|
||||
int progbar_type, progbar_value; // OSDTYPE_PROGBAR
|
||||
// OSDTYPE_OSD
|
||||
char *osd_text;
|
||||
// OSDTYPE_PROGBAR
|
||||
int progbar_type; // <0: disabled, 1-255: symbol, else: no symbol
|
||||
float progbar_value; // range 0.0-1.0
|
||||
float *progbar_stops; // used for chapter indicators (0.0-1.0 each)
|
||||
int progbar_num_stops;
|
||||
|
||||
int switch_sub_id;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user