| @@ -15,6 +15,23 @@ libavutil: 2015-08-28 | |||||
| API changes, most recent first: | API changes, most recent first: | ||||
| 2016-xx-xx - xxxxxxx - lavc 57.26.100 - avcodec.h | |||||
| Add a "sub_text_format" subtitles decoding option allowing the values "ass" | |||||
| (recommended) and "ass_with_timings" (not recommended, deprecated, default). | |||||
| The default value for this option will change to "ass" at the next major | |||||
| libavcodec version bump. | |||||
| The current default is "ass_with_timings" for compatibility. This means that | |||||
| all subtitles text decoders currently still output ASS with timings printed | |||||
| as strings in the AVSubtitles.rects[N]->ass fields. | |||||
| Setting "sub_text_format" to "ass" allows a better timing accuracy (ASS | |||||
| timing is limited to a 1/100 time base, so this is relevant for any subtitles | |||||
| format needing a bigger one), ease timing adjustments, and prevents the need | |||||
| of removing the timing from the decoded string yourself. This form is also | |||||
| known as "the Matroska form". The timing information (start time, duration) | |||||
| can be found in the AVSubtitles fields. | |||||
| 2016-xx-xx - lavc 57.25.0 - avcodec.h | 2016-xx-xx - lavc 57.25.0 - avcodec.h | ||||
| Add AVCodecContext.hw_frames_ctx. | Add AVCodecContext.hw_frames_ctx. | ||||
| @@ -90,101 +90,41 @@ int ff_ass_subtitle_header_default(AVCodecContext *avctx) | |||||
| ASS_DEFAULT_ALIGNMENT); | 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 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; | 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)); | rects = av_realloc_array(sub->rects, (sub->num_rects+1), sizeof(*sub->rects)); | ||||
| if (!rects) | if (!rects) | ||||
| goto errnomem; | |||||
| return AVERROR(ENOMEM); | |||||
| sub->rects = rects; | sub->rects = rects; | ||||
| sub->end_display_time = FFMAX(sub->end_display_time, 10 * duration); | |||||
| rects[sub->num_rects] = av_mallocz(sizeof(*rects[0])); | rects[sub->num_rects] = av_mallocz(sizeof(*rects[0])); | ||||
| if (!rects[sub->num_rects]) | if (!rects[sub->num_rects]) | ||||
| goto errnomem; | |||||
| return AVERROR(ENOMEM); | |||||
| rects[sub->num_rects]->type = SUBTITLE_ASS; | 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++; | 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, | void ff_ass_bprint_text_event(AVBPrint *buf, const char *p, int size, | ||||
| @@ -43,6 +43,10 @@ | |||||
| #define ASS_DEFAULT_BORDERSTYLE 1 | #define ASS_DEFAULT_BORDERSTYLE 1 | ||||
| /** @} */ | /** @} */ | ||||
| typedef struct FFASSDecoderContext { | |||||
| int readorder; | |||||
| } FFASSDecoderContext; | |||||
| /** | /** | ||||
| * Generate a suitable AVCodecContext.subtitle_header for SUBTITLE_ASS. | * 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); | 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. | * Escape a text subtitle using ASS syntax into an AVBPrint buffer. | ||||
| @@ -409,6 +409,55 @@ ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf, | |||||
| return dialog; | 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) | void ff_ass_split_free(ASSSplitContext *ctx) | ||||
| { | { | ||||
| if (ctx) { | if (ctx) { | ||||
| @@ -69,6 +69,7 @@ typedef struct { | |||||
| * fields extracted from the [Events] section | * fields extracted from the [Events] section | ||||
| */ | */ | ||||
| typedef struct { | typedef struct { | ||||
| int readorder; | |||||
| int layer; /**< higher numbered layers are drawn over lower numbered */ | int layer; /**< higher numbered layers are drawn over lower numbered */ | ||||
| int start; /**< start time of the dialog in centiseconds */ | int start; /**< start time of the dialog in centiseconds */ | ||||
| int end; /**< end time of the dialog in centiseconds */ | int end; /**< end time of the dialog in centiseconds */ | ||||
| @@ -124,6 +125,20 @@ ASSSplitContext *ff_ass_split(const char *buf); | |||||
| ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf, | ASSDialog *ff_ass_split_dialog(ASSSplitContext *ctx, const char *buf, | ||||
| int cache, int *number); | 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. | * Free all the memory allocated for an ASSSplitContext. | ||||
| * | * | ||||
| @@ -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, | static int ass_decode_frame(AVCodecContext *avctx, void *data, int *got_sub_ptr, | ||||
| AVPacket *avpkt) | AVPacket *avpkt) | ||||
| { | { | ||||
| int ret; | |||||
| AVSubtitle *sub = data; | 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) | if (avpkt->size <= 0) | ||||
| return avpkt->size; | 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; | return avpkt->size; | ||||
| } | } | ||||
| @@ -60,13 +60,7 @@ static int ass_encode_frame(AVCodecContext *avctx, | |||||
| return -1; | 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) { | if (i > 0) { | ||||
| av_log(avctx, AV_LOG_ERROR, "ASS encoder supports only one " | av_log(avctx, AV_LOG_ERROR, "ASS encoder supports only one " | ||||
| "ASS rectangle field.\n"); | "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); | snprintf(ass_line, sizeof(ass_line), "%d,%ld,%s", ++s->id, layer, p); | ||||
| ass_line[strcspn(ass_line, "\r\n")] = 0; | ass_line[strcspn(ass_line, "\r\n")] = 0; | ||||
| ass = ass_line; | ass = ass_line; | ||||
| } | |||||
| len = av_strlcpy(buf+total_len, ass, bufsize-total_len); | len = av_strlcpy(buf+total_len, ass, bufsize-total_len); | ||||
| @@ -3285,6 +3285,10 @@ typedef struct AVCodecContext { | |||||
| #define FF_SUB_CHARENC_MODE_AUTOMATIC 0 ///< libavcodec will select the mode itself | #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 | #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. | * Skip processing alpha if supported by codec. | ||||
| * Note that if the format uses pre-multiplied alpha (common with VP6, | * Note that if the format uses pre-multiplied alpha (common with VP6, | ||||
| @@ -248,6 +248,7 @@ typedef struct CCaptionSubContext { | |||||
| char prev_cmd[2]; | char prev_cmd[2]; | ||||
| /* buffer to store pkt data */ | /* buffer to store pkt data */ | ||||
| AVBufferRef *pktbuf; | AVBufferRef *pktbuf; | ||||
| int readorder; | |||||
| } CCaptionSubContext; | } CCaptionSubContext; | ||||
| @@ -306,6 +307,7 @@ static void flush_decoder(AVCodecContext *avctx) | |||||
| ctx->last_real_time = 0; | ctx->last_real_time = 0; | ||||
| ctx->screen_touched = 0; | ctx->screen_touched = 0; | ||||
| ctx->buffer_changed = 0; | ctx->buffer_changed = 0; | ||||
| ctx->readorder = 0; | |||||
| av_bprint_clear(&ctx->buffer); | 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) | 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); | 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) | if (ret < 0) | ||||
| return ret; | 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->buffer_changed = 0; | ||||
| ctx->last_real_time = avpkt->pts; | ctx->last_real_time = avpkt->pts; | ||||
| ctx->screen_touched = 0; | 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 && | if (ctx->real_time && ctx->screen_touched && | ||||
| avpkt->pts > ctx->last_real_time + av_rescale_q(20, ass_tb, avctx->time_base)) { | 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->last_real_time = avpkt->pts; | ||||
| ctx->screen_touched = 0; | ctx->screen_touched = 0; | ||||
| capture_screen(ctx); | capture_screen(ctx); | ||||
| ctx->buffer_changed = 0; | 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) | if (ret < 0) | ||||
| return ret; | 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; | *got_sub = sub->num_rects > 0; | ||||
| @@ -167,6 +167,7 @@ static int jacosub_decode_frame(AVCodecContext *avctx, | |||||
| int ret; | int ret; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->data; | const char *ptr = avpkt->data; | ||||
| FFASSDecoderContext *s = avctx->priv_data; | |||||
| if (avpkt->size <= 0) | if (avpkt->size <= 0) | ||||
| goto end; | goto end; | ||||
| @@ -181,7 +182,7 @@ static int jacosub_decode_frame(AVCodecContext *avctx, | |||||
| av_bprint_init(&buffer, JSS_MAX_LINESIZE, JSS_MAX_LINESIZE); | av_bprint_init(&buffer, JSS_MAX_LINESIZE, JSS_MAX_LINESIZE); | ||||
| jacosub_to_ass(avctx, &buffer, ptr); | 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); | av_bprint_finalize(&buffer, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -199,4 +200,6 @@ AVCodec ff_jacosub_decoder = { | |||||
| .id = AV_CODEC_ID_JACOSUB, | .id = AV_CODEC_ID_JACOSUB, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .decode = jacosub_decode_frame, | .decode = jacosub_decode_frame, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -74,6 +74,8 @@ typedef struct TeletextContext | |||||
| vbi_export * ex; | vbi_export * ex; | ||||
| #endif | #endif | ||||
| vbi_sliced sliced[MAX_SLICES]; | vbi_sliced sliced[MAX_SLICES]; | ||||
| int readorder; | |||||
| } TeletextContext; | } TeletextContext; | ||||
| static int chop_spaces_utf8(const unsigned char* t, int len) | 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); | 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; | 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); | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | ||||
| ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0); | ff_ass_bprint_text_event(&buf, text, strlen(text), "", 0); | ||||
| av_bprintf(&buf, "\r\n"); | |||||
| if (!av_bprint_is_complete(&buf)) { | if (!av_bprint_is_complete(&buf)) { | ||||
| av_bprint_finalize(&buf, NULL); | 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); | 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 */ | /* Draw a page as text */ | ||||
| @@ -181,11 +167,12 @@ static int gen_sub_text(TeletextContext *ctx, AVSubtitleRect *sub_rect, vbi_page | |||||
| } | } | ||||
| if (buf.len) { | if (buf.len) { | ||||
| int ret; | |||||
| sub_rect->type = SUBTITLE_ASS; | 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); | av_bprint_finalize(&buf, NULL); | ||||
| return ret; | |||||
| return AVERROR(ENOMEM); | |||||
| } | } | ||||
| av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass); | av_log(ctx, AV_LOG_DEBUG, "subtext:%s:txetbus\n", sub_rect->ass); | ||||
| } else { | } else { | ||||
| @@ -541,6 +528,7 @@ static int teletext_close_decoder(AVCodecContext *avctx) | |||||
| vbi_decoder_delete(ctx->vbi); | vbi_decoder_delete(ctx->vbi); | ||||
| ctx->vbi = NULL; | ctx->vbi = NULL; | ||||
| ctx->pts = AV_NOPTS_VALUE; | ctx->pts = AV_NOPTS_VALUE; | ||||
| ctx->readorder = 0; | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -280,6 +280,7 @@ static int microdvd_decode_frame(AVCodecContext *avctx, | |||||
| AVBPrint new_line; | AVBPrint new_line; | ||||
| char *line = avpkt->data; | char *line = avpkt->data; | ||||
| char *end = avpkt->data + avpkt->size; | char *end = avpkt->data + avpkt->size; | ||||
| FFASSDecoderContext *s = avctx->priv_data; | |||||
| struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; | struct microdvd_tag tags[sizeof(MICRODVD_TAGS) - 1] = {{0}}; | ||||
| if (avpkt->size <= 0) | if (avpkt->size <= 0) | ||||
| @@ -308,14 +309,7 @@ static int microdvd_decode_frame(AVCodecContext *avctx, | |||||
| } | } | ||||
| } | } | ||||
| if (new_line.len) { | 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); | av_bprint_finalize(&new_line, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -381,4 +375,6 @@ AVCodec ff_microdvd_decoder = { | |||||
| .id = AV_CODEC_ID_MICRODVD, | .id = AV_CODEC_ID_MICRODVD, | ||||
| .init = microdvd_init, | .init = microdvd_init, | ||||
| .decode = microdvd_decode_frame, | .decode = microdvd_decode_frame, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -99,6 +99,7 @@ typedef struct { | |||||
| uint64_t tracksize; | uint64_t tracksize; | ||||
| int size_var; | int size_var; | ||||
| int count_s, count_f; | int count_s, count_f; | ||||
| int readorder; | |||||
| } MovTextContext; | } MovTextContext; | ||||
| typedef struct { | typedef struct { | ||||
| @@ -424,7 +425,7 @@ static int mov_text_decode_frame(AVCodecContext *avctx, | |||||
| { | { | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| MovTextContext *m = avctx->priv_data; | MovTextContext *m = avctx->priv_data; | ||||
| int ret, ts_start, ts_end; | |||||
| int ret; | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| char *ptr = avpkt->data; | char *ptr = avpkt->data; | ||||
| char *end; | char *end; | ||||
| @@ -454,13 +455,6 @@ static int mov_text_decode_frame(AVCodecContext *avctx, | |||||
| end = ptr + FFMIN(2 + text_length, avpkt->size); | end = ptr + FFMIN(2 + text_length, avpkt->size); | ||||
| ptr += 2; | 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; | tsmb_size = 0; | ||||
| m->tracksize = 2 + text_length; | m->tracksize = 2 + text_length; | ||||
| m->style_entries = 0; | m->style_entries = 0; | ||||
| @@ -506,7 +500,7 @@ static int mov_text_decode_frame(AVCodecContext *avctx, | |||||
| } else | } else | ||||
| text_to_ass(&buf, ptr, end, m); | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -521,6 +515,12 @@ static int mov_text_decode_close(AVCodecContext *avctx) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| static void mov_text_flush(AVCodecContext *avctx) | |||||
| { | |||||
| MovTextContext *m = avctx->priv_data; | |||||
| m->readorder = 0; | |||||
| } | |||||
| AVCodec ff_movtext_decoder = { | AVCodec ff_movtext_decoder = { | ||||
| .name = "mov_text", | .name = "mov_text", | ||||
| .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"), | .long_name = NULL_IF_CONFIG_SMALL("3GPP Timed Text subtitle"), | ||||
| @@ -530,4 +530,5 @@ AVCodec ff_movtext_decoder = { | |||||
| .init = mov_text_init, | .init = mov_text_init, | ||||
| .decode = mov_text_decode_frame, | .decode = mov_text_decode_frame, | ||||
| .close = mov_text_decode_close, | .close = mov_text_decode_close, | ||||
| .flush = mov_text_flush, | |||||
| }; | }; | ||||
| @@ -332,16 +332,26 @@ static int mov_text_encode_frame(AVCodecContext *avctx, unsigned char *buf, | |||||
| s->box_flags = 0; | s->box_flags = 0; | ||||
| s->style_entries = 0; | s->style_entries = 0; | ||||
| for (i = 0; i < sub->num_rects; i++) { | for (i = 0; i < sub->num_rects; i++) { | ||||
| const char *ass = sub->rects[i]->ass; | |||||
| if (sub->rects[i]->type != SUBTITLE_ASS) { | if (sub->rects[i]->type != SUBTITLE_ASS) { | ||||
| av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | ||||
| return AVERROR(ENOSYS); | 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++) { | for (; dialog && num--; dialog++) { | ||||
| ff_ass_split_override_codes(&mov_text_callbacks, s, dialog->text); | 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++) { | for (j = 0; j < box_count; j++) { | ||||
| box_types[j].encode(s, box_types[j].type); | box_types[j].encode(s, box_types[j].type); | ||||
| @@ -69,13 +69,11 @@ static int mpl2_decode_frame(AVCodecContext *avctx, void *data, | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->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); | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | ||||
| if (ptr && avpkt->size > 0 && *ptr && !mpl2_event_to_ass(&buf, ptr)) | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -90,4 +88,6 @@ AVCodec ff_mpl2_decoder = { | |||||
| .id = AV_CODEC_ID_MPL2, | .id = AV_CODEC_ID_MPL2, | ||||
| .decode = mpl2_decode_frame, | .decode = mpl2_decode_frame, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -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"}, | {"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"}, | {"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"}, | {"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 }, | {"refcounted_frames", NULL, OFFSET(refcounted_frames), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, A|V|D }, | ||||
| #if FF_API_SIDEDATA_ONLY_PKT | #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 }, | {"side_data_only_packets", NULL, OFFSET(side_data_only_packets), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, A|V|E }, | ||||
| @@ -61,13 +61,12 @@ static int realtext_decode_frame(AVCodecContext *avctx, | |||||
| int ret = 0; | int ret = 0; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->data; | const char *ptr = avpkt->data; | ||||
| FFASSDecoderContext *s = avctx->priv_data; | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| av_bprint_init(&buf, 0, 4096); | 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)) | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -82,4 +81,6 @@ AVCodec ff_realtext_decoder = { | |||||
| .id = AV_CODEC_ID_REALTEXT, | .id = AV_CODEC_ID_REALTEXT, | ||||
| .decode = realtext_decode_frame, | .decode = realtext_decode_frame, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -35,6 +35,7 @@ typedef struct { | |||||
| AVBPrint encoded_source; | AVBPrint encoded_source; | ||||
| AVBPrint encoded_content; | AVBPrint encoded_content; | ||||
| AVBPrint full; | AVBPrint full; | ||||
| int readorder; | |||||
| } SAMIContext; | } SAMIContext; | ||||
| static int sami_paragraph_to_ass(AVCodecContext *avctx, const char *src) | 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; | SAMIContext *sami = avctx->priv_data; | ||||
| if (ptr && avpkt->size > 0 && !sami_paragraph_to_ass(avctx, ptr)) { | 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) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| } | } | ||||
| @@ -164,6 +163,12 @@ static av_cold int sami_close(AVCodecContext *avctx) | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| static void sami_flush(AVCodecContext *avctx) | |||||
| { | |||||
| SAMIContext *sami = avctx->priv_data; | |||||
| sami->readorder = 0; | |||||
| } | |||||
| AVCodec ff_sami_decoder = { | AVCodec ff_sami_decoder = { | ||||
| .name = "sami", | .name = "sami", | ||||
| .long_name = NULL_IF_CONFIG_SMALL("SAMI subtitle"), | .long_name = NULL_IF_CONFIG_SMALL("SAMI subtitle"), | ||||
| @@ -173,4 +178,5 @@ AVCodec ff_sami_decoder = { | |||||
| .init = sami_init, | .init = sami_init, | ||||
| .close = sami_close, | .close = sami_close, | ||||
| .decode = sami_decode_frame, | .decode = sami_decode_frame, | ||||
| .flush = sami_flush, | |||||
| }; | }; | ||||
| @@ -57,9 +57,10 @@ static int srt_decode_frame(AVCodecContext *avctx, | |||||
| { | { | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| AVBPrint buffer; | 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; | int size, ret; | ||||
| const uint8_t *p = av_packet_get_side_data(avpkt, AV_PKT_DATA_SUBTITLE_POSITION, &size); | 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) { | if (p && size == 16) { | ||||
| x1 = AV_RL32(p ); | x1 = AV_RL32(p ); | ||||
| @@ -73,16 +74,8 @@ static int srt_decode_frame(AVCodecContext *avctx, | |||||
| av_bprint_init(&buffer, 0, AV_BPRINT_SIZE_UNLIMITED); | 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); | 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); | av_bprint_finalize(&buffer, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -100,6 +93,8 @@ AVCodec ff_srt_decoder = { | |||||
| .id = AV_CODEC_ID_SUBRIP, | .id = AV_CODEC_ID_SUBRIP, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .decode = srt_decode_frame, | .decode = srt_decode_frame, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -111,5 +106,7 @@ AVCodec ff_subrip_decoder = { | |||||
| .id = AV_CODEC_ID_SUBRIP, | .id = AV_CODEC_ID_SUBRIP, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .decode = srt_decode_frame, | .decode = srt_decode_frame, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -237,18 +237,30 @@ static int encode_frame(AVCodecContext *avctx, | |||||
| av_bprint_clear(&s->buffer); | av_bprint_clear(&s->buffer); | ||||
| for (i=0; i<sub->num_rects; i++) { | for (i=0; i<sub->num_rects; i++) { | ||||
| const char *ass = sub->rects[i]->ass; | |||||
| if (sub->rects[i]->type != SUBTITLE_ASS) { | if (sub->rects[i]->type != SUBTITLE_ASS) { | ||||
| av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | ||||
| return AVERROR(ENOSYS); | 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++) { | for (; dialog && num--; dialog++) { | ||||
| s->alignment_applied = 0; | s->alignment_applied = 0; | ||||
| srt_style_apply(s, dialog->style); | srt_style_apply(s, dialog->style); | ||||
| ff_ass_split_override_codes(cb, s, dialog->text); | 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)) | if (!av_bprint_is_complete(&s->buffer)) | ||||
| @@ -52,13 +52,12 @@ static int subviewer_decode_frame(AVCodecContext *avctx, | |||||
| int ret = 0; | int ret = 0; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->data; | const char *ptr = avpkt->data; | ||||
| FFASSDecoderContext *s = avctx->priv_data; | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | 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)) | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -73,4 +72,6 @@ AVCodec ff_subviewer_decoder = { | |||||
| .id = AV_CODEC_ID_SUBVIEWER, | .id = AV_CODEC_ID_SUBVIEWER, | ||||
| .decode = subviewer_decode_frame, | .decode = subviewer_decode_frame, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -32,6 +32,7 @@ typedef struct { | |||||
| AVClass *class; | AVClass *class; | ||||
| const char *linebreaks; | const char *linebreaks; | ||||
| int keep_ass_markup; | int keep_ass_markup; | ||||
| int readorder; | |||||
| } TextContext; | } TextContext; | ||||
| #define OFFSET(x) offsetof(TextContext, x) | #define OFFSET(x) offsetof(TextContext, x) | ||||
| @@ -48,15 +49,12 @@ static int text_decode_frame(AVCodecContext *avctx, void *data, | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->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); | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | ||||
| if (ptr && avpkt->size > 0 && *ptr) { | if (ptr && avpkt->size > 0 && *ptr) { | ||||
| ff_ass_bprint_text_event(&buf, ptr, avpkt->size, text->linebreaks, text->keep_ass_markup); | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| @@ -65,6 +63,12 @@ static int text_decode_frame(AVCodecContext *avctx, void *data, | |||||
| return avpkt->size; | 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 = { \ | #define DECLARE_CLASS(decname) static const AVClass decname ## _decoder_class = { \ | ||||
| .class_name = #decname " decoder", \ | .class_name = #decname " decoder", \ | ||||
| .item_name = av_default_item_name, \ | .item_name = av_default_item_name, \ | ||||
| @@ -85,6 +89,7 @@ AVCodec ff_text_decoder = { | |||||
| .decode = text_decode_frame, | .decode = text_decode_frame, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .priv_class = &text_decoder_class, | .priv_class = &text_decoder_class, | ||||
| .flush = text_flush, | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -110,6 +115,7 @@ AVCodec ff_vplayer_decoder = { | |||||
| .decode = text_decode_frame, | .decode = text_decode_frame, | ||||
| .init = linebreak_init, | .init = linebreak_init, | ||||
| .priv_class = &vplayer_decoder_class, | .priv_class = &vplayer_decoder_class, | ||||
| .flush = text_flush, | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -126,6 +132,7 @@ AVCodec ff_stl_decoder = { | |||||
| .decode = text_decode_frame, | .decode = text_decode_frame, | ||||
| .init = linebreak_init, | .init = linebreak_init, | ||||
| .priv_class = &stl_decoder_class, | .priv_class = &stl_decoder_class, | ||||
| .flush = text_flush, | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -142,6 +149,7 @@ AVCodec ff_pjs_decoder = { | |||||
| .decode = text_decode_frame, | .decode = text_decode_frame, | ||||
| .init = linebreak_init, | .init = linebreak_init, | ||||
| .priv_class = &pjs_decoder_class, | .priv_class = &pjs_decoder_class, | ||||
| .flush = text_flush, | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -158,6 +166,7 @@ AVCodec ff_subviewer1_decoder = { | |||||
| .decode = text_decode_frame, | .decode = text_decode_frame, | ||||
| .init = linebreak_init, | .init = linebreak_init, | ||||
| .priv_class = &subviewer1_decoder_class, | .priv_class = &subviewer1_decoder_class, | ||||
| .flush = text_flush, | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -2426,6 +2426,76 @@ static int utf8_check(const uint8_t *str) | |||||
| return 1; | 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 avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub, | ||||
| int *got_sub_ptr, | int *got_sub_ptr, | ||||
| AVPacket *avpkt) | AVPacket *avpkt) | ||||
| @@ -2476,6 +2546,10 @@ int avcodec_decode_subtitle2(AVCodecContext *avctx, AVSubtitle *sub, | |||||
| av_assert1((ret >= 0) >= !!*got_sub_ptr && | av_assert1((ret >= 0) >= !!*got_sub_ptr && | ||||
| !!*got_sub_ptr >= !!sub->num_rects); | !!*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 && | if (sub->num_rects && !sub->end_display_time && avpkt->duration && | ||||
| avctx->pkt_timebase.num) { | avctx->pkt_timebase.num) { | ||||
| AVRational ms = { 1, 1000 }; | AVRational ms = { 1, 1000 }; | ||||
| @@ -28,8 +28,8 @@ | |||||
| #include "libavutil/version.h" | #include "libavutil/version.h" | ||||
| #define LIBAVCODEC_VERSION_MAJOR 57 | #define LIBAVCODEC_VERSION_MAJOR 57 | ||||
| #define LIBAVCODEC_VERSION_MINOR 25 | |||||
| #define LIBAVCODEC_VERSION_MICRO 101 | |||||
| #define LIBAVCODEC_VERSION_MINOR 26 | |||||
| #define LIBAVCODEC_VERSION_MICRO 100 | |||||
| #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ | #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ | ||||
| LIBAVCODEC_VERSION_MINOR, \ | LIBAVCODEC_VERSION_MINOR, \ | ||||
| @@ -85,15 +85,12 @@ static int webvtt_decode_frame(AVCodecContext *avctx, | |||||
| int ret = 0; | int ret = 0; | ||||
| AVSubtitle *sub = data; | AVSubtitle *sub = data; | ||||
| const char *ptr = avpkt->data; | const char *ptr = avpkt->data; | ||||
| FFASSDecoderContext *s = avctx->priv_data; | |||||
| AVBPrint buf; | AVBPrint buf; | ||||
| av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED); | 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); | av_bprint_finalize(&buf, NULL); | ||||
| if (ret < 0) | if (ret < 0) | ||||
| return ret; | return ret; | ||||
| @@ -108,4 +105,6 @@ AVCodec ff_webvtt_decoder = { | |||||
| .id = AV_CODEC_ID_WEBVTT, | .id = AV_CODEC_ID_WEBVTT, | ||||
| .decode = webvtt_decode_frame, | .decode = webvtt_decode_frame, | ||||
| .init = ff_ass_subtitle_header_default, | .init = ff_ass_subtitle_header_default, | ||||
| .flush = ff_ass_decoder_flush, | |||||
| .priv_data_size = sizeof(FFASSDecoderContext), | |||||
| }; | }; | ||||
| @@ -164,16 +164,28 @@ static int webvtt_encode_frame(AVCodecContext *avctx, | |||||
| av_bprint_clear(&s->buffer); | av_bprint_clear(&s->buffer); | ||||
| for (i=0; i<sub->num_rects; i++) { | for (i=0; i<sub->num_rects; i++) { | ||||
| const char *ass = sub->rects[i]->ass; | |||||
| if (sub->rects[i]->type != SUBTITLE_ASS) { | if (sub->rects[i]->type != SUBTITLE_ASS) { | ||||
| av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | av_log(avctx, AV_LOG_ERROR, "Only SUBTITLE_ASS type supported.\n"); | ||||
| return AVERROR(ENOSYS); | 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++) { | for (; dialog && num--; dialog++) { | ||||
| webvtt_style_apply(s, dialog->style); | webvtt_style_apply(s, dialog->style); | ||||
| ff_ass_split_override_codes(&webvtt_callbacks, s, dialog->text); | 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)) | if (!av_bprint_is_complete(&s->buffer)) | ||||
| @@ -145,6 +145,7 @@ stream=0, decode=0 | |||||
| pkt_timebase=1/25 | pkt_timebase=1/25 | ||||
| sub_charenc= | sub_charenc= | ||||
| sub_charenc_mode=0x00000000 | sub_charenc_mode=0x00000000 | ||||
| sub_text_format=1 | |||||
| refcounted_frames=false | refcounted_frames=false | ||||
| side_data_only_packets=true | side_data_only_packets=true | ||||
| skip_alpha=false | skip_alpha=false | ||||
| @@ -300,6 +301,7 @@ stream=0, decode=1 | |||||
| pkt_timebase=1/25 | pkt_timebase=1/25 | ||||
| sub_charenc= | sub_charenc= | ||||
| sub_charenc_mode=0x00000000 | sub_charenc_mode=0x00000000 | ||||
| sub_text_format=1 | |||||
| refcounted_frames=false | refcounted_frames=false | ||||
| side_data_only_packets=true | side_data_only_packets=true | ||||
| skip_alpha=false | skip_alpha=false | ||||
| @@ -145,6 +145,7 @@ stream=0, decode=0 | |||||
| pkt_timebase=1/25 | pkt_timebase=1/25 | ||||
| sub_charenc= | sub_charenc= | ||||
| sub_charenc_mode=0x00000000 | sub_charenc_mode=0x00000000 | ||||
| sub_text_format=1 | |||||
| refcounted_frames=false | refcounted_frames=false | ||||
| side_data_only_packets=true | side_data_only_packets=true | ||||
| skip_alpha=false | skip_alpha=false | ||||
| @@ -300,6 +301,7 @@ stream=0, decode=1 | |||||
| pkt_timebase=1/25 | pkt_timebase=1/25 | ||||
| sub_charenc= | sub_charenc= | ||||
| sub_charenc_mode=0x00000000 | sub_charenc_mode=0x00000000 | ||||
| sub_text_format=1 | |||||
| refcounted_frames=false | refcounted_frames=false | ||||
| side_data_only_packets=true | side_data_only_packets=true | ||||
| skip_alpha=false | skip_alpha=false | ||||