Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <math.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <assert.h>
#include "mpv_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.otf.inc";
#include "sub/ass_mp.h"#include "options/options.h"
#define ASS_USE_OSD_FONT "{\\fnmpv-osd-symbols}"
static void append_ass(struct ass_state *ass, struct mp_osd_res *res, ASS_Image **img_list, bool *changed);
static void create_ass_renderer(struct osd_state *osd, struct ass_state *ass){ if (ass->render) return;
ass->log = mp_log_new(NULL, osd->log, "libass"); ass->library = mp_ass_init(osd->global, osd->opts->osd_style, ass->log); ass_add_font(ass->library, "mpv-osd-symbols", (void *)osd_font_pfb, sizeof(osd_font_pfb) - 1);
ass->render = ass_renderer_init(ass->library); if (!ass->render) abort();
mp_ass_configure_fonts(ass->render, osd->opts->osd_style, osd->global, ass->log); ass_set_pixel_aspect(ass->render, 1.0);}
static void destroy_ass_renderer(struct ass_state *ass){ if (ass->track) ass_free_track(ass->track); ass->track = NULL; if (ass->render) ass_renderer_done(ass->render); ass->render = NULL; if (ass->library) ass_library_done(ass->library); ass->library = NULL; talloc_free(ass->log); ass->log = NULL;}
static void destroy_external(struct osd_external *ext){ destroy_ass_renderer(&ext->ass); talloc_free(ext);}
void osd_destroy_backend(struct osd_state *osd){ for (int n = 0; n < MAX_OSD_PARTS; n++) { 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]); obj->num_externals = 0; }}
static void update_playres(struct ass_state *ass, struct mp_osd_res *vo_res){ ASS_Track *track = ass->track; int old_res_x = track->PlayResX; int old_res_y = track->PlayResY;
ass->vo_res = *vo_res;
double aspect = 1.0 * vo_res->w / MPMAX(vo_res->h, 1); if (vo_res->display_par > 0) aspect = aspect / vo_res->display_par;
track->PlayResY = ass->res_y ? ass->res_y : MP_ASS_FONT_PLAYRESY; track->PlayResX = ass->res_x ? ass->res_x : track->PlayResY * aspect;
// 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(ass->render, 1, 1);}
static void create_ass_track(struct osd_state *osd, struct osd_object *obj, struct ass_state *ass){ create_ass_renderer(osd, ass);
ASS_Track *track = ass->track; if (!track) track = ass->track = ass_new_track(ass->library);
track->track_type = TRACK_TYPE_ASS; track->Timer = 100.; track->WrapStyle = 1; // end-of-line wrapping instead of smart wrapping track->Kerning = true; track->ScaledBorderAndShadow = true;#if LIBASS_VERSION >= 0x01600010 ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1);#endif update_playres(ass, &obj->vo_res);}
static int find_style(ASS_Track *track, const char *name, int def){ for (int n = 0; n < track->n_styles; n++) { if (track->styles[n].Name && strcmp(track->styles[n].Name, name) == 0) return n; } return def;}
// Find a given style, or add it if it's missing.static ASS_Style *get_style(struct ass_state *ass, char *name){ ASS_Track *track = ass->track; if (!track) return NULL;
int sid = find_style(track, name, -1); if (sid >= 0) return &track->styles[sid];
sid = ass_alloc_style(track); ASS_Style *style = &track->styles[sid]; style->Name = strdup(name); // Set to neutral base direction, as opposed to VSFilter LTR default style->Encoding = -1; return style;}
static ASS_Event *add_osd_ass_event(ASS_Track *track, const char *style, const char *text){ int n = ass_alloc_event(track); ASS_Event *event = track->events + n; event->Start = 0; event->Duration = 100; event->Style = find_style(track, style, 0); event->ReadOrder = n; assert(event->Text == NULL); if (text) event->Text = strdup(text); return event;}
static void clear_ass(struct ass_state *ass){ if (ass->track) ass_flush_events(ass->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. // (Same trick as OSD_ASS_0/OSD_ASS_1.) snprintf(buffer, buffer_size, "\xFF%c", osd_function);}
void osd_mangle_ass(bstr *dst, const char *in, bool replace_newlines){ const char *start = 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 == OSD_ASS_0[0] || *in == OSD_ASS_1[0]) { escape_ass = *in == OSD_ASS_1[0]; in += 1; continue; } if (*in == TERM_MSG_0[0]) { in += 1; continue; } if (escape_ass && *in == '{') bstr_xappend(NULL, dst, bstr0("\\")); // Replace newlines with \N for escape-ass. This is necessary to apply // ASS tags past newlines and to preserve consecutive newlines with // osd-overlay because update_external() adds a ASS event per line. if (replace_newlines && *in == '\n') { bstr_xappend(NULL, dst, bstr0("\\N")); in += 1; continue; } // Libass will strip leading whitespace if (in[0] == ' ' && (in == start || in[-1] == '\n')) { bstr_xappend(NULL, dst, bstr0("\\h")); in += 1; continue; } 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 ASS_Event *add_osd_ass_event_escaped(ASS_Track *track, const char *style, const char *text){ bstr buf = {0}; osd_mangle_ass(&buf, text, false); ASS_Event *e = add_osd_ass_event(track, style, buf.start); talloc_free(buf.start); return e;}
static ASS_Style *prepare_osd_ass(struct osd_state *osd, struct osd_object *obj){ struct mp_osd_render_opts *opts = osd->opts;
create_ass_track(osd, obj, &obj->ass);
struct osd_style_opts font = *opts->osd_style; font.font_size *= opts->osd_scale;
double playresy = obj->ass.track->PlayResY; // Compensate for libass and mp_ass_set_style scaling the font etc. if (!opts->osd_scale_by_window && obj->vo_res.h) playresy *= 720.0 / obj->vo_res.h;
ASS_Style *style = get_style(&obj->ass, "OSD"); mp_ass_set_style(style, playresy, &font); return style;}
static void update_osd_text(struct osd_state *osd, struct osd_object *obj){
if (!obj->text[0]) return;
prepare_osd_ass(osd, obj); add_osd_ass_event_escaped(obj->ass.track, "OSD", obj->text);}
void osd_get_text_size(struct osd_state *osd, int *out_screen_h, int *out_font_h){ mp_mutex_lock(&osd->lock); struct osd_object *obj = osd->objs[OSDTYPE_OSD]; ASS_Style *style = prepare_osd_ass(osd, obj); *out_screen_h = obj->ass.track->PlayResY - style->MarginV; *out_font_h = style->FontSize; mp_mutex_unlock(&osd->lock);}
// 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 = MPMAX(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 mp_osd_render_opts *opts = osd->opts; struct osd_bar_style_opts *bar_opts = opts->osd_bar_style;
create_ass_track(osd, obj, &obj->ass); ASS_Track *track = obj->ass.track;
ASS_Style *style = get_style(&obj->ass, "progbar"); if (!style) { *o_x = *o_y = *o_w = *o_h = *o_border = 0; return; }
mp_ass_set_style(style, track->PlayResY, opts->osd_style);
// override the default osd opaque-box into plain outline. Otherwise // the opaque box is not aligned with the bar (even without shadow), // and each bar ass event gets its own opaque box - breaking the bar. style->BorderStyle = 1; // outline
*o_w = track->PlayResX * (bar_opts->w / 100.0); *o_h = track->PlayResY * (bar_opts->h / 100.0);
style->Outline = bar_opts->outline_size; // Rendering with shadow is broken (because there's more than one shape) style->Shadow = 0; style->Blur = 0;
style->Alignment = 5;
*o_border = style->Outline;
*o_x = get_align(bar_opts->align_x, track->PlayResX, *o_w, *o_border); *o_y = get_align(bar_opts->align_y, track->PlayResY, *o_h, *o_border);}
static void update_progbar(struct osd_state *osd, struct osd_object *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);
ASS_Track *track = obj->ass.track;
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(track, "progbar", buf.start); talloc_free(buf.start);
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
if (osd->opts->osd_style->back_color.a && osd->opts->osd_style->border_style != 1) { // the bar style always ignores the --osd-back-color config - it messes // up the bar. draw an artificial box at the original back color. struct m_color bc = osd->opts->osd_style->back_color; d->text = talloc_asprintf_append(d->text, "{\\pos(%f,%f)\\bord0\\1a&H%02X\\1c&H%02X%02X%02X&}", px, py, 255 - bc.a, (int)bc.b, (int)bc.g, (int)bc.r);
ass_draw_start(d); ass_draw_rect_cw(d, -border, -border, width + border, height + border); ass_draw_stop(d); add_osd_ass_event(track, "progbar", d->text); ass_draw_reset(d); }
// 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(track, "progbar", 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(track, "progbar", 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);
struct osd_bar_style_opts *bar_opts = osd->opts->osd_bar_style; // chapter marks if (bar_opts->marker_style) { for (int n = 0; n < obj->progbar_state.num_stops; n++) { float s = obj->progbar_state.stops[n] * width; float size = MPMAX(border * bar_opts->marker_scale, bar_opts->marker_min_size);
if (bar_opts->marker_style == 2 && s > size / 2 && s < width - size / 2) { // line ass_draw_rect_cw(d, s - size / 2, 0, s + size / 2, height); } else if (s > size && s < width - size) { //triangle ass_draw_move_to(d, s + size, 0); ass_draw_line_to(d, s, size); ass_draw_line_to(d, s - size, 0);
ass_draw_move_to(d, s - size, height); ass_draw_line_to(d, s, height - size); ass_draw_line_to(d, s + size, height); } } }
ass_draw_stop(d); add_osd_ass_event(track, "progbar", d->text); ass_draw_reset(d);}
static void update_osd(struct osd_state *osd, struct osd_object *obj){ obj->osd_changed = false; clear_ass(&obj->ass); update_osd_text(osd, obj); update_progbar(osd, obj);}
static void update_external(struct osd_state *osd, struct osd_object *obj, struct osd_external *ext){ 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);
int resy = ext->ass.track->PlayResY; mp_ass_set_style(get_style(&ext->ass, "OSD"), resy, osd->opts->osd_style);
// Some scripts will reference this style name with \r tags. const struct osd_style_opts *def = osd_style_conf.defaults; mp_ass_set_style(get_style(&ext->ass, "Default"), resy, def);
while (t.len) { bstr line; bstr_split_tok(t, "\n", &line, &t); if (line.len) { char *tmp = bstrdup0(NULL, line); add_osd_ass_event(ext->ass.track, "OSD", tmp); talloc_free(tmp); } }}
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){ mp_mutex_lock(&osd->lock); struct osd_object *obj = osd->objs[OSDTYPE_EXTERNAL]; bool zorder_changed = false; int index = -1;
for (int n = 0; n < obj->num_externals; n++) { struct osd_external *e = obj->externals[n]; if (e->ov.id == ov->id && e->ov.owner == ov->owner) { index = n; break; } }
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); index = obj->num_externals - 1; zorder_changed = true; }
struct osd_external *entry = obj->externals[index];
if (!ov->format) { if (!entry->ov.hidden) { obj->changed = true; osd->want_redraw_notification = true; } destroy_external(entry); MP_TARRAY_REMOVE_AT(obj->externals, obj->num_externals, index); goto done; }
if (!entry->ov.hidden || !ov->hidden) { 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; entry->ov.hidden = ov->hidden;
update_external(osd, obj, entry);
if (zorder_changed) { qsort(obj->externals, obj->num_externals, sizeof(obj->externals[0]), cmp_zorder); }
if (ov->out_rc) { struct mp_osd_res vo_res = entry->ass.vo_res; // Defined fallback if VO has not drawn this yet if (vo_res.w < 1 || vo_res.h < 1) { vo_res = (struct mp_osd_res){ .w = entry->ov.res_x, .h = entry->ov.res_y, .display_par = 1, }; // According to osd-overlay command description. if (vo_res.w < 1) vo_res.w = 1280; if (vo_res.h < 1) vo_res.h = 720; }
ASS_Image *img_list = NULL; append_ass(&entry->ass, &vo_res, &img_list, NULL);
mp_ass_get_bb(img_list, entry->ass.track, &vo_res, ov->out_rc); }
done: mp_mutex_unlock(&osd->lock);}
void osd_set_external_remove_owner(struct osd_state *osd, void *owner){ mp_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; } } mp_mutex_unlock(&osd->lock);}
static void append_ass(struct ass_state *ass, struct mp_osd_res *res, ASS_Image **img_list, bool *changed){ if (!ass->render || !ass->track) { *img_list = NULL; return; }
update_playres(ass, res);
ass_set_frame_size(ass->render, res->w, res->h); ass_set_pixel_aspect(ass->render, res->display_par);
int ass_changed; *img_list = ass_render_frame(ass->render, ass->track, 0, &ass_changed);
ass->changed |= ass_changed;
if (changed) { *changed |= ass->changed; ass->changed = false; }}
struct sub_bitmaps *osd_object_get_bitmaps(struct osd_state *osd, struct osd_object *obj, int format){ if (obj->type == OSDTYPE_OSD && obj->osd_changed) update_osd(osd, obj);
if (!obj->ass_packer) obj->ass_packer = mp_ass_packer_alloc(obj);
MP_TARRAY_GROW(obj, obj->ass_imgs, obj->num_externals + 1);
append_ass(&obj->ass, &obj->vo_res, &obj->ass_imgs[0], &obj->changed); for (int n = 0; n < obj->num_externals; n++) { if (obj->externals[n]->ov.hidden) { update_playres(&obj->externals[n]->ass, &obj->vo_res); obj->ass_imgs[n + 1] = NULL; } else { append_ass(&obj->externals[n]->ass, &obj->vo_res, &obj->ass_imgs[n + 1], &obj->changed); } }
struct sub_bitmaps out_imgs = {0}; mp_ass_packer_pack(obj->ass_packer, obj->ass_imgs, obj->num_externals + 1, obj->changed, false, format, &out_imgs);
obj->changed = false;
return sub_bitmaps_copy(&obj->copy_cache, &out_imgs);}