[FFmpeg-devel] [PATCH 2/6] lavc: allow subtitle text format to be ASS without timing

Clément Bœsch u at pkh.me
Sun Feb 21 13:08:29 CET 2016


---
 libavcodec/ass.c                     | 96 +++++++-----------------------------
 libavcodec/ass.h                     | 56 ++++++---------------
 libavcodec/ass_split.c               | 49 ++++++++++++++++++
 libavcodec/ass_split.h               | 15 ++++++
 libavcodec/assdec.c                  | 25 +++++-----
 libavcodec/assenc.c                  |  9 +---
 libavcodec/avcodec.h                 |  4 ++
 libavcodec/ccaption_dec.c            | 24 +++++----
 libavcodec/jacosubdec.c              |  5 +-
 libavcodec/libzvbi-teletextdec.c     | 38 +++++---------
 libavcodec/microdvddec.c             | 12 ++---
 libavcodec/movtextdec.c              | 19 +++----
 libavcodec/movtextenc.c              | 12 ++++-
 libavcodec/mpl2dec.c                 |  8 +--
 libavcodec/options_table.h           |  3 ++
 libavcodec/realtextdec.c             |  7 +--
 libavcodec/samidec.c                 | 14 ++++--
 libavcodec/srtdec.c                  | 17 +++----
 libavcodec/srtenc.c                  | 14 +++++-
 libavcodec/subviewerdec.c            |  7 +--
 libavcodec/textdec.c                 | 19 +++++--
 libavcodec/utils.c                   | 74 +++++++++++++++++++++++++++
 libavcodec/webvttdec.c               | 11 ++---
 libavcodec/webvttenc.c               | 14 +++++-
 tests/ref/fate/api-mjpeg-codec-param |  2 +
 tests/ref/fate/api-png-codec-param   |  2 +
 26 files changed, 322 insertions(+), 234 deletions(-)

diff --git a/libavcodec/ass.c b/libavcodec/ass.c
index 56d452f..da0ee18 100644
--- a/libavcodec/ass.c
+++ b/libavcodec/ass.c
@@ -90,101 +90,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 621a7ba..314b43b 100644
--- a/libavcodec/ass.h
+++ b/libavcodec/ass.h
@@ -43,6 +43,10 @@
 #define ASS_DEFAULT_BORDERSTYLE 1
 /** @} */
 
+typedef struct FFASSDecoderContext {
+    int readorder;
+} FFASSDecoderContext;
+
 /**
  * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS.
  *
@@ -74,55 +78,23 @@ 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.
+ * Craft an ASS dialog string.
  */
-int ff_ass_add_rect(AVSubtitle *sub, const char *dialog,
-                    int ts_start, int duration, int raw);
+char *ff_ass_get_dialog(int readorder, int layer, const char *style,
+                        const char *speaker, const char *text);
 
 /**
- * Same as ff_ass_add_rect, but taking an AVBPrint buffer instead of a
- * string, and assuming raw=0.
+ * Add an ASS dialog to a subtitle.
  */
-int ff_ass_add_rect_bprint(AVSubtitle *sub, AVBPrint *buf,
-                           int ts_start, int duration);
+int ff_ass_add_rect(AVSubtitle *sub, const char *dialog,
+                    int readorder, int layer, const char *style,
+                    const char *speaker);
 
 /**
- * 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.
+ * Helper to flush a text subtitles decoder making use of the
+ * FFASSDecoderContext.
  */
-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/ass_split.c b/libavcodec/ass_split.c
index f84a686..8e26a1a 100644
--- a/libavcodec/ass_split.c
+++ b/libavcodec/ass_split.c
@@ -409,6 +409,55 @@ ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf,
     return dialog;
 }
 
