@item tabsize
The size in number of spaces to use for rendering the tab.
Default value is 4.
+
+ @item fix_bounds
+ If true, check and fix text coords to avoid clipping.
@end table
-For example the command:
+The parameters for @var{x} and @var{y} are expressions containing the
+following constants:
+
+@table @option
+@item W, H
+the input width and height
+
+@item tw, text_w
+the width of the rendered text
+
+@item th, text_h
+the height of the rendered text
+
+@item lh, line_h
+the height of each text line
+
+@item sar
+input sample aspect ratio
+
+@item dar
+input display aspect ratio, it is the same as (@var{w} / @var{h}) * @var{sar}
+
+@item hsub, vsub
+horizontal and vertical chroma subsample values. For example for the
+pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1.
+
+@item max_glyph_w
+maximum glyph width, that is the maximum width for all the glyphs
+contained in the rendered text
+
+@item max_glyph_h
+maximum glyph height, that is the maximum height for all the glyphs
+contained in the rendered text, it is equivalent to @var{ascent} -
+@var{descent}.
+
+@item max_glyph_a, ascent
+
+the maximum distance from the baseline to the highest/upper grid
+coordinate used to place a glyph outline point, for all the rendered
+glyphs.
+It is a positive value, due to the grid's orientation with the Y axis
+upwards.
+
+@item max_glyph_d, descent
+the maximum distance from the baseline to the lowest grid coordinate
+used to place a glyph outline point, for all the rendered glyphs.
+This is a negative value, due to the grid's orientation, with the Y axis
+upwards.
+
+@item n
+the number of input frame, starting from 0
+
+@item t
+timestamp expressed in seconds, NAN if the input timestamp is unknown
+
+@item timecode
+initial timecode representation in "hh:mm:ss[:;.]ff" format. It can be used
+with or without text parameter. @var{rate} option must be specified.
+
+@item r, rate
+frame rate (timecode only)
+@end table
+
+Some examples follow.
+
+@itemize
+
+@item
+Draw "Test Text" with font FreeSerif, using the default values for the
+optional parameters.
+
@example
drawtext="fontfile=/usr/share/fonts/truetype/freefont/FreeSerif.ttf: text='Test Text'"
@end example
{"shadowx", "set x", OFFSET(shadowx), AV_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX },
{"shadowy", "set y", OFFSET(shadowy), AV_OPT_TYPE_INT, {.dbl=0}, INT_MIN, INT_MAX },
{"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.dbl=4}, 0, INT_MAX },
+{"basetime", "set base time", OFFSET(basetime), AV_OPT_TYPE_INT64, {.dbl=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX },
{"draw", "if false do not draw", OFFSET(d_expr), AV_OPT_TYPE_STRING, {.str="1"}, CHAR_MIN, CHAR_MAX },
+{"timecode", "set initial timecode", OFFSET(tc_opt_string), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX },
+{"r", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX },
+{"rate", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX },
+ {"fix_bounds", "if true, check and fix text coords to avoid clipping",
+ OFFSET(fix_bounds), AV_OPT_TYPE_INT, {.dbl=1}, 0, 1 },
/* FT_LOAD_* flags */
{"ft_load_flags", "set font loading flags for libfreetype", OFFSET(ft_load_flags), AV_OPT_TYPE_FLAGS, {.dbl=FT_LOAD_DEFAULT|FT_LOAD_RENDER}, 0, INT_MAX, 0, "ft_load_flags" },
int width, int height)
{
DrawTextContext *dtext = ctx->priv;
- int ret;
+ uint32_t code = 0, prev_code = 0;
+ int x = 0, y = 0, i = 0, ret;
+ int max_text_line_w = 0, len;
+ int box_w, box_h;
+ char *text = dtext->text;
+ uint8_t *p;
+ int y_min = 32000, y_max = -32000;
+ int x_min = 32000, x_max = -32000;
+ FT_Vector delta;
+ Glyph *glyph = NULL, *prev_glyph = NULL;
+ Glyph dummy = { 0 };
- /* draw box */
- if (dtext->draw_box)
- drawbox(picref, dtext->x, dtext->y, dtext->w, dtext->h,
- dtext->box_line, dtext->pixel_step, dtext->boxcolor,
- dtext->hsub, dtext->vsub, dtext->is_packed_rgb,
- dtext->rgba_map);
+ time_t now = time(0);
+ struct tm ltime;
+ uint8_t *buf = dtext->expanded_text;
+ int buf_size = dtext->expanded_text_size;
- if (dtext->shadowx || dtext->shadowy) {
- if ((ret = draw_glyphs(dtext, picref, width, height,
- dtext->shadowcolor_rgba,
- dtext->shadowcolor,
- dtext->x + dtext->shadowx,
- dtext->y + dtext->shadowy)) < 0)
- return ret;
+ if(dtext->basetime != AV_NOPTS_VALUE)
+ now= picref->pts*av_q2d(ctx->inputs[0]->time_base) + dtext->basetime/1000000;
+
+ if (!buf) {
+ buf_size = 2*strlen(dtext->text)+1;
+ buf = av_malloc(buf_size);
}
- if ((ret = draw_glyphs(dtext, picref, width, height,
- dtext->fontcolor_rgba,
- dtext->fontcolor,
- dtext->x,
- dtext->y)) < 0)
- return ret;
+#if HAVE_LOCALTIME_R
+ localtime_r(&now, <ime);
+#else
+ if(strchr(dtext->text, '%'))
+ ltime= *localtime(&now);
+#endif
- return 0;
-}
+ do {
+ *buf = 1;
+ if (strftime(buf, buf_size, dtext->text, <ime) != 0 || *buf == 0)
+ break;
+ buf_size *= 2;
+ } while ((buf = av_realloc(buf, buf_size)));
-static void null_draw_slice(AVFilterLink *link, int y, int h, int slice_dir) { }
+ if (dtext->tc_opt_string) {
+ char tcbuf[AV_TIMECODE_STR_SIZE];
+ av_timecode_make_string(&dtext->tc, tcbuf, dtext->frame_id++);
+ buf = av_asprintf("%s%s", dtext->text, tcbuf);
+ }
-static inline int normalize_double(int *n, double d)
-{
- int ret = 0;
+ if (!buf)
+ return AVERROR(ENOMEM);
+ text = dtext->expanded_text = buf;
+ dtext->expanded_text_size = buf_size;
+ if ((len = strlen(text)) > dtext->nb_positions) {
+ if (!(dtext->positions =
+ av_realloc(dtext->positions, len*sizeof(*dtext->positions))))
+ return AVERROR(ENOMEM);
+ dtext->nb_positions = len;
+ }
- if (isnan(d)) {
- ret = AVERROR(EINVAL);
- } else if (d > INT_MAX || d < INT_MIN) {
- *n = d > INT_MAX ? INT_MAX : INT_MIN;
- ret = AVERROR(EINVAL);
- } else
- *n = round(d);
+ x = 0;
+ y = 0;
- return ret;
-}
+ /* load and cache glyphs */
+ for (i = 0, p = text; *p; i++) {
+ GET_UTF8(code, *p++, continue;);
-static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *inpicref)
-{
- AVFilterContext *ctx = inlink->dst;
- DrawTextContext *dtext = ctx->priv;
- int fail = 0;
+ /* get glyph */
+ dummy.code = code;
+ glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL);
- if (!glyph)
++ if (!glyph) {
+ load_glyph(ctx, &glyph, code);
++ }
- if (dtext_prepare_text(ctx) < 0) {
- av_log(ctx, AV_LOG_ERROR, "Can't draw text\n");
- fail = 1;
+ y_min = FFMIN(glyph->bbox.yMin, y_min);
+ y_max = FFMAX(glyph->bbox.yMax, y_max);
+ x_min = FFMIN(glyph->bbox.xMin, x_min);
+ x_max = FFMAX(glyph->bbox.xMax, x_max);
}
+ dtext->max_glyph_h = y_max - y_min;
+ dtext->max_glyph_w = x_max - x_min;
+
+ /* compute and save position for each glyph */
+ glyph = NULL;
+ for (i = 0, p = text; *p; i++) {
+ GET_UTF8(code, *p++, continue;);
- dtext->var_values[VAR_T] = inpicref->pts == AV_NOPTS_VALUE ?
- NAN : inpicref->pts * av_q2d(inlink->time_base);
- dtext->var_values[VAR_X] =
- av_expr_eval(dtext->x_pexpr, dtext->var_values, &dtext->prng);
- dtext->var_values[VAR_Y] =
- av_expr_eval(dtext->y_pexpr, dtext->var_values, &dtext->prng);
- dtext->var_values[VAR_X] =
- av_expr_eval(dtext->x_pexpr, dtext->var_values, &dtext->prng);
-
- dtext->draw = fail ? 0 :
- av_expr_eval(dtext->d_pexpr, dtext->var_values, &dtext->prng);
-
- normalize_double(&dtext->x, dtext->var_values[VAR_X]);
- normalize_double(&dtext->y, dtext->var_values[VAR_Y]);
-
- if (dtext->fix_bounds) {
- if (dtext->x < 0) dtext->x = 0;
- if (dtext->y < 0) dtext->y = 0;
- if ((unsigned)dtext->x + (unsigned)dtext->w > inlink->w)
- dtext->x = inlink->w - dtext->w;
- if ((unsigned)dtext->y + (unsigned)dtext->h > inlink->h)
- dtext->y = inlink->h - dtext->h;
+ /* skip the \n in the sequence \r\n */
+ if (prev_code == '\r' && code == '\n')
+ continue;
+
+ prev_code = code;
+ if (is_newline(code)) {
+ max_text_line_w = FFMAX(max_text_line_w, x);
+ y += dtext->max_glyph_h;
+ x = 0;
+ continue;
+ }
+
+ /* get glyph */
+ prev_glyph = glyph;
+ dummy.code = code;
+ glyph = av_tree_find(dtext->glyphs, &dummy, glyph_cmp, NULL);
+
+ /* kerning */
+ if (dtext->use_kerning && prev_glyph && glyph->code) {
+ FT_Get_Kerning(dtext->face, prev_glyph->code, glyph->code,
+ ft_kerning_default, &delta);
+ x += delta.x >> 6;
+ }
+
+ /* save position */
+ dtext->positions[i].x = x + glyph->bitmap_left;
+ dtext->positions[i].y = y - glyph->bitmap_top + y_max;
+ if (code == '\t') x = (x / dtext->tabsize + 1)*dtext->tabsize;
+ else x += glyph->advance;
}
+ max_text_line_w = FFMAX(x, max_text_line_w);
+
+ dtext->var_values[VAR_TW] = dtext->var_values[VAR_TEXT_W] = max_text_line_w;
+ dtext->var_values[VAR_TH] = dtext->var_values[VAR_TEXT_H] = y + dtext->max_glyph_h;
+
+ dtext->var_values[VAR_MAX_GLYPH_W] = dtext->max_glyph_w;
+ dtext->var_values[VAR_MAX_GLYPH_H] = dtext->max_glyph_h;
+ dtext->var_values[VAR_MAX_GLYPH_A] = dtext->var_values[VAR_ASCENT ] = y_max;
+ dtext->var_values[VAR_MAX_GLYPH_D] = dtext->var_values[VAR_DESCENT] = y_min;
+
+ dtext->var_values[VAR_LINE_H] = dtext->var_values[VAR_LH] = dtext->max_glyph_h;
+
+ dtext->x = dtext->var_values[VAR_X] = av_expr_eval(dtext->x_pexpr, dtext->var_values, &dtext->prng);
+ dtext->y = dtext->var_values[VAR_Y] = av_expr_eval(dtext->y_pexpr, dtext->var_values, &dtext->prng);
+ dtext->x = dtext->var_values[VAR_X] = av_expr_eval(dtext->x_pexpr, dtext->var_values, &dtext->prng);
+ dtext->draw = av_expr_eval(dtext->d_pexpr, dtext->var_values, &dtext->prng);
+
+ if(!dtext->draw)
+ return 0;
+
dtext->x &= ~((1 << dtext->hsub) - 1);
dtext->y &= ~((1 << dtext->vsub) - 1);