mirror of https://github.com/mpv-player/mpv
sub: basic subtitle converters
Add a basic infrastructure for subtitle converters. These converters work sort-of like decoders, except that they produce packets instead of subtitle bitmaps. They are put in front of actual decoders. Start with sd_movtext. 4 lines of code are blown up to a 55 lines file, but fortunately this is not going to be that bad for the following converters.
This commit is contained in:
parent
02ce316ade
commit
3000df35d3
1
Makefile
1
Makefile
|
@ -231,6 +231,7 @@ SOURCES = talloc.c \
|
|||
sub/find_subfiles.c \
|
||||
sub/img_convert.c \
|
||||
sub/sd_lavc.c \
|
||||
sub/sd_movtext.c \
|
||||
sub/sd_spu.c \
|
||||
sub/spudec.c \
|
||||
sub/sub.c \
|
||||
|
|
|
@ -1858,7 +1858,6 @@ static void update_subtitles(struct MPContext *mpctx, double refpts_tl)
|
|||
|
||||
if (!track->preloaded) {
|
||||
struct demux_stream *d_sub = sh_sub->ds;
|
||||
const char *type = sh_sub->gsh->codec;
|
||||
bool non_interleaved = is_non_interleaved(mpctx, track);
|
||||
|
||||
while (1) {
|
||||
|
@ -1898,12 +1897,6 @@ static void update_subtitles(struct MPContext *mpctx, double refpts_tl)
|
|||
mp_dbg(MSGT_CPLAYER, MSGL_V, "Sub: c_pts=%5.3f s_pts=%5.3f "
|
||||
"duration=%5.3f len=%d\n", curpts_s, subpts_s, duration,
|
||||
len);
|
||||
if (type && strcmp(type, "mov_text") == 0) {
|
||||
if (len < 2)
|
||||
continue;
|
||||
len = FFMIN(len - 2, AV_RB16(packet));
|
||||
packet += 2;
|
||||
}
|
||||
struct demux_packet pkt = {
|
||||
.buffer = packet,
|
||||
.len = len,
|
||||
|
|
170
sub/dec_sub.c
170
sub/dec_sub.c
|
@ -26,10 +26,12 @@
|
|||
#include "sub/sub.h"
|
||||
#include "sub/dec_sub.h"
|
||||
#include "core/options.h"
|
||||
#include "core/mp_msg.h"
|
||||
|
||||
extern const struct sd_functions sd_ass;
|
||||
extern const struct sd_functions sd_lavc;
|
||||
extern const struct sd_functions sd_spu;
|
||||
extern const struct sd_functions sd_movtext;
|
||||
|
||||
static const struct sd_functions *sd_list[] = {
|
||||
#ifdef CONFIG_ASS
|
||||
|
@ -37,14 +39,18 @@ static const struct sd_functions *sd_list[] = {
|
|||
#endif
|
||||
&sd_lavc,
|
||||
&sd_spu,
|
||||
&sd_movtext,
|
||||
NULL
|
||||
};
|
||||
|
||||
#define MAX_NUM_SD 2
|
||||
|
||||
struct dec_sub {
|
||||
struct MPOpts *opts;
|
||||
struct sd init_sd;
|
||||
|
||||
struct sd *sd;
|
||||
struct sd *sd[MAX_NUM_SD];
|
||||
int num_sd;
|
||||
};
|
||||
|
||||
struct dec_sub *sub_create(struct MPOpts *opts)
|
||||
|
@ -54,24 +60,33 @@ struct dec_sub *sub_create(struct MPOpts *opts)
|
|||
return sub;
|
||||
}
|
||||
|
||||
static void sub_uninit(struct dec_sub *sub)
|
||||
{
|
||||
sub_reset(sub);
|
||||
for (int n = 0; n < sub->num_sd; n++) {
|
||||
if (sub->sd[n]->driver->uninit)
|
||||
sub->sd[n]->driver->uninit(sub->sd[n]);
|
||||
talloc_free(sub->sd[n]);
|
||||
}
|
||||
sub->num_sd = 0;
|
||||
}
|
||||
|
||||
void sub_destroy(struct dec_sub *sub)
|
||||
{
|
||||
if (!sub)
|
||||
return;
|
||||
if (sub->sd && sub->sd->driver->uninit)
|
||||
sub->sd->driver->uninit(sub->sd);
|
||||
talloc_free(sub->sd);
|
||||
sub_uninit(sub);
|
||||
talloc_free(sub);
|
||||
}
|
||||
|
||||
bool sub_is_initialized(struct dec_sub *sub)
|
||||
{
|
||||
return !!sub->sd;
|
||||
return !!sub->num_sd;
|
||||
}
|
||||
|
||||
struct sd *sub_get_sd(struct dec_sub *sub)
|
||||
struct sd *sub_get_last_sd(struct dec_sub *sub)
|
||||
{
|
||||
return sub->sd;
|
||||
return sub->num_sd ? sub->sd[sub->num_sd - 1] : NULL;
|
||||
}
|
||||
|
||||
void sub_set_video_res(struct dec_sub *sub, int w, int h)
|
||||
|
@ -114,62 +129,159 @@ static int sub_init_decoder(struct dec_sub *sub, struct sd *sd)
|
|||
|
||||
void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh)
|
||||
{
|
||||
assert(!sub->sd);
|
||||
assert(!sub->num_sd);
|
||||
|
||||
if (sh->extradata && !sub->init_sd.extradata)
|
||||
sub_set_extradata(sub, sh->extradata, sh->extradata_len);
|
||||
struct sd *sd = talloc(NULL, struct sd);
|
||||
*sd = sub->init_sd;
|
||||
sd->opts = sub->opts;
|
||||
sd->codec = sh->gsh->codec;
|
||||
sd->ass_track = sh->track;
|
||||
if (sub_init_decoder(sub, sd) < 0) {
|
||||
talloc_free(sd);
|
||||
sd = NULL;
|
||||
struct sd init_sd = sub->init_sd;
|
||||
init_sd.codec = sh->gsh->codec;
|
||||
init_sd.ass_track = sh->track;
|
||||
|
||||
while (sub->num_sd < MAX_NUM_SD) {
|
||||
struct sd *sd = talloc(NULL, struct sd);
|
||||
*sd = init_sd;
|
||||
sd->opts = sub->opts;
|
||||
if (sub_init_decoder(sub, sd) < 0) {
|
||||
talloc_free(sd);
|
||||
break;
|
||||
}
|
||||
sub->sd[sub->num_sd] = sd;
|
||||
sub->num_sd++;
|
||||
// Try adding new converters until a decoder is reached
|
||||
if (sd->driver->get_bitmaps || sd->driver->get_text)
|
||||
return;
|
||||
init_sd = (struct sd) {
|
||||
.codec = sd->output_codec,
|
||||
.extradata = sd->output_extradata,
|
||||
.extradata_len = sd->output_extradata_len,
|
||||
.ass_library = sub->init_sd.ass_library,
|
||||
.ass_renderer = sub->init_sd.ass_renderer,
|
||||
};
|
||||
}
|
||||
sub->sd = sd;
|
||||
|
||||
sub_uninit(sub);
|
||||
mp_msg(MSGT_OSD, MSGL_ERR, "Could not find subtitle decoder for format '%s'.\n",
|
||||
sh->gsh->codec ? sh->gsh->codec : "<unknown>");
|
||||
}
|
||||
|
||||
bool sub_accept_packets_in_advance(struct dec_sub *sub)
|
||||
{
|
||||
return sub->sd && sub->sd->driver->accept_packets_in_advance;
|
||||
// Converters are assumed to always accept packets in advance
|
||||
struct sd *sd = sub_get_last_sd(sub);
|
||||
return sd && sd->driver->accept_packets_in_advance;
|
||||
}
|
||||
|
||||
static void decode_next(struct dec_sub *sub, int n, struct demux_packet *packet)
|
||||
{
|
||||
struct sd *sd = sub->sd[n];
|
||||
sd->driver->decode(sd, packet);
|
||||
if (n + 1 >= sub->num_sd || !sd->driver->get_converted)
|
||||
return;
|
||||
while (1) {
|
||||
struct demux_packet *next =
|
||||
sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL;
|
||||
if (!next)
|
||||
break;
|
||||
decode_next(sub, n + 1, next);
|
||||
}
|
||||
}
|
||||
|
||||
void sub_decode(struct dec_sub *sub, struct demux_packet *packet)
|
||||
{
|
||||
if (sub->sd)
|
||||
sub->sd->driver->decode(sub->sd, packet);
|
||||
if (sub->num_sd > 0)
|
||||
decode_next(sub, 0, packet);
|
||||
}
|
||||
|
||||
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res)
|
||||
{
|
||||
struct MPOpts *opts = sub->opts;
|
||||
struct sd *sd = sub_get_last_sd(sub);
|
||||
|
||||
*res = (struct sub_bitmaps) {0};
|
||||
if (sub->sd && opts->sub_visibility) {
|
||||
if (sub->sd->driver->get_bitmaps)
|
||||
sub->sd->driver->get_bitmaps(sub->sd, dim, pts, res);
|
||||
if (sd && opts->sub_visibility) {
|
||||
if (sd->driver->get_bitmaps)
|
||||
sd->driver->get_bitmaps(sd, dim, pts, res);
|
||||
}
|
||||
}
|
||||
|
||||
bool sub_has_get_text(struct dec_sub *sub)
|
||||
{
|
||||
return sub->sd && sub->sd->driver->get_text;
|
||||
struct sd *sd = sub_get_last_sd(sub);
|
||||
return sd && sd->driver->get_text;
|
||||
}
|
||||
|
||||
char *sub_get_text(struct dec_sub *sub, double pts)
|
||||
{
|
||||
struct MPOpts *opts = sub->opts;
|
||||
struct sd *sd = sub_get_last_sd(sub);
|
||||
char *text = NULL;
|
||||
if (sub->sd && opts->sub_visibility) {
|
||||
if (sub->sd->driver->get_text)
|
||||
text = sub->sd->driver->get_text(sub->sd, pts);
|
||||
if (sd && opts->sub_visibility) {
|
||||
if (sd->driver->get_text)
|
||||
text = sd->driver->get_text(sd, pts);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void sub_reset(struct dec_sub *sub)
|
||||
{
|
||||
if (sub->sd && sub->sd->driver->reset)
|
||||
sub->sd->driver->reset(sub->sd);
|
||||
for (int n = 0; n < sub->num_sd; n++) {
|
||||
if (sub->sd[n]->driver->reset)
|
||||
sub->sd[n]->driver->reset(sub->sd[n]);
|
||||
}
|
||||
}
|
||||
|
||||
#define MAX_PACKETS 10
|
||||
#define MAX_BYTES 10000
|
||||
|
||||
struct sd_conv_buffer {
|
||||
struct demux_packet pkt[MAX_PACKETS];
|
||||
int num_pkt;
|
||||
int read_pkt;
|
||||
char buffer[MAX_BYTES];
|
||||
int cur_buffer;
|
||||
};
|
||||
|
||||
void sd_conv_add_packet(struct sd *sd, void *data, int data_len, double pts,
|
||||
double duration)
|
||||
{
|
||||
if (!sd->sd_conv_buffer)
|
||||
sd->sd_conv_buffer = talloc_zero(sd, struct sd_conv_buffer);
|
||||
struct sd_conv_buffer *buf = sd->sd_conv_buffer;
|
||||
if (buf->num_pkt >= MAX_PACKETS || buf->cur_buffer + data_len + 1 > MAX_BYTES)
|
||||
goto out_of_space;
|
||||
if (buf->read_pkt == buf->num_pkt)
|
||||
sd_conv_def_reset(sd);
|
||||
assert(buf->read_pkt == 0); // no mixing of reading/adding allowed
|
||||
struct demux_packet *pkt = &buf->pkt[buf->num_pkt++];
|
||||
*pkt = (struct demux_packet) {
|
||||
.buffer = &buf->buffer[buf->cur_buffer],
|
||||
.len = data_len,
|
||||
.pts = pts,
|
||||
.duration = duration,
|
||||
};
|
||||
memcpy(pkt->buffer, data, data_len);
|
||||
pkt->buffer[data_len] = 0;
|
||||
buf->cur_buffer += data_len + 1;
|
||||
return;
|
||||
|
||||
out_of_space:
|
||||
mp_msg(MSGT_OSD, MSGL_ERR, "Subtitle too big.\n");
|
||||
}
|
||||
|
||||
struct demux_packet *sd_conv_def_get_converted(struct sd *sd)
|
||||
{
|
||||
struct sd_conv_buffer *buf = sd->sd_conv_buffer;
|
||||
if (buf && buf->read_pkt < buf->num_pkt)
|
||||
return &buf->pkt[buf->read_pkt++];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sd_conv_def_reset(struct sd *sd)
|
||||
{
|
||||
struct sd_conv_buffer *buf = sd->sd_conv_buffer;
|
||||
if (buf) {
|
||||
buf->read_pkt = buf->num_pkt = 0;
|
||||
buf->cur_buffer = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ bool sub_has_get_text(struct dec_sub *sub);
|
|||
char *sub_get_text(struct dec_sub *sub, double pts);
|
||||
void sub_reset(struct dec_sub *sub);
|
||||
|
||||
struct sd *sub_get_sd(struct dec_sub *sub);
|
||||
struct sd *sub_get_last_sd(struct dec_sub *sub);
|
||||
|
||||
#ifdef CONFIG_ASS
|
||||
struct ass_track *sub_get_ass_track(struct dec_sub *sub);
|
||||
|
|
23
sub/sd.h
23
sub/sd.h
|
@ -26,6 +26,14 @@ struct sd {
|
|||
// Shared renderer for ASS - done to avoid reloading embedded fonts.
|
||||
struct ass_library *ass_library;
|
||||
struct ass_renderer *ass_renderer;
|
||||
|
||||
// Set by sub converter
|
||||
const char *output_codec;
|
||||
char *output_extradata;
|
||||
int output_extradata_len;
|
||||
|
||||
// Internal buffer for sd_conv_* functions
|
||||
struct sd_conv_buffer *sd_conv_buffer;
|
||||
};
|
||||
|
||||
struct sd_functions {
|
||||
|
@ -33,11 +41,22 @@ struct sd_functions {
|
|||
bool (*supports_format)(const char *format);
|
||||
int (*init)(struct sd *sd);
|
||||
void (*decode)(struct sd *sd, struct demux_packet *packet);
|
||||
void (*reset)(struct sd *sd);
|
||||
void (*uninit)(struct sd *sd);
|
||||
|
||||
// decoder
|
||||
void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, double pts,
|
||||
struct sub_bitmaps *res);
|
||||
char *(*get_text)(struct sd *sd, double pts);
|
||||
void (*reset)(struct sd *sd);
|
||||
void (*uninit)(struct sd *sd);
|
||||
|
||||
// converter
|
||||
struct demux_packet *(*get_converted)(struct sd *sd);
|
||||
};
|
||||
|
||||
void sd_conv_add_packet(struct sd *sd, void *data, int data_len, double pts,
|
||||
double duration);
|
||||
struct demux_packet *sd_conv_def_get_converted(struct sd *sd);
|
||||
void sd_conv_def_reset(struct sd *sd);
|
||||
void sd_conv_def_uninit(struct sd *sd);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -53,8 +53,7 @@ static bool is_text_sub(const char *t)
|
|||
{
|
||||
return t && (is_ass_sub(t) ||
|
||||
strcmp(t, "text") == 0 ||
|
||||
strcmp(t, "subrip") == 0 ||
|
||||
strcmp(t, "mov_text") == 0);
|
||||
strcmp(t, "subrip") == 0);
|
||||
}
|
||||
|
||||
static bool supports_format(const char *format)
|
||||
|
@ -296,7 +295,7 @@ const struct sd_functions sd_ass = {
|
|||
|
||||
struct ass_track *sub_get_ass_track(struct dec_sub *sub)
|
||||
{
|
||||
struct sd *sd = sub_get_sd(sub);
|
||||
struct sd *sd = sub_get_last_sd(sub);
|
||||
if (sd && sd->driver == &sd_ass && sd->priv) {
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
return ctx->ass_track;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/intreadwrite.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "sd.h"
|
||||
|
||||
static bool supports_format(const char *format)
|
||||
{
|
||||
return format && strcmp(format, "mov_text") == 0;
|
||||
}
|
||||
|
||||
static int init(struct sd *sd)
|
||||
{
|
||||
sd->output_codec = "text";
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
{
|
||||
unsigned char *data = packet->buffer;
|
||||
int len = packet->len;
|
||||
if (len < 2)
|
||||
return;
|
||||
len = FFMIN(len - 2, AV_RB16(data));
|
||||
data += 2;
|
||||
sd_conv_add_packet(sd, data, len, packet->pts, packet->duration);
|
||||
}
|
||||
|
||||
const struct sd_functions sd_movtext = {
|
||||
.supports_format = supports_format,
|
||||
.init = init,
|
||||
.decode = decode,
|
||||
.get_converted = sd_conv_def_get_converted,
|
||||
.reset = sd_conv_def_reset,
|
||||
};
|
Loading…
Reference in New Issue