2011-01-16 18:03:08 +00:00
|
|
|
/*
|
|
|
|
* 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 <stdbool.h>
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
#include <string.h>
|
2013-07-07 21:54:11 +00:00
|
|
|
#include <math.h>
|
2011-01-16 18:03:08 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
2013-06-11 19:39:54 +00:00
|
|
|
#include "demux/demux.h"
|
2013-06-01 17:54:18 +00:00
|
|
|
#include "sd.h"
|
|
|
|
#include "sub.h"
|
|
|
|
#include "dec_sub.h"
|
2013-08-06 20:41:30 +00:00
|
|
|
#include "mpvcore/options.h"
|
|
|
|
#include "mpvcore/mp_msg.h"
|
|
|
|
#include "mpvcore/charset_conv.h"
|
2011-01-16 18:03:08 +00:00
|
|
|
|
|
|
|
extern const struct sd_functions sd_ass;
|
2012-08-16 15:21:21 +00:00
|
|
|
extern const struct sd_functions sd_lavc;
|
sub: add sd_spu.c to wrap spudec, cleanup mplayer.c
This unifies the subtitle rendering path. Now all subtitle rendering
goes through sd_ass.c/sd_lavc.c/sd_spu.c.
Before that commit, the spudec.h functions were used directly in
mplayer.c, which introduced many special cases. Add sd_spu.c, which is
just a small wrapper connecting the new subtitle render API with the
dusty old vobsub decoder in spudec.c.
One detail that changes is that we always pass the palette as extra
data, instead of passing the libdvdread palette as pointer to spudec
directly. This is a bit roundabout, but actually makes the code simpler
and more elegant: the difference between DVD and non-DVD dvdsubs is
reduced.
Ideally, we would just delete spudec.c and use libavcodec's DVD sub
decoder. However, DVD playback with demux_mpg produces packets
incompatible to lavc. There are incompatibilities the other way around
as well: packets from libavformat's vobsub demuxer are incompatible to
spudec.c. So we define a new subtitle codec name for demux_mpg subs,
"dvd_subtitle_mpg", which only sd_spu can decode.
There is actually code in spudec.c to "assemble" fragments into complete
packets, but using the whole spudec.c is easier than trying to move this
code into demux_mpg to fix subtitle packets.
As additional complication, Libav 9.x can't decode DVD subs correctly,
so use sd_spu in that case as well.
2013-04-28 23:13:22 +00:00
|
|
|
extern const struct sd_functions sd_spu;
|
2013-06-01 17:44:55 +00:00
|
|
|
extern const struct sd_functions sd_movtext;
|
2013-06-01 17:54:18 +00:00
|
|
|
extern const struct sd_functions sd_srt;
|
|
|
|
extern const struct sd_functions sd_microdvd;
|
2013-06-24 00:06:55 +00:00
|
|
|
extern const struct sd_functions sd_lavf_srt;
|
2013-06-01 17:54:31 +00:00
|
|
|
extern const struct sd_functions sd_lavc_conv;
|
sub: add sd_spu.c to wrap spudec, cleanup mplayer.c
This unifies the subtitle rendering path. Now all subtitle rendering
goes through sd_ass.c/sd_lavc.c/sd_spu.c.
Before that commit, the spudec.h functions were used directly in
mplayer.c, which introduced many special cases. Add sd_spu.c, which is
just a small wrapper connecting the new subtitle render API with the
dusty old vobsub decoder in spudec.c.
One detail that changes is that we always pass the palette as extra
data, instead of passing the libdvdread palette as pointer to spudec
directly. This is a bit roundabout, but actually makes the code simpler
and more elegant: the difference between DVD and non-DVD dvdsubs is
reduced.
Ideally, we would just delete spudec.c and use libavcodec's DVD sub
decoder. However, DVD playback with demux_mpg produces packets
incompatible to lavc. There are incompatibilities the other way around
as well: packets from libavformat's vobsub demuxer are incompatible to
spudec.c. So we define a new subtitle codec name for demux_mpg subs,
"dvd_subtitle_mpg", which only sd_spu can decode.
There is actually code in spudec.c to "assemble" fragments into complete
packets, but using the whole spudec.c is easier than trying to move this
code into demux_mpg to fix subtitle packets.
As additional complication, Libav 9.x can't decode DVD subs correctly,
so use sd_spu in that case as well.
2013-04-28 23:13:22 +00:00
|
|
|
|
|
|
|
static const struct sd_functions *sd_list[] = {
|
|
|
|
#ifdef CONFIG_ASS
|
|
|
|
&sd_ass,
|
|
|
|
#endif
|
|
|
|
&sd_lavc,
|
|
|
|
&sd_spu,
|
2013-06-01 17:44:55 +00:00
|
|
|
&sd_movtext,
|
2013-06-01 17:54:18 +00:00
|
|
|
&sd_srt,
|
|
|
|
&sd_microdvd,
|
2013-06-24 00:06:55 +00:00
|
|
|
&sd_lavf_srt,
|
2013-06-01 17:54:31 +00:00
|
|
|
&sd_lavc_conv,
|
sub: add sd_spu.c to wrap spudec, cleanup mplayer.c
This unifies the subtitle rendering path. Now all subtitle rendering
goes through sd_ass.c/sd_lavc.c/sd_spu.c.
Before that commit, the spudec.h functions were used directly in
mplayer.c, which introduced many special cases. Add sd_spu.c, which is
just a small wrapper connecting the new subtitle render API with the
dusty old vobsub decoder in spudec.c.
One detail that changes is that we always pass the palette as extra
data, instead of passing the libdvdread palette as pointer to spudec
directly. This is a bit roundabout, but actually makes the code simpler
and more elegant: the difference between DVD and non-DVD dvdsubs is
reduced.
Ideally, we would just delete spudec.c and use libavcodec's DVD sub
decoder. However, DVD playback with demux_mpg produces packets
incompatible to lavc. There are incompatibilities the other way around
as well: packets from libavformat's vobsub demuxer are incompatible to
spudec.c. So we define a new subtitle codec name for demux_mpg subs,
"dvd_subtitle_mpg", which only sd_spu can decode.
There is actually code in spudec.c to "assemble" fragments into complete
packets, but using the whole spudec.c is easier than trying to move this
code into demux_mpg to fix subtitle packets.
As additional complication, Libav 9.x can't decode DVD subs correctly,
so use sd_spu in that case as well.
2013-04-28 23:13:22 +00:00
|
|
|
NULL
|
|
|
|
};
|
2011-01-16 18:03:08 +00:00
|
|
|
|
2013-06-01 17:54:18 +00:00
|
|
|
#define MAX_NUM_SD 3
|
2013-06-01 17:44:55 +00:00
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
struct dec_sub {
|
|
|
|
struct MPOpts *opts;
|
|
|
|
struct sd init_sd;
|
|
|
|
|
2013-06-11 19:39:54 +00:00
|
|
|
double video_fps;
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
const char *charset;
|
2013-06-11 19:39:54 +00:00
|
|
|
|
2013-06-01 17:44:55 +00:00
|
|
|
struct sd *sd[MAX_NUM_SD];
|
|
|
|
int num_sd;
|
2013-06-01 17:44:12 +00:00
|
|
|
};
|
|
|
|
|
2013-06-11 19:39:54 +00:00
|
|
|
struct packet_list {
|
|
|
|
struct demux_packet **packets;
|
|
|
|
int num_packets;
|
|
|
|
};
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
struct dec_sub *sub_create(struct MPOpts *opts)
|
|
|
|
{
|
|
|
|
struct dec_sub *sub = talloc_zero(NULL, struct dec_sub);
|
|
|
|
sub->opts = opts;
|
|
|
|
return sub;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:55 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
void sub_destroy(struct dec_sub *sub)
|
|
|
|
{
|
|
|
|
if (!sub)
|
|
|
|
return;
|
2013-06-01 17:44:55 +00:00
|
|
|
sub_uninit(sub);
|
2013-06-01 17:44:12 +00:00
|
|
|
talloc_free(sub);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool sub_is_initialized(struct dec_sub *sub)
|
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
return !!sub->num_sd;
|
2013-06-01 17:44:12 +00:00
|
|
|
}
|
|
|
|
|
2013-06-28 23:34:11 +00:00
|
|
|
static struct sd *sub_get_last_sd(struct dec_sub *sub)
|
2013-06-01 17:44:12 +00:00
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
return sub->num_sd ? sub->sd[sub->num_sd - 1] : NULL;
|
2013-06-01 17:44:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void sub_set_video_res(struct dec_sub *sub, int w, int h)
|
|
|
|
{
|
|
|
|
sub->init_sd.sub_video_w = w;
|
|
|
|
sub->init_sd.sub_video_h = h;
|
|
|
|
}
|
|
|
|
|
2013-06-11 19:39:54 +00:00
|
|
|
void sub_set_video_fps(struct dec_sub *sub, double fps)
|
|
|
|
{
|
|
|
|
sub->video_fps = fps;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
void sub_set_extradata(struct dec_sub *sub, void *data, int data_len)
|
|
|
|
{
|
|
|
|
sub->init_sd.extradata = data_len ? talloc_memdup(sub, data, data_len) : NULL;
|
|
|
|
sub->init_sd.extradata_len = data_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sub_set_ass_renderer(struct dec_sub *sub, struct ass_library *ass_library,
|
|
|
|
struct ass_renderer *ass_renderer)
|
|
|
|
{
|
|
|
|
sub->init_sd.ass_library = ass_library;
|
|
|
|
sub->init_sd.ass_renderer = ass_renderer;
|
|
|
|
}
|
|
|
|
|
2013-06-03 19:49:39 +00:00
|
|
|
static void print_chain(struct dec_sub *sub)
|
|
|
|
{
|
|
|
|
mp_msg(MSGT_OSD, MSGL_V, "Subtitle filter chain: ");
|
|
|
|
for (int n = 0; n < sub->num_sd; n++) {
|
|
|
|
struct sd *sd = sub->sd[n];
|
2013-06-24 00:06:55 +00:00
|
|
|
mp_msg(MSGT_OSD, MSGL_V, "%s%s (%s)", n > 0 ? " -> " : "",
|
2013-06-03 19:49:39 +00:00
|
|
|
sd->driver->name, sd->codec);
|
|
|
|
}
|
|
|
|
mp_msg(MSGT_OSD, MSGL_V, "\n");
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
static int sub_init_decoder(struct dec_sub *sub, struct sd *sd)
|
2011-01-16 18:03:08 +00:00
|
|
|
{
|
2013-06-01 17:44:12 +00:00
|
|
|
sd->driver = NULL;
|
sub: add sd_spu.c to wrap spudec, cleanup mplayer.c
This unifies the subtitle rendering path. Now all subtitle rendering
goes through sd_ass.c/sd_lavc.c/sd_spu.c.
Before that commit, the spudec.h functions were used directly in
mplayer.c, which introduced many special cases. Add sd_spu.c, which is
just a small wrapper connecting the new subtitle render API with the
dusty old vobsub decoder in spudec.c.
One detail that changes is that we always pass the palette as extra
data, instead of passing the libdvdread palette as pointer to spudec
directly. This is a bit roundabout, but actually makes the code simpler
and more elegant: the difference between DVD and non-DVD dvdsubs is
reduced.
Ideally, we would just delete spudec.c and use libavcodec's DVD sub
decoder. However, DVD playback with demux_mpg produces packets
incompatible to lavc. There are incompatibilities the other way around
as well: packets from libavformat's vobsub demuxer are incompatible to
spudec.c. So we define a new subtitle codec name for demux_mpg subs,
"dvd_subtitle_mpg", which only sd_spu can decode.
There is actually code in spudec.c to "assemble" fragments into complete
packets, but using the whole spudec.c is easier than trying to move this
code into demux_mpg to fix subtitle packets.
As additional complication, Libav 9.x can't decode DVD subs correctly,
so use sd_spu in that case as well.
2013-04-28 23:13:22 +00:00
|
|
|
for (int n = 0; sd_list[n]; n++) {
|
2013-06-01 17:44:12 +00:00
|
|
|
if (sd_list[n]->supports_format(sd->codec)) {
|
|
|
|
sd->driver = sd_list[n];
|
sub: add sd_spu.c to wrap spudec, cleanup mplayer.c
This unifies the subtitle rendering path. Now all subtitle rendering
goes through sd_ass.c/sd_lavc.c/sd_spu.c.
Before that commit, the spudec.h functions were used directly in
mplayer.c, which introduced many special cases. Add sd_spu.c, which is
just a small wrapper connecting the new subtitle render API with the
dusty old vobsub decoder in spudec.c.
One detail that changes is that we always pass the palette as extra
data, instead of passing the libdvdread palette as pointer to spudec
directly. This is a bit roundabout, but actually makes the code simpler
and more elegant: the difference between DVD and non-DVD dvdsubs is
reduced.
Ideally, we would just delete spudec.c and use libavcodec's DVD sub
decoder. However, DVD playback with demux_mpg produces packets
incompatible to lavc. There are incompatibilities the other way around
as well: packets from libavformat's vobsub demuxer are incompatible to
spudec.c. So we define a new subtitle codec name for demux_mpg subs,
"dvd_subtitle_mpg", which only sd_spu can decode.
There is actually code in spudec.c to "assemble" fragments into complete
packets, but using the whole spudec.c is easier than trying to move this
code into demux_mpg to fix subtitle packets.
As additional complication, Libav 9.x can't decode DVD subs correctly,
so use sd_spu in that case as well.
2013-04-28 23:13:22 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-01-16 18:03:08 +00:00
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
if (!sd->driver)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (sd->driver->init(sd) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sub_init_from_sh(struct dec_sub *sub, struct sh_sub *sh)
|
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
assert(!sub->num_sd);
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
if (sh->extradata && !sub->init_sd.extradata)
|
|
|
|
sub_set_extradata(sub, sh->extradata, sh->extradata_len);
|
2013-06-01 17:44:55 +00:00
|
|
|
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
|
2013-06-01 17:54:18 +00:00
|
|
|
if (sd->driver->get_bitmaps || sd->driver->get_text) {
|
2013-06-03 19:49:39 +00:00
|
|
|
print_chain(sub);
|
2013-06-01 17:44:55 +00:00
|
|
|
return;
|
2013-06-01 17:54:18 +00:00
|
|
|
}
|
2013-06-01 17:44:55 +00:00
|
|
|
init_sd = (struct sd) {
|
|
|
|
.codec = sd->output_codec,
|
2013-06-02 21:01:37 +00:00
|
|
|
.converted_from = sd->codec,
|
2013-06-01 17:44:55 +00:00
|
|
|
.extradata = sd->output_extradata,
|
|
|
|
.extradata_len = sd->output_extradata_len,
|
|
|
|
.ass_library = sub->init_sd.ass_library,
|
|
|
|
.ass_renderer = sub->init_sd.ass_renderer,
|
|
|
|
};
|
2011-01-16 18:03:08 +00:00
|
|
|
}
|
2013-06-01 17:44:55 +00:00
|
|
|
|
|
|
|
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>");
|
2011-01-16 18:03:08 +00:00
|
|
|
}
|
|
|
|
|
2013-06-23 22:47:08 +00:00
|
|
|
static struct demux_packet *get_decoded_packet(struct sd *sd)
|
|
|
|
{
|
|
|
|
return sd->driver->get_converted ? sd->driver->get_converted(sd) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void decode_chain(struct sd **sd, int num_sd, struct demux_packet *packet)
|
|
|
|
{
|
|
|
|
if (num_sd == 0)
|
|
|
|
return;
|
|
|
|
struct sd *dec = sd[0];
|
|
|
|
dec->driver->decode(dec, packet);
|
|
|
|
if (num_sd > 1) {
|
|
|
|
while (1) {
|
|
|
|
struct demux_packet *next = get_decoded_packet(dec);
|
|
|
|
if (!next)
|
|
|
|
break;
|
|
|
|
decode_chain(sd + 1, num_sd - 1, next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct demux_packet *recode_packet(struct demux_packet *in,
|
|
|
|
const char *charset)
|
|
|
|
{
|
|
|
|
struct demux_packet *pkt = NULL;
|
|
|
|
bstr in_buf = {in->buffer, in->len};
|
|
|
|
bstr conv = mp_iconv_to_utf8(in_buf, charset, MP_ICONV_VERBOSE);
|
|
|
|
if (conv.start && conv.start != in_buf.start) {
|
|
|
|
pkt = talloc_ptrtype(NULL, pkt);
|
|
|
|
talloc_steal(pkt, conv.start);
|
|
|
|
*pkt = (struct demux_packet) {
|
|
|
|
.buffer = conv.start,
|
|
|
|
.len = conv.len,
|
|
|
|
.pts = in->pts,
|
|
|
|
.duration = in->duration,
|
|
|
|
.avpacket = in->avpacket, // questionable, but gives us sidedata
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return pkt;
|
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
static void decode_chain_recode(struct dec_sub *sub, struct sd **sd, int num_sd,
|
|
|
|
struct demux_packet *packet)
|
2013-06-23 22:47:08 +00:00
|
|
|
{
|
2013-06-23 23:04:17 +00:00
|
|
|
if (num_sd > 0) {
|
2013-06-23 22:47:08 +00:00
|
|
|
struct demux_packet *recoded = NULL;
|
|
|
|
if (sub->charset)
|
|
|
|
recoded = recode_packet(packet, sub->charset);
|
2013-06-23 23:04:17 +00:00
|
|
|
decode_chain(sd, num_sd, recoded ? recoded : packet);
|
2013-06-28 12:22:53 +00:00
|
|
|
talloc_free(recoded);
|
2013-06-23 22:47:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
void sub_decode(struct dec_sub *sub, struct demux_packet *packet)
|
|
|
|
{
|
|
|
|
decode_chain_recode(sub, sub->sd, sub->num_sd, packet);
|
|
|
|
}
|
|
|
|
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
static const char *guess_sub_cp(struct packet_list *subs, const char *usercp)
|
|
|
|
{
|
|
|
|
if (!mp_charset_requires_guess(usercp))
|
|
|
|
return usercp;
|
|
|
|
|
|
|
|
// Concat all subs into a buffer. We can't probably do much better without
|
|
|
|
// having the original data (which we don't, not anymore).
|
|
|
|
int max_size = 2 * 1024 * 1024;
|
|
|
|
const char *sep = "\n\n"; // In utf-16: U+0A0A GURMUKHI LETTER UU
|
|
|
|
int sep_len = strlen(sep);
|
|
|
|
int num_pkt = 0;
|
|
|
|
int size = 0;
|
|
|
|
for (int n = 0; n < subs->num_packets; n++) {
|
|
|
|
struct demux_packet *pkt = subs->packets[n];
|
|
|
|
if (size + pkt->len > max_size)
|
|
|
|
break;
|
|
|
|
size += pkt->len + sep_len;
|
|
|
|
num_pkt++;
|
|
|
|
}
|
|
|
|
bstr text = {talloc_size(NULL, size), 0};
|
|
|
|
for (int n = 0; n < num_pkt; n++) {
|
|
|
|
struct demux_packet *pkt = subs->packets[n];
|
|
|
|
memcpy(text.start + text.len, pkt->buffer, pkt->len);
|
|
|
|
memcpy(text.start + text.len + pkt->len, sep, sep_len);
|
|
|
|
text.len += pkt->len + sep_len;
|
|
|
|
}
|
2013-08-15 17:29:42 +00:00
|
|
|
const char *guess = mp_charset_guess(text, usercp, 0);
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
talloc_free(text.start);
|
|
|
|
return guess;
|
|
|
|
}
|
|
|
|
|
2013-06-11 19:41:50 +00:00
|
|
|
static void multiply_timings(struct packet_list *subs, double factor)
|
|
|
|
{
|
|
|
|
for (int n = 0; n < subs->num_packets; n++) {
|
|
|
|
struct demux_packet *pkt = subs->packets[n];
|
|
|
|
if (pkt->pts != MP_NOPTS_VALUE)
|
|
|
|
pkt->pts *= factor;
|
|
|
|
if (pkt->duration > 0)
|
|
|
|
pkt->duration *= factor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-14 11:23:37 +00:00
|
|
|
#define MS_TS(f_ts) ((int)((f_ts) * 1000 + 0.5))
|
|
|
|
|
2013-06-11 19:41:50 +00:00
|
|
|
// Remove overlaps and fill gaps between adjacent subtitle packets. This is done
|
|
|
|
// by adjusting the duration of the earlier packet. If the gaps or overlap are
|
|
|
|
// larger than the threshold, or if the durations are close to the threshold,
|
|
|
|
// don't change the events.
|
|
|
|
// The algorithm is maximally naive and doesn't work if there are multiple
|
|
|
|
// overlapping lines. (It's not worth the trouble.)
|
|
|
|
static void fix_overlaps_and_gaps(struct packet_list *subs)
|
|
|
|
{
|
|
|
|
double threshold = 0.2; // up to 200 ms overlaps or gaps are removed
|
|
|
|
double keep = threshold * 2;// don't change timings if durations are smaller
|
|
|
|
for (int i = 0; i < subs->num_packets - 1; i++) {
|
|
|
|
struct demux_packet *cur = subs->packets[i];
|
|
|
|
struct demux_packet *next = subs->packets[i + 1];
|
|
|
|
if (cur->pts != MP_NOPTS_VALUE && cur->duration > 0 &&
|
|
|
|
next->pts != MP_NOPTS_VALUE && next->duration > 0)
|
|
|
|
{
|
|
|
|
double end = cur->pts + cur->duration;
|
|
|
|
if (fabs(next->pts - end) <= threshold && cur->duration >= keep &&
|
|
|
|
next->duration >= keep)
|
|
|
|
{
|
2013-08-14 11:23:37 +00:00
|
|
|
// Conceptually: cur->duration = next->pts - cur->pts;
|
|
|
|
// But make sure the rounding and conversion to integers in
|
|
|
|
// sd_ass.c can't produce overlaps.
|
|
|
|
cur->duration = (MS_TS(next->pts) - MS_TS(cur->pts)) / 1000.0;
|
2013-06-11 19:41:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
static void add_sub_list(struct dec_sub *sub, int at, struct packet_list *subs)
|
2013-06-11 19:39:54 +00:00
|
|
|
{
|
|
|
|
struct sd *sd = sub_get_last_sd(sub);
|
|
|
|
assert(sd);
|
|
|
|
|
|
|
|
sd->no_remove_duplicates = true;
|
|
|
|
|
|
|
|
for (int n = 0; n < subs->num_packets; n++)
|
2013-06-23 23:04:17 +00:00
|
|
|
decode_chain_recode(sub, sub->sd + at, sub->num_sd - at, subs->packets[n]);
|
2013-06-11 19:39:54 +00:00
|
|
|
|
|
|
|
// Hack for broken FFmpeg packet format: make sd_ass keep the subtitle
|
|
|
|
// events on reset(), even if broken FFmpeg ASS packets were received
|
|
|
|
// (from sd_lavc_conv.c). Normally, these events are removed on seek/reset,
|
|
|
|
// but this is obviously unwanted in this case.
|
|
|
|
if (sd->driver->fix_events)
|
|
|
|
sd->driver->fix_events(sd);
|
|
|
|
|
|
|
|
sd->no_remove_duplicates = false;
|
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
static void add_packet(struct packet_list *subs, struct demux_packet *pkt)
|
|
|
|
{
|
|
|
|
pkt = demux_copy_packet(pkt);
|
|
|
|
talloc_steal(subs, pkt);
|
|
|
|
MP_TARRAY_APPEND(subs, subs->packets, subs->num_packets, pkt);
|
|
|
|
}
|
|
|
|
|
2013-06-11 19:39:54 +00:00
|
|
|
// Read all packets from the demuxer and decode/add them. Returns false if
|
|
|
|
// there are circumstances which makes this not possible.
|
|
|
|
bool sub_read_all_packets(struct dec_sub *sub, struct sh_sub *sh)
|
|
|
|
{
|
2013-06-11 19:41:50 +00:00
|
|
|
struct MPOpts *opts = sub->opts;
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
if (!sub_accept_packets_in_advance(sub) || sh->track || sub->num_sd < 1)
|
2013-06-11 19:39:54 +00:00
|
|
|
return false;
|
|
|
|
|
2013-06-23 22:58:10 +00:00
|
|
|
struct packet_list *subs = talloc_zero(NULL, struct packet_list);
|
2013-06-11 19:39:54 +00:00
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
// In some cases, we want to put the packets through a decoder first.
|
|
|
|
// Preprocess until sub->sd[preprocess].
|
|
|
|
int preprocess = 0;
|
|
|
|
|
|
|
|
// movtext is currently the only subtitle format that has text output,
|
|
|
|
// but binary input. Do charset conversion after converting to text.
|
|
|
|
if (sub->sd[0]->driver == &sd_movtext)
|
|
|
|
preprocess = 1;
|
|
|
|
|
2013-06-24 00:06:55 +00:00
|
|
|
// Broken Libav libavformat srt packet format (fix timestamps first).
|
|
|
|
if (sub->sd[0]->driver == &sd_lavf_srt)
|
|
|
|
preprocess = 1;
|
|
|
|
|
2013-06-11 19:39:54 +00:00
|
|
|
for (;;) {
|
2013-07-11 17:10:33 +00:00
|
|
|
struct demux_packet *pkt = demux_read_packet(sh->gsh);
|
2013-06-11 19:39:54 +00:00
|
|
|
if (!pkt)
|
|
|
|
break;
|
2013-06-23 23:04:17 +00:00
|
|
|
if (preprocess) {
|
|
|
|
decode_chain(sub->sd, preprocess, pkt);
|
2013-07-11 17:10:33 +00:00
|
|
|
talloc_free(pkt);
|
2013-06-23 23:04:17 +00:00
|
|
|
while (1) {
|
|
|
|
pkt = get_decoded_packet(sub->sd[preprocess - 1]);
|
|
|
|
if (!pkt)
|
|
|
|
break;
|
|
|
|
add_packet(subs, pkt);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
add_packet(subs, pkt);
|
2013-07-11 17:10:33 +00:00
|
|
|
talloc_free(pkt);
|
2013-06-23 23:04:17 +00:00
|
|
|
}
|
2013-06-11 19:39:54 +00:00
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
if (opts->sub_cp && !sh->is_utf8)
|
2013-06-23 22:58:10 +00:00
|
|
|
sub->charset = guess_sub_cp(subs, opts->sub_cp);
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
|
2013-08-15 21:13:10 +00:00
|
|
|
if (sub->charset && sub->charset[0] && !mp_charset_is_utf8(sub->charset))
|
sub: add subtitle charset conversion
This code was once part of subreader.c, then traveled to libass, and now
made its way back to the fork of the fork of the original code, MPlayer.
It works pretty much the same as subreader.c, except that we have to
concatenate some packets to do auto-detection. This is rather annoying,
but for all we know the actual source file could be a binary format.
Unlike subreader.c, the iconv context is reopened on each packet. This
is simpler, and with respect to multibyte encodings, more robust.
Reopening is probably not a very fast, but I suspect subtitle charset
conversion is not an operation that happens often or has to be fast.
Also, this auto-detection is disabled for microdvd - this is the only
format we know that has binary data in its packets, but is actually
decoded to text. FFmpeg doesn't really allow us to solve this properly,
because a) the input packets can be binary, and b) the output will be
checked whether it's UTF-8, and if it's not, the output is thrown away
and an error message is printed. We could just recode the decoded
subtitles before sd_ass if it weren't for that.
2013-06-23 20:15:04 +00:00
|
|
|
mp_msg(MSGT_OSD, MSGL_INFO, "Using subtitle charset: %s\n", sub->charset);
|
|
|
|
|
2013-06-24 22:03:37 +00:00
|
|
|
double sub_speed = 1.0;
|
|
|
|
|
2013-06-23 20:14:43 +00:00
|
|
|
// 23.976 FPS is used as default timebase for frame based formats
|
|
|
|
if (sub->video_fps && sh->frame_based)
|
2013-06-24 22:03:37 +00:00
|
|
|
sub_speed *= sub->video_fps / 23.976;
|
2013-06-23 20:14:43 +00:00
|
|
|
|
2013-06-11 19:41:50 +00:00
|
|
|
if (opts->sub_fps && sub->video_fps)
|
2013-06-24 22:03:37 +00:00
|
|
|
sub_speed *= opts->sub_fps / sub->video_fps;
|
|
|
|
|
|
|
|
sub_speed *= opts->sub_speed;
|
|
|
|
|
|
|
|
if (sub_speed != 1.0)
|
|
|
|
multiply_timings(subs, sub_speed);
|
2013-06-11 19:41:50 +00:00
|
|
|
|
|
|
|
if (!opts->suboverlap_enabled)
|
2013-06-23 22:58:10 +00:00
|
|
|
fix_overlaps_and_gaps(subs);
|
2013-06-11 19:41:50 +00:00
|
|
|
|
2013-06-24 21:52:53 +00:00
|
|
|
if (sh->gsh->codec && strcmp(sh->gsh->codec, "microdvd") == 0) {
|
|
|
|
// The last subtitle event in MicroDVD subs can have duration unset,
|
|
|
|
// which means show the subtitle until end of video.
|
|
|
|
// See FFmpeg FATE MicroDVD_capability_tester.sub
|
|
|
|
if (subs->num_packets) {
|
|
|
|
struct demux_packet *last = subs->packets[subs->num_packets - 1];
|
|
|
|
if (last->duration <= 0)
|
|
|
|
last->duration = 10; // arbitrary
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-23 23:04:17 +00:00
|
|
|
add_sub_list(sub, preprocess, subs);
|
2013-06-11 19:39:54 +00:00
|
|
|
|
2013-06-23 22:58:10 +00:00
|
|
|
talloc_free(subs);
|
2013-06-11 19:39:54 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
bool sub_accept_packets_in_advance(struct dec_sub *sub)
|
2013-04-28 19:12:11 +00:00
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
void sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim, double pts,
|
2012-10-04 15:16:36 +00:00
|
|
|
struct sub_bitmaps *res)
|
2012-08-25 18:22:39 +00:00
|
|
|
{
|
2013-06-01 17:44:12 +00:00
|
|
|
struct MPOpts *opts = sub->opts;
|
2013-06-01 17:44:55 +00:00
|
|
|
struct sd *sd = sub_get_last_sd(sub);
|
2012-08-25 18:22:39 +00:00
|
|
|
|
2012-10-04 15:16:36 +00:00
|
|
|
*res = (struct sub_bitmaps) {0};
|
2013-06-01 17:44:55 +00:00
|
|
|
if (sd && opts->sub_visibility) {
|
|
|
|
if (sd->driver->get_bitmaps)
|
|
|
|
sd->driver->get_bitmaps(sd, dim, pts, res);
|
2012-08-25 18:22:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
bool sub_has_get_text(struct dec_sub *sub)
|
2013-04-28 19:12:11 +00:00
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
struct sd *sd = sub_get_last_sd(sub);
|
|
|
|
return sd && sd->driver->get_text;
|
2013-04-28 19:12:11 +00:00
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
char *sub_get_text(struct dec_sub *sub, double pts)
|
2011-01-16 18:03:08 +00:00
|
|
|
{
|
2013-06-01 17:44:12 +00:00
|
|
|
struct MPOpts *opts = sub->opts;
|
2013-06-01 17:44:55 +00:00
|
|
|
struct sd *sd = sub_get_last_sd(sub);
|
2013-06-01 17:44:12 +00:00
|
|
|
char *text = NULL;
|
2013-06-01 17:44:55 +00:00
|
|
|
if (sd && opts->sub_visibility) {
|
|
|
|
if (sd->driver->get_text)
|
|
|
|
text = sd->driver->get_text(sd, pts);
|
2012-08-25 18:22:39 +00:00
|
|
|
}
|
2013-06-01 17:44:12 +00:00
|
|
|
return text;
|
2011-01-16 18:03:08 +00:00
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:12 +00:00
|
|
|
void sub_reset(struct dec_sub *sub)
|
2011-01-16 18:03:08 +00:00
|
|
|
{
|
2013-06-01 17:44:55 +00:00
|
|
|
for (int n = 0; n < sub->num_sd; n++) {
|
|
|
|
if (sub->sd[n]->driver->reset)
|
|
|
|
sub->sd[n]->driver->reset(sub->sd[n]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-28 23:34:11 +00:00
|
|
|
int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg)
|
|
|
|
{
|
|
|
|
for (int n = 0; n < sub->num_sd; n++) {
|
|
|
|
if (sub->sd[n]->driver->control) {
|
|
|
|
int r = sub->sd[n]->driver->control(sub->sd[n], cmd, arg);
|
|
|
|
if (r != CONTROL_UNKNOWN)
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CONTROL_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2013-06-01 17:44:55 +00:00
|
|
|
#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;
|
|
|
|
}
|
2011-01-16 18:03:08 +00:00
|
|
|
}
|