mpv/sub/sd_ass.c

456 lines
14 KiB
C

/*
* This file is part of MPlayer.
*
* MPlayer 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.
*
* MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <libavutil/common.h>
#include <ass/ass.h>
#include "talloc.h"
#include "options/options.h"
#include "common/common.h"
#include "common/msg.h"
#include "video/csputils.h"
#include "video/mp_image.h"
#include "dec_sub.h"
#include "ass_mp.h"
#include "sd.h"
struct sd_ass_priv {
struct ass_track *ass_track;
bool is_converted;
struct sub_bitmap *parts;
bool flush_on_seek;
char last_text[500];
struct mp_image_params video_params;
struct mp_image_params last_params;
};
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts);
static bool supports_format(const char *format)
{
// ass-text is produced by converters and the subreader.c ssa parser; this
// format has ASS tags, but doesn't start with any prelude, nor does it
// have extradata.
return format && (strcmp(format, "ass") == 0 ||
strcmp(format, "ssa") == 0 ||
strcmp(format, "ass-text") == 0);
}
static int init(struct sd *sd)
{
struct MPOpts *opts = sd->opts;
if (!sd->ass_library || !sd->ass_renderer || !sd->codec)
return -1;
struct sd_ass_priv *ctx = talloc_zero(NULL, struct sd_ass_priv);
sd->priv = ctx;
ctx->is_converted = sd->converted_from != NULL;
ctx->ass_track = ass_new_track(sd->ass_library);
if (!ctx->is_converted)
ctx->ass_track->track_type = TRACK_TYPE_ASS;
if (sd->extradata) {
ass_process_codec_private(ctx->ass_track, sd->extradata,
sd->extradata_len);
}
mp_ass_add_default_styles(ctx->ass_track, opts);
return 0;
}
static void decode(struct sd *sd, struct demux_packet *packet)
{
struct sd_ass_priv *ctx = sd->priv;
ASS_Track *track = ctx->ass_track;
long long ipts = packet->pts * 1000 + 0.5;
long long iduration = packet->duration * 1000 + 0.5;
if (strcmp(sd->codec, "ass") == 0) {
ass_process_chunk(track, packet->buffer, packet->len, ipts, iduration);
return;
} else if (strcmp(sd->codec, "ssa") == 0) {
// broken ffmpeg ASS packet format
ctx->flush_on_seek = true;
ass_process_data(track, packet->buffer, packet->len);
return;
}
// plaintext subs
if (packet->pts == MP_NOPTS_VALUE) {
MP_WARN(sd, "Subtitle without pts, ignored\n");
return;
}
if (packet->duration <= 0) {
MP_WARN(sd, "Subtitle without duration or "
"duration set to 0 at pts %f, ignored\n", packet->pts);
return;
}
unsigned char *text = packet->buffer;
if (!sd->no_remove_duplicates) {
for (int i = 0; i < track->n_events; i++) {
if (track->events[i].Start == ipts
&& (track->events[i].Duration == iduration)
&& strcmp(track->events[i].Text, text) == 0)
return; // We've already added this subtitle
}
}
int eid = ass_alloc_event(track);
ASS_Event *event = track->events + eid;
event->Start = ipts;
event->Duration = iduration;
event->Style = track->default_style;
event->Text = strdup(text);
}
static ASS_Style *find_style(ASS_Track *track, const char *name)
{
for (int n = track->n_styles - 1; n >= 0; n--) {
const char *style_name = track->styles[n].Name;
if (style_name && strcasecmp(style_name, name) == 0)
return &track->styles[n];
}
return NULL;
}
static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
struct sub_bitmaps *res)
{
struct sd_ass_priv *ctx = sd->priv;
struct MPOpts *opts = sd->opts;
if (pts == MP_NOPTS_VALUE || !sd->ass_renderer)
return;
ASS_Style prev_default_style;
ASS_Style *default_style = NULL;
if (opts->ass_style_override == 2) {
default_style = find_style(ctx->ass_track, "Default");
if (default_style) {
prev_default_style = *default_style;
default_style->FontName = NULL; // don't free this
mp_ass_set_style(default_style, ctx->ass_track->PlayResY,
opts->sub_text_style);
}
}
ASS_Renderer *renderer = sd->ass_renderer;
double scale = dim.display_par;
if (!ctx->is_converted && (!opts->ass_style_override ||
opts->ass_vsfilter_aspect_compat))
{
// Let's use the original video PAR for vsfilter compatibility:
double par = scale
* (ctx->video_params.d_w / (double)ctx->video_params.d_h)
/ (ctx->video_params.w / (double)ctx->video_params.h);
if (isnormal(par))
scale = par;
}
mp_ass_configure(renderer, opts, &dim);
ass_set_aspect_ratio(renderer, scale, 1);
#if LIBASS_VERSION >= 0x01020000
if (!ctx->is_converted && (!opts->ass_style_override ||
opts->ass_vsfilter_blur_compat))
{
ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h);
} else {
ass_set_storage_size(renderer, 0, 0);
}
#endif
mp_ass_render_frame(renderer, ctx->ass_track, pts * 1000 + .5,
&ctx->parts, res);
talloc_steal(ctx, ctx->parts);
if (!ctx->is_converted)
mangle_colors(sd, res);
if (default_style) {
free(default_style->FontName);
*default_style = prev_default_style;
}
}
struct buf {
char *start;
int size;
int len;
};
static void append(struct buf *b, char c)
{
if (b->len < b->size) {
b->start[b->len] = c;
b->len++;
}
}
static void ass_to_plaintext(struct buf *b, const char *in)
{
bool in_tag = false;
bool in_drawing = false;
while (*in) {
if (in_tag) {
if (in[0] == '}') {
in += 1;
in_tag = false;
} else if (in[0] == '\\' && in[1] == 'p') {
in += 2;
// skip text between \pN and \p0 tags
if (in[0] == '0') {
in_drawing = false;
} else if (in[0] >= '1' && in[0] <= '9') {
in_drawing = true;
}
} else {
in += 1;
}
} else {
if (in[0] == '\\' && (in[1] == 'N' || in[1] == 'n')) {
in += 2;
append(b, '\n');
} else if (in[0] == '\\' && in[1] == 'h') {
in += 2;
append(b, ' ');
} else if (in[0] == '{') {
in += 1;
in_tag = true;
} else {
if (!in_drawing)
append(b, in[0]);
in += 1;
}
}
}
}
// Empty string counts as whitespace. Reads s[len-1] even if there are \0s.
static bool is_whitespace_only(char *s, int len)
{
for (int n = 0; n < len; n++) {
if (s[n] != ' ' && s[n] != '\t')
return false;
}
return true;
}
static char *get_text(struct sd *sd, double pts)
{
struct sd_ass_priv *ctx = sd->priv;
ASS_Track *track = ctx->ass_track;
if (pts == MP_NOPTS_VALUE)
return NULL;
long long ipts = pts * 1000 + 0.5;
struct buf b = {ctx->last_text, sizeof(ctx->last_text) - 1};
for (int i = 0; i < track->n_events; ++i) {
ASS_Event *event = track->events + i;
if (ipts >= event->Start && ipts < event->Start + event->Duration) {
if (event->Text) {
int start = b.len;
ass_to_plaintext(&b, event->Text);
if (is_whitespace_only(&b.start[start], b.len - start)) {
b.len = start;
} else {
append(&b, '\n');
}
}
}
}
b.start[b.len] = '\0';
if (b.len > 0 && b.start[b.len - 1] == '\n')
b.start[b.len - 1] = '\0';
return ctx->last_text;
}
static void fix_events(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
ctx->flush_on_seek = false;
}
static void reset(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
if (ctx->flush_on_seek)
ass_flush_events(ctx->ass_track);
ctx->flush_on_seek = false;
}
static void uninit(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
ass_free_track(ctx->ass_track);
talloc_free(ctx);
}
static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
{
struct sd_ass_priv *ctx = sd->priv;
switch (cmd) {
case SD_CTRL_SUB_STEP: {
double *a = arg;
long long res = ass_step_sub(ctx->ass_track, a[0] * 1000 + 0.5, a[1]);
if (!res)
return false;
a[0] = res / 1000.0;
return true;
case SD_CTRL_SET_VIDEO_PARAMS:
ctx->video_params = *(struct mp_image_params *)arg;
return CONTROL_OK;
}
default:
return CONTROL_UNKNOWN;
}
}
const struct sd_functions sd_ass = {
.name = "ass",
.accept_packets_in_advance = true,
.supports_format = supports_format,
.init = init,
.decode = decode,
.get_bitmaps = get_bitmaps,
.get_text = get_text,
.fix_events = fix_events,
.control = control,
.reset = reset,
.uninit = uninit,
};
// Disgusting hack for (xy-)vsfilter color compatibility.
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
{
struct MPOpts *opts = sd->opts;
struct sd_ass_priv *ctx = sd->priv;
enum mp_csp csp = 0;
enum mp_csp_levels levels = 0;
if (opts->ass_vsfilter_color_compat == 0) // "no"
return;
bool force_601 = opts->ass_vsfilter_color_compat == 3;
#if LIBASS_VERSION >= 0x01020000
ASS_Track *track = ctx->ass_track;
static const int ass_csp[] = {
[YCBCR_BT601_TV] = MP_CSP_BT_601,
[YCBCR_BT601_PC] = MP_CSP_BT_601,
[YCBCR_BT709_TV] = MP_CSP_BT_709,
[YCBCR_BT709_PC] = MP_CSP_BT_709,
[YCBCR_SMPTE240M_TV] = MP_CSP_SMPTE_240M,
[YCBCR_SMPTE240M_PC] = MP_CSP_SMPTE_240M,
};
static const int ass_levels[] = {
[YCBCR_BT601_TV] = MP_CSP_LEVELS_TV,
[YCBCR_BT601_PC] = MP_CSP_LEVELS_PC,
[YCBCR_BT709_TV] = MP_CSP_LEVELS_TV,
[YCBCR_BT709_PC] = MP_CSP_LEVELS_PC,
[YCBCR_SMPTE240M_TV] = MP_CSP_LEVELS_TV,
[YCBCR_SMPTE240M_PC] = MP_CSP_LEVELS_PC,
};
int trackcsp = track->YCbCrMatrix;
if (force_601)
trackcsp = YCBCR_BT601_TV;
// NONE is a bit random, but the intention is: don't modify colors.
if (trackcsp == YCBCR_NONE)
return;
if (trackcsp < sizeof(ass_csp) / sizeof(ass_csp[0]))
csp = ass_csp[trackcsp];
if (trackcsp < sizeof(ass_levels) / sizeof(ass_levels[0]))
levels = ass_levels[trackcsp];
if (trackcsp == YCBCR_DEFAULT) {
csp = MP_CSP_BT_601;
levels = MP_CSP_LEVELS_TV;
}
// Unknown colorspace (either YCBCR_UNKNOWN, or a valid value unknown to us)
if (!csp || !levels)
return;
#endif
struct mp_image_params params = ctx->video_params;
if (force_601) {
params.colorspace = MP_CSP_BT_709;
params.colorlevels = MP_CSP_LEVELS_TV;
}
if (csp == params.colorspace && levels == params.colorlevels)
return;
bool basic_conv = params.colorspace == MP_CSP_BT_709 &&
params.colorlevels == MP_CSP_LEVELS_TV &&
csp == MP_CSP_BT_601 &&
levels == MP_CSP_LEVELS_TV;
// With "basic", only do as much as needed for basic compatibility.
if (opts->ass_vsfilter_color_compat == 1 && !basic_conv)
return;
if (params.colorspace != ctx->last_params.colorspace ||
params.colorlevels != ctx->last_params.colorlevels)
{
int msgl = basic_conv ? MSGL_V : MSGL_WARN;
ctx->last_params = params;
MP_MSG(sd, msgl, "mangling colors like vsfilter: "
"RGB -> %s %s -> %s %s -> RGB\n", mp_csp_names[csp],
mp_csp_levels_names[levels], mp_csp_names[params.colorspace],
mp_csp_levels_names[params.colorlevels]);
}
// Conversion that VSFilter would use
struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS;
vs_params.colorspace.format = csp;
vs_params.colorspace.levels_in = levels;
vs_params.int_bits_in = 8;
vs_params.int_bits_out = 8;
float vs_yuv2rgb[3][4], vs_rgb2yuv[3][4];
mp_get_yuv2rgb_coeffs(&vs_params, vs_yuv2rgb);
mp_invert_yuv2rgb(vs_rgb2yuv, vs_yuv2rgb);
// Proper conversion to RGB
struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS;
rgb_params.colorspace.format = params.colorspace;
rgb_params.colorspace.levels_in = params.colorlevels;
rgb_params.int_bits_in = 8;
rgb_params.int_bits_out = 8;
float vs2rgb[3][4];
mp_get_yuv2rgb_coeffs(&rgb_params, vs2rgb);
for (int n = 0; n < parts->num_parts; n++) {
struct sub_bitmap *sb = &parts->parts[n];
uint32_t color = sb->libass.color;
int r = (color >> 24u) & 0xff;
int g = (color >> 16u) & 0xff;
int b = (color >> 8u) & 0xff;
int a = 0xff - (color & 0xff);
int c[3] = {r, g, b};
mp_map_int_color(vs_rgb2yuv, 8, c);
mp_map_int_color(vs2rgb, 8, c);
sb->libass.color = MP_ASS_RGBA(c[0], c[1], c[2], a);
}
}