+void ff_ass_free_dialog(ASSDialog **dialogp)
+{
+    ASSDialog *dialog = *dialogp;
+    if (!dialog)
+        return;
+    av_freep(&dialog->style);
+    av_freep(&dialog->name);
+    av_freep(&dialog->effect);
+    av_freep(&dialog->text);
+    av_freep(dialogp);
+}
+
+ASSDialog *ff_ass_split_dialog2(ASSSplitContext *ctx, const char *buf)
+{
+    int i;
+    static const ASSFields fields[] = {
+        {"ReadOrder", ASS_INT, offsetof(ASSDialog, readorder)},
+        {"Layer",     ASS_INT, offsetof(ASSDialog, layer)    },
+        {"Style",     ASS_STR, offsetof(ASSDialog, style)    },
+        {"Name",      ASS_STR, offsetof(ASSDialog, name)     },
+        {"MarginL",   ASS_INT, offsetof(ASSDialog, margin_l) },
+        {"MarginR",   ASS_INT, offsetof(ASSDialog, margin_r) },
+        {"MarginV",   ASS_INT, offsetof(ASSDialog, margin_v) },
+        {"Effect",    ASS_STR, offsetof(ASSDialog, effect)   },
+        {"Text",      ASS_STR, offsetof(ASSDialog, text)     },
+    };
+
+    ASSDialog *dialog = av_mallocz(sizeof(*dialog));
+    if (!dialog)
+        return NULL;
+
+    for (i = 0; i < FF_ARRAY_ELEMS(fields); i++) {
+        size_t len;
+        const int last = i == FF_ARRAY_ELEMS(fields) - 1;
+        const ASSFieldType type = fields[i].type;
+        uint8_t *ptr = (uint8_t *)dialog + fields[i].offset;
+        buf = skip_space(buf);
+        len = last ? strlen(buf) : strcspn(buf, ",");
+        if (len >= INT_MAX) {
+            ff_ass_free_dialog(dialog);
+            return NULL;
+        }
+        convert_func[type](ptr, buf, len);
+        buf += len;
+        if (*buf) buf++;
+    }
+    return dialog;
+}
+
 void ff_ass_split_free(ASSSplitContext *ctx)
 {
     if (ctx) {
diff --git a/libavcodec/ass_split.h b/libavcodec/ass_split.h
index defb5cc..abb6e58 100644
--- a/libavcodec/ass_split.h
+++ b/libavcodec/ass_split.h
@@ -69,6 +69,7 @@ typedef struct {
  * fields extracted from the [Events] section
  */
 typedef struct {
+    int   readorder;
     int   layer;    /**< higher numbered layers are drawn over lower numbered */
     int   start;    /**< start time of the dialog in centiseconds */
     int   end;      /**< end time of the dialog in centiseconds */
@@ -125,6 +126,20 @@ ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf,
                                int cache, int *number);
 
 /**
+ * Free a dialogue obtained from ff_ass_split_dialog2().
+ */
+void ff_ass_free_dialog(ASSDialog **dialogp);
+
+/**
+ * Split one ASS Dialogue line from a string buffer.
+ *
+ * @param ctx Context previously initialized by ff_ass_split().
+ * @param buf String containing the ASS "Dialogue" line.
+ * @return Pointer to the split ASSDialog. Must be freed with ff_ass_free_dialog()
+ */
+ASSDialog *ff_ass_split_dialog2(ASSSplitContext *ctx, const char *buf);
+
+/**
  * Free all the memory allocated for an ASSSplitContext.
  *
  * @param ctx Context previously initialized by ff_ass_split().
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 d849765..de463ff 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -3275,6 +3275,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 237272c..44c3b98 100644
--- a/libavcodec/ccaption_dec.c
+++ b/libavcodec/ccaption_dec.c
@@ -248,6 +248,7 @@ typedef struct CCaptionSubContext {
     char prev_cmd[2];
     /* buffer to store pkt data */
     AVBufferRef *pktbuf;
+    int readorder;
 } CCaptionSubContext;
 
 
@@ -306,6 +307,7 @@ static void flush_decoder(AVCodecContext *avctx)
     ctx->last_real_time = 0;
     ctx->screen_touched = 0;
     ctx->buffer_changed = 0;
+    ctx->readorder = 0;
     av_bprint_clear(&ctx->buffer);
 }
 
@@ -752,18 +754,16 @@ static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avp
 
         if (*ctx->buffer.str || ctx->real_time)
         {
-            int64_t sub_pts = ctx->real_time ? avpkt->pts : ctx->start_time;
-            int start_time = av_rescale_q(sub_pts, avctx->time_base, ass_tb);
-            int duration = -1;
-            if (!ctx->real_time) {
-                int end_time = av_rescale_q(ctx->end_time, avctx->time_base, ass_tb);
-                duration = end_time - start_time;
-            }
             ff_dlog(ctx, "cdp writing data (%s)\n",ctx->buffer.str);
-            ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, duration);
+            ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL);
             if (ret < 0)
                 return ret;
