mirror of https://github.com/mpv-player/mpv
player/sub: attempt to detect animated subtitles
The previous commits optimized sub redrawing on still images/terminal so mpv wouldn't redraw so much. There is a gap though. It only assumes static subtitles. Since ASS can be animated, those types of subtitles will always need redraws so we need to build in specific detection for this. We need to build a whitelist of events in ASS that are considered animations and then flag the packet. Additionally, there's a bunch of annoying bookkeeping that has to be done since packets can be dropped on seeks and so on.
This commit is contained in:
parent
25bba7c811
commit
4e5d996c3a
|
@ -59,6 +59,9 @@ typedef struct demux_packet {
|
|||
double start, end; // set to non-NOPTS iff segmented is set
|
||||
|
||||
// subtitles only
|
||||
bool animated;
|
||||
bool seen;
|
||||
int seen_pos;
|
||||
double sub_duration;
|
||||
|
||||
// private
|
||||
|
|
|
@ -312,12 +312,16 @@ static bool update_pkt_cache(struct dec_sub *sub, double video_pts)
|
|||
if (next_pts < pts || end_pts < pts) {
|
||||
if (sub->cached_pkt_pos + 1 < sub->num_cached_pkts) {
|
||||
TA_FREEP(&sub->cached_pkts[sub->cached_pkt_pos]);
|
||||
pkt = NULL;
|
||||
sub->cached_pkt_pos++;
|
||||
}
|
||||
if (next_pts < pts)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pkt && pkt->animated)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
81
sub/sd_ass.c
81
sub/sd_ass.c
|
@ -57,6 +57,8 @@ struct sd_ass_priv {
|
|||
struct mp_osd_res osd;
|
||||
int64_t *seen_packets;
|
||||
int num_seen_packets;
|
||||
bool *packets_animated;
|
||||
int num_packets_animated;
|
||||
bool duration_unknown;
|
||||
};
|
||||
|
||||
|
@ -280,11 +282,55 @@ static int init(struct sd *sd)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Check if subtitle has events that would cause it to be animated inside {}
|
||||
static bool is_animated(char *s)
|
||||
{
|
||||
bool in_tag = false;
|
||||
bool valid_event = false;
|
||||
bool valid_tag = false;
|
||||
while (*s) {
|
||||
if (!in_tag && s[0] == '{')
|
||||
in_tag = true;
|
||||
if (s[0] == '\\') {
|
||||
s++;
|
||||
if (!s[0])
|
||||
break;
|
||||
if (s[0] == 'k' || s[0] == 'K' || s[0] == 't') {
|
||||
valid_event = true;
|
||||
continue;
|
||||
// just bruteforce the multi-letter ones
|
||||
} else if (s[0] == 'f') {
|
||||
if (!strncmp(s, "fad", 3)) {
|
||||
valid_event = true;
|
||||
continue;
|
||||
}
|
||||
} else if (s[0] == 'm') {
|
||||
if (!strncmp(s, "move", 4)) {
|
||||
valid_event = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (in_tag && valid_event && s[0] == '}') {
|
||||
valid_tag = true;
|
||||
break;
|
||||
} else if (s[0] == '}') {
|
||||
in_tag = false;
|
||||
valid_event = false;
|
||||
valid_tag = false;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
return valid_tag;
|
||||
}
|
||||
|
||||
// Note: pkt is not necessarily a fully valid refcounted packet.
|
||||
static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
struct demux_packet *orig_pkt = pkt;
|
||||
ASS_Track *track = ctx->ass_track;
|
||||
int old_n_events = track->n_events;
|
||||
|
||||
for (int n = 0; n < ctx->num_filters; n++) {
|
||||
struct sd_filter *ft = ctx->filters[n];
|
||||
|
@ -300,6 +346,22 @@ static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
|
|||
llrint(pkt->pts * 1000),
|
||||
llrint(pkt->duration * 1000));
|
||||
|
||||
// This bookkeeping is only ever needed for ASS subs
|
||||
if (!ctx->is_converted) {
|
||||
if (!pkt->seen) {
|
||||
for (int n = track->n_events - 1; n >= 0; n--) {
|
||||
if (n + 1 == old_n_events || pkt->animated)
|
||||
break;
|
||||
ASS_Event *event = &track->events[n];
|
||||
pkt->animated = (event->Effect && event->Effect[0]) ||
|
||||
is_animated(event->Text);
|
||||
}
|
||||
MP_TARRAY_APPEND(ctx, ctx->packets_animated, ctx->num_packets_animated, pkt->animated);
|
||||
} else {
|
||||
pkt->animated = ctx->packets_animated[pkt->seen_pos];
|
||||
}
|
||||
}
|
||||
|
||||
if (pkt != orig_pkt)
|
||||
talloc_free(pkt);
|
||||
}
|
||||
|
@ -307,7 +369,7 @@ static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
|
|||
// Test if the packet with the given file position (used as unique ID) was
|
||||
// already consumed. Return false if the packet is new (and add it to the
|
||||
// internal list), and return true if it was already seen.
|
||||
static bool check_packet_seen(struct sd *sd, int64_t pos)
|
||||
static bool check_packet_seen(struct sd *sd, struct demux_packet *packet)
|
||||
{
|
||||
struct sd_ass_priv *priv = sd->priv;
|
||||
int a = 0;
|
||||
|
@ -315,15 +377,18 @@ static bool check_packet_seen(struct sd *sd, int64_t pos)
|
|||
while (a < b) {
|
||||
int mid = a + (b - a) / 2;
|
||||
int64_t val = priv->seen_packets[mid];
|
||||
if (pos == val)
|
||||
if (packet->pos == val) {
|
||||
packet->seen_pos = mid;
|
||||
return true;
|
||||
if (pos > val) {
|
||||
}
|
||||
if (packet->pos > val) {
|
||||
a = mid + 1;
|
||||
} else {
|
||||
b = mid;
|
||||
}
|
||||
}
|
||||
MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, pos);
|
||||
packet->seen_pos = a;
|
||||
MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, packet->pos);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -332,13 +397,13 @@ static bool check_packet_seen(struct sd *sd, int64_t pos)
|
|||
static void decode(struct sd *sd, struct demux_packet *packet)
|
||||
{
|
||||
struct sd_ass_priv *ctx = sd->priv;
|
||||
ASS_Track *track = ctx->ass_track;
|
||||
|
||||
packet->sub_duration = packet->duration;
|
||||
|
||||
ASS_Track *track = ctx->ass_track;
|
||||
if (ctx->converter) {
|
||||
if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 &&
|
||||
check_packet_seen(sd, packet->pos))
|
||||
check_packet_seen(sd, packet))
|
||||
return;
|
||||
|
||||
double sub_pts = 0;
|
||||
|
@ -377,7 +442,9 @@ static void decode(struct sd *sd, struct demux_packet *packet)
|
|||
}
|
||||
} else {
|
||||
// Note that for this packet format, libass has an internal mechanism
|
||||
// for discarding duplicate (already seen) packets.
|
||||
// for discarding duplicate (already seen) packets but we check this
|
||||
// anyways for our purposes for ASS subtitles.
|
||||
packet->seen = check_packet_seen(sd, packet);
|
||||
filter_and_add(sd, packet);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue