lavc: allow subtitle text format to be ASS without timing

This commit is contained in:
Clément Bœsch 2016-01-06 13:43:23 +01:00
parent 805685fffd
commit 2941282124
28 changed files with 343 additions and 238 deletions

View File

@ -15,6 +15,23 @@ libavutil: 2015-08-28
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
Add AVCodecContext.hw_frames_ctx.

View File

@ -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,

View File

@ -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.
*/
char *ff_ass_get_dialog(int readorder, int layer, const char *style,
const char *speaker, const char *text);
/**
* Add an ASS dialog to a subtitle.
*/
int ff_ass_add_rect(AVSubtitle *sub, const char *dialog,
int ts_start, int duration, int raw);
int readorder, int layer, const char *style,
const char *speaker);
/**
* Same as ff_ass_add_rect, but taking an AVBPrint buffer instead of a
* string, and assuming raw=0.
* Helper to flush a text subtitles decoder making use of the
* FFASSDecoderContext.
*/
int ff_ass_add_rect_bprint(AVSubtitle *sub, AVBPrint *buf,
int ts_start, int duration);
/**
* Add an ASS dialog line to an AVBPrint buffer.
*
* @param buf pointer to an initialized AVBPrint buffer
* @param dialog ASS dialog to add to sub
* @param ts_start start timestamp for this dialog (in 1/100 second unit)
* @param duration duration for this dialog (in 1/100 second unit), can be -1
* to last until the end of the presentation
* @param raw when set to 2, it indicates that dialog contains an ASS
* dialog line as muxed in Matroska
* when set to 1, it indicates that dialog contains a whole SSA
* dialog line which should be copied as is.
* when set to 0, it indicates that dialog contains only the Text
* part of the ASS dialog line, the rest of the line
* will be generated.
* @return number of characters read from dialog. It can be less than the whole
* length of dialog, if dialog contains several lines of text.
* A negative value indicates an error.
*/
int ff_ass_bprint_dialog(AVBPrint *buf, const char *dialog,
int ts_start, int duration, int raw);
void ff_ass_decoder_flush(AVCodecContext *avctx);
/**
* Escape a text subtitle using ASS syntax into an AVBPrint buffer.

View File

@ -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) {

View File

@ -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 */
@ -124,6 +125,20 @@ ASSSplitContext *ff_ass_split(const char *buf);
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.
*

View File

@ -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;
}

View File

@ -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);

View File

@ -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_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,

View File

@ -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;

View File

@ -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),
};

View File

@ -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;
}

View File

@ -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),
};

View File

@ -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,
};

View File

@ -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);

View File

@ -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),
};

View File

@ -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 },

View File

@ -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),
};

View File

@ -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,
};

View File

@ -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

View File

@ -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))

View File

@ -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),
};

View File

@ -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

View File

@ -2426,6 +2426,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)
@ -2476,6 +2546,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 };

View File

@ -28,8 +28,8 @@
#include "libavutil/version.h"
#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, \
LIBAVCODEC_VERSION_MINOR, \

View File

@ -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),
};

View File

@ -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))

View File

@ -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

View File

@ -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