-            sub->pts = av_rescale_q(sub_pts, avctx->time_base, AV_TIME_BASE_Q);
+            sub->pts = av_rescale_q(ctx->start_time, avctx->time_base, AV_TIME_BASE_Q);
+            if (!ctx->real_time)
+                sub->end_display_time = av_rescale_q(ctx->end_time - ctx->start_time,
+                                                     avctx->time_base, av_make_q(1, 1000));
+            else
+                sub->end_display_time = -1;
             ctx->buffer_changed = 0;
             ctx->last_real_time = avpkt->pts;
             ctx->screen_touched = 0;
@@ -772,18 +772,16 @@ static int decode(AVCodecContext *avctx, void *data, int *got_sub, AVPacket *avp
 
     if (ctx->real_time && ctx->screen_touched &&
         avpkt->pts > ctx->last_real_time + av_rescale_q(20, ass_tb, avctx->time_base)) {
-        int start_time;
         ctx->last_real_time = avpkt->pts;
         ctx->screen_touched = 0;
 
         capture_screen(ctx);
         ctx->buffer_changed = 0;
 
-        start_time = av_rescale_q(avpkt->pts, avctx->time_base, ass_tb);
-        ret = ff_ass_add_rect_bprint(sub, &ctx->buffer, start_time, -1);
+        ret = ff_ass_add_rect(sub, ctx->buffer.str, ctx->readorder++, 0, NULL, NULL);
         if (ret < 0)
             return ret;
-        sub->pts = av_rescale_q(avpkt->pts, avctx->time_base, AV_TIME_BASE_Q);
+        sub->end_display_time = -1;
     }
 
     *got_sub = sub->num_rects > 0;
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 667cd28..8b031fa 100644
--- a/libavcodec/libzvbi-teletextdec.c
+++ b/libavcodec/libzvbi-teletextdec.c
@@ -74,6 +74,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)
@@ -95,37 +97,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 */
@@ -181,11 +167,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 {
@@ -541,6 +528,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 46d6d14..e8d2719 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;
@@ -381,4 +375,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 8d0e814..fa70497 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 {
@@ -424,7 +425,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;
@@ -454,13 +455,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;
@@ -506,7 +500,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;
@@ -521,6 +515,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"),
@@ -530,4 +530,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/movtextenc.c b/libavcodec/movtextenc.c
index 6d42d5f..fcd6613 100644
--- a/libavcodec/movtextenc.c
+++ b/libavcodec/movtextenc.c
@@ -332,16 +332,26 @@ static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf,
     s->box_flags = 0;
     s->style_entries = 0;
     for (i = 0; i < sub->num_rects; i++) {
+        const char *ass = sub->rects[i]->ass;
 
         if (sub->rects[i]->type != SUBTITLE_ASS) {
             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
             return AVERROR(ENOSYS);
         }
 
-        dialog = ff_ass_split_dialog(s->ass_ctx, sub->rects[i]->ass, 0, &num);
+        if (!strncmp(ass, "Dialogue: ", 10)) {
+            dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
+            // TODO reindent
         for (; dialog && num--; dialog++) {
             ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
         }
+        } else {
+            dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
+            if (!dialog)
+                return AVERROR(ENOMEM);
+            ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text);
+            ff_ass_free_dialog(&dialog);
+        }
 
         for (j = 0; j < box_count; j++) {
             box_types[j].encode(s, box_types[j].type);
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 aa8bfac..b493dd1 100644
--- a/libavcodec/options_table.h
+++ b/libavcodec/options_table.h
@@ -519,6 +519,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/srtenc.c b/libavcodec/srtenc.c
index 0a6875a..88fc241 100644
--- a/libavcodec/srtenc.c
+++ b/libavcodec/srtenc.c
@@ -237,18 +237,30 @@ static int encode_frame(AVCodecContext *avctx,
     av_bprint_clear(&s->buffer);
 
     for (i=0; i<sub->num_rects; i++) {
+        const char *ass = sub->rects[i]->ass;
 
         if (sub->rects[i]->type != SUBTITLE_ASS) {
             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
             return AVERROR(ENOSYS);
         }
 
-        dialog = ff_ass_split_dialog(s->ass_ctx, sub->rects[i]->ass, 0, &num);
+        if (!strncmp(ass, "Dialogue: ", 10)) {
+            dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
+            // TODO reindent
         for (; dialog && num--; dialog++) {
             s->alignment_applied = 0;
             srt_style_apply(s, dialog->style);
             ff_ass_split_override_codes(cb, s, dialog->text);
         }
+        } else {
+            dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
+            if (!dialog)
+                return AVERROR(ENOMEM);
+            s->alignment_applied = 0;
+            srt_style_apply(s, dialog->style);
+            ff_ass_split_override_codes(cb, s, dialog->text);
+            ff_ass_free_dialog(&dialog);
+        }
     }
 
     if (!av_bprint_is_complete(&s->buffer))
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 f532824..48764d4 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -2413,6 +2413,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)
@@ -2463,6 +2533,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),
 };
diff --git a/libavcodec/webvttenc.c b/libavcodec/webvttenc.c
index 9f67a2e..f85b340 100644
--- a/libavcodec/webvttenc.c
+++ b/libavcodec/webvttenc.c
@@ -164,16 +164,28 @@ static int webvtt_encode_frame(AVCodecContext *avctx,
     av_bprint_clear(&s->buffer);
 
     for (i=0; i<sub->num_rects; i++) {
+        const char *ass = sub->rects[i]->ass;
+
         if (sub->rects[i]->type != SUBTITLE_ASS) {
             av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n");
             return AVERROR(ENOSYS);
         }
 
-        dialog = ff_ass_split_dialog(s->ass_ctx, sub->rects[i]->ass, 0, &num);
+        if (!strncmp(ass, "Dialogue: ", 10)) {
+            dialog = ff_ass_split_dialog(s->ass_ctx, ass, 0, &num);
+            // TODO reindent
         for (; dialog && num--; dialog++) {
             webvtt_style_apply(s, dialog->style);
             ff_ass_split_override_codes(&webvtt_callbacks, s, dialog->text);
         }
+        } else {
+            dialog = ff_ass_split_dialog2(s->ass_ctx, ass);
+            if (!dialog)
+                return AVERROR(ENOMEM);
+            webvtt_style_apply(s, dialog->style);
+            ff_ass_split_override_codes(&webvtt_callbacks, s, dialog->text);
+            ff_ass_free_dialog(&dialog);
+        }
     }
 
     if (!av_bprint_is_complete(&s->buffer))
diff --git a/tests/ref/fate/api-mjpeg-codec-param b/tests/ref/fate/api-mjpeg-codec-param
index 6f8da74..26822fd 100644
--- a/tests/ref/fate/api-mjpeg-codec-param
+++ b/tests/ref/fate/api-mjpeg-codec-param
@@ -145,6 +145,7 @@ stream=0, decode=0
     pkt_timebase=1/25
     sub_charenc=
     sub_charenc_mode=0x00000000
+    sub_text_format=1
     refcounted_frames=false
     side_data_only_packets=true
     skip_alpha=false
@@ -300,6 +301,7 @@ stream=0, decode=1
     pkt_timebase=1/25
     sub_charenc=
     sub_charenc_mode=0x00000000
+    sub_text_format=1
     refcounted_frames=false
     side_data_only_packets=true
     skip_alpha=false
diff --git a/tests/ref/fate/api-png-codec-param b/tests/ref/fate/api-png-codec-param
index 2263774..46ac8eb 100644
--- a/tests/ref/fate/api-png-codec-param
+++ b/tests/ref/fate/api-png-codec-param
@@ -145,6 +145,7 @@ stream=0, decode=0
     pkt_timebase=1/25
     sub_charenc=
     sub_charenc_mode=0x00000000
+    sub_text_format=1
     refcounted_frames=false
     side_data_only_packets=true
     skip_alpha=false
@@ -300,6 +301,7 @@ stream=0, decode=1
     pkt_timebase=1/25
     sub_charenc=
     sub_charenc_mode=0x00000000
+    sub_text_format=1
     refcounted_frames=false
     side_data_only_packets=true
     skip_alpha=false
-- 
2.7.1



More information about the ffmpeg-devel mailing list