mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-21 10:55:51 +02:00
lavc: allow subtitle text format to be ASS without timing
This commit is contained in:
parent
805685fffd
commit
2941282124
@ -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.
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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 },
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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, \
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user