[FFmpeg-devel] [PATCH 2/2] lavc/WIP: allow subtitle text format to be ASS without timing
Clément Bœsch
u at pkh.me
Sat Jan 9 14:46:52 CET 2016
From: Clément Bœsch <clement at stupeflix.com>
---
TODO
- ass_split needs to be able to handle dialog event without "Dialogue:
..." so our encoding chain can handle the sane decoded subtitles
- zvbi needs testing
- option sub_text_format needs to be documented and default ready to be
changed
- doxy ff_ass_*
- minor or micro lavc bump
---
libavcodec/ass.c | 96 ++++++++--------------------------------
libavcodec/ass.h | 58 +++++-------------------
libavcodec/assdec.c | 25 +++++------
libavcodec/assenc.c | 9 +---
libavcodec/avcodec.h | 4 ++
libavcodec/ccaption_dec.c | 14 ++++--
libavcodec/jacosubdec.c | 5 ++-
libavcodec/libzvbi-teletextdec.c | 38 ++++++----------
libavcodec/microdvddec.c | 12 ++---
libavcodec/movtextdec.c | 19 ++++----
libavcodec/mpl2dec.c | 8 ++--
libavcodec/options_table.h | 3 ++
libavcodec/realtextdec.c | 7 +--
libavcodec/samidec.c | 14 ++++--
libavcodec/srtdec.c | 17 +++----
libavcodec/subviewerdec.c | 7 +--
libavcodec/textdec.c | 19 +++++---
libavcodec/utils.c | 74 +++++++++++++++++++++++++++++++
libavcodec/webvttdec.c | 11 +++--
19 files changed, 213 insertions(+), 227 deletions(-)
diff --git a/libavcodec/ass.c b/libavcodec/ass.c
index 336c308..71c7242 100644
--- a/libavcodec/ass.c
+++ b/libavcodec/ass.c
@@ -89,101 +89,41 @@ int ff_ass_subtitle_header_default(AVCodecContext *avctx)
ASS_DEFAULT_ALIGNMENT);
}
-static void insert_ts(AVBPrint *buf, int ts)
+char *ff_ass_get_dialog(int readorder, int layer, const char *style,
+ const char *speaker, const char *text)
{
- if (ts == -1) {
- av_bprintf(buf, "9:59:59.99,");
- } else {
- int h, m, s;
-
- h = ts/360000; ts -= 360000*h;
- m = ts/ 6000; ts -= 6000*m;
- s = ts/ 100; ts -= 100*s;
- av_bprintf(buf, "%d:%02d:%02d.%02d,", h, m, s, ts);
- }
-}
-
-int ff_ass_bprint_dialog(AVBPrint *buf, const char *dialog,
- int ts_start, int duration, int raw)
-{
- int dlen;
-
- if (!raw || raw == 2) {
- long int layer = 0;
-
- if (raw == 2) {
- /* skip ReadOrder */
- dialog = strchr(dialog, ',');
- if (!dialog)
- return AVERROR_INVALIDDATA;
- dialog++;
-
- /* extract Layer or Marked */
- layer = strtol(dialog, (char**)&dialog, 10);
- if (*dialog != ',')
- return AVERROR_INVALIDDATA;
- dialog++;
- }
- av_bprintf(buf, "Dialogue: %ld,", layer);
- insert_ts(buf, ts_start);
- insert_ts(buf, duration == -1 ? -1 : ts_start + duration);
- if (raw != 2)
- av_bprintf(buf, "Default,,0,0,0,,");
- }
-
- dlen = strcspn(dialog, "\n");
- dlen += dialog[dlen] == '\n';
-
- av_bprintf(buf, "%.*s", dlen, dialog);
- if (raw == 2)
- av_bprintf(buf, "\r\n");
-
- return dlen;
+ return av_asprintf("%d,%d,%s,%s,0,0,0,,%s",
+ readorder, layer, style ? style : "Default",
+ speaker ? speaker : "", text);
}
int ff_ass_add_rect(AVSubtitle *sub, const char *dialog,
- int ts_start, int duration, int raw)
+ int readorder, int layer, const char *style,
+ const char *speaker)
{
- AVBPrint buf;
- int ret, dlen;
+ char *ass_str;
AVSubtitleRect **rects;
- av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
- if ((ret = ff_ass_bprint_dialog(&buf, dialog, ts_start, duration, raw)) < 0)
- goto err;
- dlen = ret;
- if (!av_bprint_is_complete(&buf))
- goto errnomem;
-
rects = av_realloc_array(sub->rects, (sub->num_rects+1), sizeof(*sub->rects));
if (!rects)
- goto errnomem;
+ return AVERROR(ENOMEM);
sub->rects = rects;
- sub->end_display_time = FFMAX(sub->end_display_time, 10 * duration);
rects[sub->num_rects] = av_mallocz(sizeof(*rects[0]));
if (!rects[sub->num_rects])
- goto errnomem;
+ return AVERROR(ENOMEM);
rects[sub->num_rects]->type = SUBTITLE_ASS;
- ret = av_bprint_finalize(&buf, &rects[sub->num_rects]->ass);
- if (ret < 0)
- goto err;
+ ass_str = ff_ass_get_dialog(readorder, layer, style, speaker, dialog);
+ if (!ass_str)
+ return AVERROR(ENOMEM);
+ rects[sub->num_rects]->ass = ass_str;
sub->num_rects++;
- return dlen;
-
-errnomem:
- ret = AVERROR(ENOMEM);
-err:
- av_bprint_finalize(&buf, NULL);
- return ret;
+ return 0;
}
-int ff_ass_add_rect_bprint(AVSubtitle *sub, AVBPrint *buf,
- int ts_start, int duration)
+void ff_ass_decoder_flush(AVCodecContext *avctx)
{
- av_bprintf(buf, "\r\n");
- if (!av_bprint_is_complete(buf))
- return AVERROR(ENOMEM);
- return ff_ass_add_rect(sub, buf->str, ts_start, duration, 0);
+ FFASSDecoderContext *s = avctx->priv_data;
+ s->readorder = 0;
}
void ff_ass_bprint_text_event(AVBPrint *buf, const char *p, int size,
diff --git a/libavcodec/ass.h b/libavcodec/ass.h
index 14a1ed2..3f1347b 100644
--- a/libavcodec/ass.h
+++ b/libavcodec/ass.h
@@ -42,6 +42,10 @@
#define ASS_DEFAULT_ALIGNMENT 2
/** @} */
+typedef struct FFASSDecoderContext {
+ int readorder;
+} FFASSDecoderContext;
+
/**
* Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS.
*
@@ -72,56 +76,14 @@ int ff_ass_subtitle_header(AVCodecContext *avctx,
*/
int ff_ass_subtitle_header_default(AVCodecContext *avctx);
-/**
- * Add an ASS dialog line to an AVSubtitle as a new AVSubtitleRect.
- *
- * @param sub pointer to the AVSubtitle
- * @param dialog ASS dialog to add to sub
- * @param ts_start start timestamp for this dialog (in 1/100 second unit)
- * @param duration duration for this dialog (in 1/100 second unit), can be -1
- * to last until the end of the presentation
- * @param raw when set to 2, it indicates that dialog contains an ASS
- * dialog line as muxed in Matroska
- * when set to 1, it indicates that dialog contains a whole SSA
- * dialog line which should be copied as is.
- * when set to 0, it indicates that dialog contains only the Text
- * part of the ASS dialog line, the rest of the line
- * will be generated.
- * @return number of characters read from dialog. It can be less than the whole
- * length of dialog, if dialog contains several lines of text.
- * A negative value indicates an error.
- */
+// TODO doxy
+char *ff_ass_get_dialog(int readorder, int layer, const char *style,
+ const char *speaker, const char *text);
int ff_ass_add_rect(AVSubtitle *sub, const char *dialog,
- int ts_start, int duration, int raw);
+ int readorder, int layer, const char *style,
+ const char *speaker);
-/**
- * Same as ff_ass_add_rect, but taking an AVBPrint buffer instead of a
- * string, and assuming raw=0.
- */
-int ff_ass_add_rect_bprint(AVSubtitle *sub, AVBPrint *buf,
- int ts_start, int duration);
-
-/**
- * Add an ASS dialog line to an AVBPrint buffer.
- *
- * @param buf pointer to an initialized AVBPrint buffer
- * @param dialog ASS dialog to add to sub
- * @param ts_start start timestamp for this dialog (in 1/100 second unit)
- * @param duration duration for this dialog (in 1/100 second unit), can be -1
- * to last until the end of the presentation
- * @param raw when set to 2, it indicates that dialog contains an ASS
- * dialog line as muxed in Matroska
- * when set to 1, it indicates that dialog contains a whole SSA
- * dialog line which should be copied as is.
- * when set to 0, it indicates that dialog contains only the Text
- * part of the ASS dialog line, the rest of the line
- * will be generated.
- * @return number of characters read from dialog. It can be less than the whole
- * length of dialog, if dialog contains several lines of text.
- * A negative value indicates an error.
- */
-int ff_ass_bprint_dialog(AVBPrint *buf, const char *dialog,
- int ts_start, int duration, int raw);
+void ff_ass_decoder_flush(AVCodecContext *avctx);
/**
* Escape a text subtitle using ASS syntax into an AVBPrint buffer.
diff --git a/libavcodec/assdec.c b/libavcodec/assdec.c
index 624052e..3178f29 100644
--- a/libavcodec/assdec.c
+++ b/libavcodec/assdec.c
@@ -40,24 +40,23 @@ static av_cold int ass_decode_init(AVCodecContext *avctx)
static int ass_decode_frame(AVCodecContext *avctx, void *data, int *got_sub_ptr,
AVPacket *avpkt)
{
- int ret;
AVSubtitle *sub = data;
- const char *ptr = avpkt->data;
- static const AVRational ass_tb = {1, 100};
- const int ts_start = av_rescale_q(avpkt->pts, avctx->time_base, ass_tb);
- const int ts_duration = av_rescale_q(avpkt->duration, avctx->time_base, ass_tb);
if (avpkt->size <= 0)
return avpkt->size;
- ret = ff_ass_add_rect(sub, ptr, ts_start, ts_duration, 2);
- if (ret < 0) {
- if (ret == AVERROR_INVALIDDATA)
- av_log(avctx, AV_LOG_ERROR, "Invalid ASS packet\n");
- return ret;
- }
-
- *got_sub_ptr = avpkt->size > 0;
+ sub->rects = av_malloc(sizeof(*sub->rects));
+ if (!sub->rects)
+ return AVERROR(ENOMEM);
+ sub->rects[0] = av_mallocz(sizeof(*sub->rects[0]));
+ if (!sub->rects[0])
+ return AVERROR(ENOMEM);
+ sub->num_rects = 1;
+ sub->rects[0]->type = SUBTITLE_ASS;
+ sub->rects[0]->ass = av_strdup(avpkt->data);
+ if (!sub->rects[0]->ass)
+ return AVERROR(ENOMEM);
+ *got_sub_ptr = 1;
return avpkt->size;
}
diff --git a/libavcodec/assenc.c b/libavcodec/assenc.c
index 06aa916..4e9825c 100644
--- a/libavcodec/assenc.c
+++ b/libavcodec/assenc.c
@@ -60,13 +60,7 @@ static int ass_encode_frame(AVCodecContext *avctx,
return -1;
}
- if (strncmp(ass, "Dialogue: ", 10)) {
- av_log(avctx, AV_LOG_ERROR, "AVSubtitle rectangle ass \"%s\""
- " does not look like a SSA markup\n", ass);
- return AVERROR_INVALIDDATA;
- }
-
- // TODO: reindent
+ if (!strncmp(ass, "Dialogue: ", 10)) {
if (i > 0) {
av_log(avctx, AV_LOG_ERROR, "ASS encoder supports only one "
"ASS rectangle field.\n");
@@ -91,6 +85,7 @@ static int ass_encode_frame(AVCodecContext *avctx,
snprintf(ass_line, sizeof(ass_line), "%d,%ld,%s", ++s->id, layer, p);
ass_line[strcspn(ass_line, "\r\n")] = 0;
ass = ass_line;
+ }
len = av_strlcpy(buf+total_len, ass, bufsize-total_len);
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index f365775..3f19418 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -3291,6 +3291,10 @@ typedef struct AVCodecContext {
#define FF_SUB_CHARENC_MODE_AUTOMATIC 0 ///< libavcodec will select the mode itself
#define FF_SUB_CHARENC_MODE_PRE_DECODER 1 ///< the AVPacket data needs to be recoded to UTF-8 before being fed to the decoder, requires iconv
+ int sub_text_format;
+#define FF_SUB_TEXT_FMT_ASS 0
+#define FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS 1
+
/**
* Skip processing alpha if supported by codec.
* Note that if the format uses pre-multiplied alpha (common with VP6,
diff --git a/libavcodec/ccaption_dec.c b/libavcodec/ccaption_dec.c
index fc6431b..5139c39 100644
--- a/libavcodec/ccaption_dec.c
+++ b/libavcodec/ccaption_dec.c
@@ -133,6 +133,7 @@ typedef struct CCaptionSubContext {
char prev_cmd[2];
/* buffer to store pkt data */
AVBufferRef *pktbuf;
+ int readorder;
} CCaptionSubContext;
@@ -536,13 +537,13 @@ static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avp
process_cc608(ctx, avpkt->pts, *(bptr + i + 1) & 0x7f, *(bptr + i + 2) & 0x7f);
if (ctx->buffer_changed && *ctx->buffer.str)
{
- int start_time = av_rescale_q(ctx->start_time, avctx->time_base, ass_tb);
- int end_time = av_rescale_q(ctx->end_time, avctx->time_base, ass_tb);
ff_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
- ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, end_time - start_time);
+ ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL);
if (ret < 0)
return ret;
sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
+ sub->end_display_time = av_rescale_q(ctx->end_time - ctx->start_time,
+ avctx->time_base, av_make_q(1, 1000));
ctx->buffer_changed = 0;
}
}
@@ -551,6 +552,12 @@ static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avp
return ret;
}
+static void cc_flush(AVCodecContext *avctx)
+{
+ CCaptionSubContext *ctx = avctx->priv_data;
+ ctx->readorder = 0;
+}
+
static const AVOption options[] = {
{NULL}
};
@@ -572,4 +579,5 @@ AVCodec ff_ccaption_decoder = {
.close = close_decoder,
.decode = decode,
.priv_class = &ccaption_dec_class,
+ .flush = cc_flush,
};
diff --git a/libavcodec/jacosubdec.c b/libavcodec/jacosubdec.c
index 0c97eb8..cdb372a 100644
--- a/libavcodec/jacosubdec.c
+++ b/libavcodec/jacosubdec.c
@@ -167,6 +167,7 @@ static int jacosub_decode_frame(AVCodecContext *avctx,
int ret;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
+ FFASSDecoderContext *s = avctx->priv_data;
if (avpkt->size <= 0)
goto end;
@@ -181,7 +182,7 @@ static int jacosub_decode_frame(AVCodecContext *avctx,
av_bprint_init(&buffer, JSS_MAX_LINESIZE, JSS_MAX_LINESIZE);
jacosub_to_ass(avctx, &buffer, ptr);
- ret = ff_ass_add_rect_bprint(sub, &buffer, avpkt->pts, avpkt->duration);
+ ret = ff_ass_add_rect(sub, buffer.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buffer, NULL);
if (ret < 0)
return ret;
@@ -199,4 +200,6 @@ AVCodec ff_jacosub_decoder = {
.id = AV_CODEC_ID_JACOSUB,
.init = ff_ass_subtitle_header_default,
.decode = jacosub_decode_frame,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
diff --git a/libavcodec/libzvbi-teletextdec.c b/libavcodec/libzvbi-teletextdec.c
index 4e59531..a761ab2 100644
--- a/libavcodec/libzvbi-teletextdec.c
+++ b/libavcodec/libzvbi-teletextdec.c
@@ -71,6 +71,8 @@ typedef struct TeletextContext
vbi_export * ex;
#endif
vbi_sliced sliced[MAX_SLICES];
+
+ int readorder;
} TeletextContext;
static int chop_spaces_utf8(const unsigned char* t, int len)
@@ -92,37 +94,21 @@ static void subtitle_rect_free(AVSubtitleRect **sub_rect)
av_freep(sub_rect);
}
-static int create_ass_text(TeletextContext *ctx, const char *text, char **ass)
+static char *create_ass_text(TeletextContext *ctx, const char *text)
{
int ret;
- AVBPrint buf, buf2;
- const int ts_start = av_rescale_q(ctx->pts, AV_TIME_BASE_Q, (AVRational){1, 100});
- const int ts_duration = av_rescale_q(ctx->sub_duration, (AVRational){1, 1000}, (AVRational){1, 100});
+ char *dialog;
+ AVBPrint buf;
- /* First we escape the plain text into buf. */
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0);
- av_bprintf(&buf, "\r\n");
-
if (!av_bprint_is_complete(&buf)) {
av_bprint_finalize(&buf, NULL);
- return AVERROR(ENOMEM);
+ return NULL;
}
-
- /* Then we create the ass dialog line in buf2 from the escaped text in buf. */
- av_bprint_init(&buf2, 0, AV_BPRINT_SIZE_UNLIMITED);
- ff_ass_bprint_dialog(&buf2, buf.str, ts_start, ts_duration, 0);
+ dialog = ff_ass_get_dialog(ctx->readorder++, 0, NULL, NULL, buf.str);
av_bprint_finalize(&buf, NULL);
-
- if (!av_bprint_is_complete(&buf2)) {
- av_bprint_finalize(&buf2, NULL);
- return AVERROR(ENOMEM);
- }
-
- if ((ret = av_bprint_finalize(&buf2, ass)) < 0)
- return ret;
-
- return 0;
+ return dialog;
}
/* Draw a page as text */
@@ -178,11 +164,12 @@ static int gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page
}
if (buf.len) {
- int ret;
sub_rect->type = SUBTITLE_ASS;
- if ((ret = create_ass_text(ctx, buf.str, &sub_rect->ass)) < 0) {
+ sub_rect->ass = create_ass_text(ctx, buf.str);
+
+ if (!sub_rect->ass) {
av_bprint_finalize(&buf, NULL);
- return ret;
+ return AVERROR(ENOMEM);
}
av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass);
} else {
@@ -534,6 +521,7 @@ static int teletext_close_decoder(AVCodecContext *avctx)
vbi_decoder_delete(ctx->vbi);
ctx->vbi = NULL;
ctx->pts = AV_NOPTS_VALUE;
+ ctx->readorder = 0;
return 0;
}
diff --git a/libavcodec/microdvddec.c b/libavcodec/microdvddec.c
index 9035892..290af1a 100644
--- a/libavcodec/microdvddec.c
+++ b/libavcodec/microdvddec.c
@@ -280,6 +280,7 @@ static int microdvd_decode_frame(AVCodecContext *avctx,
AVBPrint new_line;
char *line = avpkt->data;
char *end = avpkt->data + avpkt->size;
+ FFASSDecoderContext *s = avctx->priv_data;
struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}};
if (avpkt->size <= 0)
@@ -308,14 +309,7 @@ static int microdvd_decode_frame(AVCodecContext *avctx,
}
}
if (new_line.len) {
- int ret;
- int64_t start = avpkt->pts;
- int64_t duration = avpkt->duration;
- int ts_start = av_rescale_q(start, avctx->time_base, (AVRational){1,100});
- int ts_duration = duration != -1 ?
- av_rescale_q(duration, avctx->time_base, (AVRational){1,100}) : -1;
-
- ret = ff_ass_add_rect_bprint(sub, &new_line, ts_start, ts_duration);
+ int ret = ff_ass_add_rect(sub, new_line.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&new_line, NULL);
if (ret < 0)
return ret;
@@ -380,4 +374,6 @@ AVCodec ff_microdvd_decoder = {
.id = AV_CODEC_ID_MICRODVD,
.init = microdvd_init,
.decode = microdvd_decode_frame,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
diff --git a/libavcodec/movtextdec.c b/libavcodec/movtextdec.c
index 257d598..e06c094 100644
--- a/libavcodec/movtextdec.c
+++ b/libavcodec/movtextdec.c
@@ -99,6 +99,7 @@ typedef struct {
uint64_t tracksize;
int size_var;
int count_s, count_f;
+ int readorder;
} MovTextContext;
typedef struct {
@@ -423,7 +424,7 @@ static int mov_text_decode_frame(AVCodecContext *avctx,
{
AVSubtitle *sub = data;
MovTextContext *m = avctx->priv_data;
- int ret, ts_start, ts_end;
+ int ret;
AVBPrint buf;
char *ptr = avpkt->data;
char *end;
@@ -453,13 +454,6 @@ static int mov_text_decode_frame(AVCodecContext *avctx,
end = ptr + FFMIN(2 + text_length, avpkt->size);
ptr += 2;
- ts_start = av_rescale_q(avpkt->pts,
- avctx->time_base,
- (AVRational){1,100});
- ts_end = av_rescale_q(avpkt->pts + avpkt->duration,
- avctx->time_base,
- (AVRational){1,100});
-
tsmb_size = 0;
m->tracksize = 2 + text_length;
m->style_entries = 0;
@@ -505,7 +499,7 @@ static int mov_text_decode_frame(AVCodecContext *avctx,
} else
text_to_ass(&buf, ptr, end, m);
- ret = ff_ass_add_rect_bprint(sub, &buf, ts_start, ts_end - ts_start);
+ ret = ff_ass_add_rect(sub, buf.str, m->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buf, NULL);
if (ret < 0)
return ret;
@@ -520,6 +514,12 @@ static int mov_text_decode_close(AVCodecContext *avctx)
return 0;
}
+static void mov_text_flush(AVCodecContext *avctx)
+{
+ MovTextContext *m = avctx->priv_data;
+ m->readorder = 0;
+}
+
AVCodec ff_movtext_decoder = {
.name = "mov_text",
.long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"),
@@ -529,4 +529,5 @@ AVCodec ff_movtext_decoder = {
.init = mov_text_init,
.decode = mov_text_decode_frame,
.close = mov_text_decode_close,
+ .flush = mov_text_flush,
};
diff --git a/libavcodec/mpl2dec.c b/libavcodec/mpl2dec.c
index ca95dc8..409e4b3 100644
--- a/libavcodec/mpl2dec.c
+++ b/libavcodec/mpl2dec.c
@@ -69,13 +69,11 @@ static int mpl2_decode_frame(AVCodecContext *avctx, void *data,
AVBPrint buf;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
- const int ts_start = av_rescale_q(avpkt->pts, avctx->time_base, (AVRational){1,100});
- const int ts_duration = avpkt->duration != -1 ?
- av_rescale_q(avpkt->duration, avctx->time_base, (AVRational){1,100}) : -1;
+ FFASSDecoderContext *s = avctx->priv_data;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
if (ptr && avpkt->size > 0 && *ptr && !mpl2_event_to_ass(&buf, ptr))
- ret = ff_ass_add_rect_bprint(sub, &buf, ts_start, ts_duration);
+ ret = ff_ass_add_rect(sub, buf.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buf, NULL);
if (ret < 0)
return ret;
@@ -90,4 +88,6 @@ AVCodec ff_mpl2_decoder = {
.id = AV_CODEC_ID_MPL2,
.decode = mpl2_decode_frame,
.init = ff_ass_subtitle_header_default,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
diff --git a/libavcodec/options_table.h b/libavcodec/options_table.h
index cc11900..c805465 100644
--- a/libavcodec/options_table.h
+++ b/libavcodec/options_table.h
@@ -493,6 +493,9 @@ static const AVOption avcodec_options[] = {
{"do_nothing", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_SUB_CHARENC_MODE_DO_NOTHING}, INT_MIN, INT_MAX, S|D, "sub_charenc_mode"},
{"auto", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_SUB_CHARENC_MODE_AUTOMATIC}, INT_MIN, INT_MAX, S|D, "sub_charenc_mode"},
{"pre_decoder", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_SUB_CHARENC_MODE_PRE_DECODER}, INT_MIN, INT_MAX, S|D, "sub_charenc_mode"},
+{"sub_text_format", "set decoded text subtitle format", OFFSET(sub_text_format), AV_OPT_TYPE_INT, {.i64 = FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS}, 0, 1, S|D, "sub_text_format"},
+{"ass", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_SUB_TEXT_FMT_ASS}, INT_MIN, INT_MAX, S|D, "sub_text_format"},
+{"ass_with_timings", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS}, INT_MIN, INT_MAX, S|D, "sub_text_format"},
{"refcounted_frames", NULL, OFFSET(refcounted_frames), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, A|V|D },
#if FF_API_SIDEDATA_ONLY_PKT
{"side_data_only_packets", NULL, OFFSET(side_data_only_packets), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, A|V|E },
diff --git a/libavcodec/realtextdec.c b/libavcodec/realtextdec.c
index 870953b..5084781 100644
--- a/libavcodec/realtextdec.c
+++ b/libavcodec/realtextdec.c
@@ -61,13 +61,12 @@ static int realtext_decode_frame(AVCodecContext *avctx,
int ret = 0;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
+ FFASSDecoderContext *s = avctx->priv_data;
AVBPrint buf;
av_bprint_init(&buf, 0, 4096);
- // note: no need to rescale pts & duration since they are in the same
- // timebase as ASS (1/100)
if (ptr && avpkt->size > 0 && !rt_event_to_ass(&buf, ptr))
- ret = ff_ass_add_rect_bprint(sub, &buf, avpkt->pts, avpkt->duration);
+ ret = ff_ass_add_rect(sub, buf.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buf, NULL);
if (ret < 0)
return ret;
@@ -82,4 +81,6 @@ AVCodec ff_realtext_decoder = {
.id = AV_CODEC_ID_REALTEXT,
.decode = realtext_decode_frame,
.init = ff_ass_subtitle_header_default,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
diff --git a/libavcodec/samidec.c b/libavcodec/samidec.c
index 95f35ab..2874e21 100644
--- a/libavcodec/samidec.c
+++ b/libavcodec/samidec.c
@@ -35,6 +35,7 @@ typedef struct {
AVBPrint encoded_source;
AVBPrint encoded_content;
AVBPrint full;
+ int readorder;
} SAMIContext;
static int sami_paragraph_to_ass(AVCodecContext *avctx, const char *src)
@@ -131,10 +132,8 @@ static int sami_decode_frame(AVCodecContext *avctx,
SAMIContext *sami = avctx->priv_data;
if (ptr && avpkt->size > 0 && !sami_paragraph_to_ass(avctx, ptr)) {
- int ts_start = av_rescale_q(avpkt->pts, avctx->time_base, (AVRational){1,100});
- int ts_duration = avpkt->duration != -1 ?
- av_rescale_q(avpkt->duration, avctx->time_base, (AVRational){1,100}) : -1;
- int ret = ff_ass_add_rect_bprint(sub, &sami->full, ts_start, ts_duration);
+ // TODO: pass escaped sami->encoded_source.str as source
+ int ret = ff_ass_add_rect(sub, sami->full.str, sami->readorder++, 0, NULL, NULL);
if (ret < 0)
return ret;
}
@@ -164,6 +163,12 @@ static av_cold int sami_close(AVCodecContext *avctx)
return 0;
}
+static void sami_flush(AVCodecContext *avctx)
+{
+ SAMIContext *sami = avctx->priv_data;
+ sami->readorder = 0;
+}
+
AVCodec ff_sami_decoder = {
.name = "sami",
.long_name = NULL_IF_CONFIG_SMALL("SAMI subtitle"),
@@ -173,4 +178,5 @@ AVCodec ff_sami_decoder = {
.init = sami_init,
.close = sami_close,
.decode = sami_decode_frame,
+ .flush = sami_flush,
};
diff --git a/libavcodec/srtdec.c b/libavcodec/srtdec.c
index 542dd35..30930c8 100644
--- a/libavcodec/srtdec.c
+++ b/libavcodec/srtdec.c
@@ -57,9 +57,10 @@ static int srt_decode_frame(AVCodecContext *avctx,
{
AVSubtitle *sub = data;
AVBPrint buffer;
- int ts_start, ts_end, x1 = -1, y1 = -1, x2 = -1, y2 = -1;
+ int x1 = -1, y1 = -1, x2 = -1, y2 = -1;
int size, ret;
const uint8_t *p = av_packet_get_side_data(avpkt, AV_PKT_DATA_SUBTITLE_POSITION, &size);
+ FFASSDecoderContext *s = avctx->priv_data;
if (p && size == 16) {
x1 = AV_RL32(p );
@@ -73,16 +74,8 @@ static int srt_decode_frame(AVCodecContext *avctx,
av_bprint_init(&buffer, 0, AV_BPRINT_SIZE_UNLIMITED);
- // Do final divide-by-10 outside rescale to force rounding down.
- ts_start = av_rescale_q(avpkt->pts,
- avctx->time_base,
- (AVRational){1,100});
- ts_end = av_rescale_q(avpkt->pts + avpkt->duration,
- avctx->time_base,
- (AVRational){1,100});
-
srt_to_ass(avctx, &buffer, avpkt->data, x1, y1, x2, y2);
- ret = ff_ass_add_rect_bprint(sub, &buffer, ts_start, ts_end-ts_start);
+ ret = ff_ass_add_rect(sub, buffer.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buffer, NULL);
if (ret < 0)
return ret;
@@ -100,6 +93,8 @@ AVCodec ff_srt_decoder = {
.id = AV_CODEC_ID_SUBRIP,
.init = ff_ass_subtitle_header_default,
.decode = srt_decode_frame,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
#endif
@@ -111,5 +106,7 @@ AVCodec ff_subrip_decoder = {
.id = AV_CODEC_ID_SUBRIP,
.init = ff_ass_subtitle_header_default,
.decode = srt_decode_frame,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
#endif
diff --git a/libavcodec/subviewerdec.c b/libavcodec/subviewerdec.c
index a008828..805c7dd 100644
--- a/libavcodec/subviewerdec.c
+++ b/libavcodec/subviewerdec.c
@@ -52,13 +52,12 @@ static int subviewer_decode_frame(AVCodecContext *avctx,
int ret = 0;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
+ FFASSDecoderContext *s = avctx->priv_data;
AVBPrint buf;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
- // note: no need to rescale pts & duration since they are in the same
- // timebase as ASS (1/100)
if (ptr && avpkt->size > 0 && !subviewer_event_to_ass(&buf, ptr))
- ret = ff_ass_add_rect_bprint(sub, &buf, avpkt->pts, avpkt->duration);
+ ret = ff_ass_add_rect(sub, buf.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buf, NULL);
if (ret < 0)
return ret;
@@ -73,4 +72,6 @@ AVCodec ff_subviewer_decoder = {
.id = AV_CODEC_ID_SUBVIEWER,
.decode = subviewer_decode_frame,
.init = ff_ass_subtitle_header_default,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
diff --git a/libavcodec/textdec.c b/libavcodec/textdec.c
index a6c8722..4e2ff2c 100644
--- a/libavcodec/textdec.c
+++ b/libavcodec/textdec.c
@@ -32,6 +32,7 @@ typedef struct {
AVClass *class;
const char *linebreaks;
int keep_ass_markup;
+ int readorder;
} TextContext;
#define OFFSET(x) offsetof(TextContext, x)
@@ -48,15 +49,12 @@ static int text_decode_frame(AVCodecContext *avctx, void *data,
AVBPrint buf;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
- const TextContext *text = avctx->priv_data;
- const int ts_start = av_rescale_q(avpkt->pts, avctx->time_base, (AVRational){1,100});
- const int ts_duration = avpkt->duration != -1 ?
- av_rescale_q(avpkt->duration, avctx->time_base, (AVRational){1,100}) : -1;
+ TextContext *text = avctx->priv_data;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
if (ptr && avpkt->size > 0 && *ptr) {
ff_ass_bprint_text_event(&buf, ptr, avpkt->size, text->linebreaks, text->keep_ass_markup);
- ret = ff_ass_add_rect_bprint(sub, &buf, ts_start, ts_duration);
+ ret = ff_ass_add_rect(sub, buf.str, text->readorder++, 0, NULL, NULL);
}
av_bprint_finalize(&buf, NULL);
if (ret < 0)
@@ -65,6 +63,12 @@ static int text_decode_frame(AVCodecContext *avctx, void *data,
return avpkt->size;
}
+static void text_flush(AVCodecContext *avctx)
+{
+ TextContext *text = avctx->priv_data;
+ text->readorder = 0;
+}
+
#define DECLARE_CLASS(decname) static const AVClass decname ## _decoder_class = { \
.class_name = #decname " decoder", \
.item_name = av_default_item_name, \
@@ -85,6 +89,7 @@ AVCodec ff_text_decoder = {
.decode = text_decode_frame,
.init = ff_ass_subtitle_header_default,
.priv_class = &text_decoder_class,
+ .flush = text_flush,
};
#endif
@@ -110,6 +115,7 @@ AVCodec ff_vplayer_decoder = {
.decode = text_decode_frame,
.init = linebreak_init,
.priv_class = &vplayer_decoder_class,
+ .flush = text_flush,
};
#endif
@@ -126,6 +132,7 @@ AVCodec ff_stl_decoder = {
.decode = text_decode_frame,
.init = linebreak_init,
.priv_class = &stl_decoder_class,
+ .flush = text_flush,
};
#endif
@@ -142,6 +149,7 @@ AVCodec ff_pjs_decoder = {
.decode = text_decode_frame,
.init = linebreak_init,
.priv_class = &pjs_decoder_class,
+ .flush = text_flush,
};
#endif
@@ -158,6 +166,7 @@ AVCodec ff_subviewer1_decoder = {
.decode = text_decode_frame,
.init = linebreak_init,
.priv_class = &subviewer1_decoder_class,
+ .flush = text_flush,
};
#endif
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 63439bb..5bd1d53 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -2400,6 +2400,76 @@ static int utf8_check(const uint8_t *str)
return 1;
}
+static void insert_ts(AVBPrint *buf, int ts)
+{
+ if (ts == -1) {
+ av_bprintf(buf, "9:59:59.99,");
+ } else {
+ int h, m, s;
+
+ h = ts/360000; ts -= 360000*h;
+ m = ts/ 6000; ts -= 6000*m;
+ s = ts/ 100; ts -= 100*s;
+ av_bprintf(buf, "%d:%02d:%02d.%02d,", h, m, s, ts);
+ }
+}
+
+static int convert_sub_to_old_ass_form(AVSubtitle *sub, const AVPacket *pkt, AVRational tb)
+{
+ int i;
+ AVBPrint buf;
+
+ av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+ for (i = 0; i < sub->num_rects; i++) {
+ char *final_dialog;
+ const char *dialog;
+ AVSubtitleRect *rect = sub->rects[i];
+ int ts_start, ts_duration = -1;
+ long int layer;
+
+ if (rect->type != SUBTITLE_ASS || !strncmp(rect->ass, "Dialogue ", 10))
+ continue;
+
+ av_bprint_clear(&buf);
+
+ /* skip ReadOrder */
+ dialog = strchr(rect->ass, ',');
+ if (!dialog)
+ continue;
+ dialog++;
+
+ /* extract Layer or Marked */
+ layer = strtol(dialog, (char**)&dialog, 10);
+ if (*dialog != ',')
+ continue;
+ dialog++;
+
+ /* rescale timing to ASS time base (ms) */
+ ts_start = av_rescale_q(pkt->pts, tb, av_make_q(1, 100));
+ if (pkt->duration != -1)
+ ts_duration = av_rescale_q(pkt->duration, tb, av_make_q(1, 100));
+ sub->end_display_time = FFMAX(sub->end_display_time, 10 * ts_duration);
+
+ /* construct ASS (standalone file form with timestamps) string */
+ av_bprintf(&buf, "Dialogue: %ld,", layer);
+ insert_ts(&buf, ts_start);
+ insert_ts(&buf, ts_duration == -1 ? -1 : ts_start + ts_duration);
+ av_bprintf(&buf, "%s\r\n", dialog);
+
+ final_dialog = av_strdup(buf.str);
+ if (!av_bprint_is_complete(&buf) || !final_dialog) {
+ av_bprint_finalize(&buf, NULL);
+ return AVERROR(ENOMEM);
+ }
+ av_freep(&rect->ass);
+ rect->ass = final_dialog;
+ }
+
+ av_bprint_finalize(&buf, NULL);
+ return 0;
+}
+
int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
int *got_sub_ptr,
AVPacket *avpkt)
@@ -2450,6 +2520,10 @@ int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub,
av_assert1((ret >= 0) >= !!*got_sub_ptr &&
!!*got_sub_ptr >= !!sub->num_rects);
+ if (avctx->sub_text_format == FF_SUB_TEXT_FMT_ASS_WITH_TIMINGS
+ && *got_sub_ptr && sub->num_rects)
+ ret = convert_sub_to_old_ass_form(sub, avpkt, avctx->time_base);
+
if (sub->num_rects && !sub->end_display_time && avpkt->duration &&
avctx->pkt_timebase.num) {
AVRational ms = { 1, 1000 };
diff --git a/libavcodec/webvttdec.c b/libavcodec/webvttdec.c
index 7354588..7b2d175 100644
--- a/libavcodec/webvttdec.c
+++ b/libavcodec/webvttdec.c
@@ -85,15 +85,12 @@ static int webvtt_decode_frame(AVCodecContext *avctx,
int ret = 0;
AVSubtitle *sub = data;
const char *ptr = avpkt->data;
+ FFASSDecoderContext *s = avctx->priv_data;
AVBPrint buf;
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
- if (ptr && avpkt->size > 0 && !webvtt_event_to_ass(&buf, ptr)) {
- int ts_start = av_rescale_q(avpkt->pts, avctx->time_base, (AVRational){1,100});
- int ts_duration = avpkt->duration != -1 ?
- av_rescale_q(avpkt->duration, avctx->time_base, (AVRational){1,100}) : -1;
- ret = ff_ass_add_rect_bprint(sub, &buf, ts_start, ts_duration);
- }
+ if (ptr && avpkt->size > 0 && !webvtt_event_to_ass(&buf, ptr))
+ ret = ff_ass_add_rect(sub, buf.str, s->readorder++, 0, NULL, NULL);
av_bprint_finalize(&buf, NULL);
if (ret < 0)
return ret;
@@ -108,4 +105,6 @@ AVCodec ff_webvtt_decoder = {
.id = AV_CODEC_ID_WEBVTT,
.decode = webvtt_decode_frame,
.init = ff_ass_subtitle_header_default,
+ .flush = ff_ass_decoder_flush,
+ .priv_data_size = sizeof(FFASSDecoderContext),
};
--
2.7.0
More information about the ffmpeg-devel
mailing list