The new expansion mechanism uses the %{...} notation.
For compatibility reasons, it must be enabled explicitly,
but a warning is printed if a conflict is likely to happen.
... | ... |
@@ -1844,8 +1844,7 @@ libfreetype library. |
1844 | 1844 |
To enable compilation of this filter you need to configure FFmpeg with |
1845 | 1845 |
@code{--enable-libfreetype}. |
1846 | 1846 |
|
1847 |
-The filter also recognizes strftime() sequences in the provided text |
|
1848 |
-and expands them accordingly. Check the documentation of strftime(). |
|
1847 |
+@subsection Syntax |
|
1849 | 1848 |
|
1850 | 1849 |
The filter accepts parameters as a list of @var{key}=@var{value} pairs, |
1851 | 1850 |
separated by ":". |
... | ... |
@@ -1875,6 +1874,12 @@ Default value is "1". |
1875 | 1875 |
|
1876 | 1876 |
See below for the list of accepted constants and functions. |
1877 | 1877 |
|
1878 |
+@item expansion |
|
1879 |
+Select how the @var{text} is expanded. Can be either @code{none}, |
|
1880 |
+@code{strftime} (default for compatibity reasons but deprecated) or |
|
1881 |
+@code{normal}. See the @ref{drawtext_expansion, Text expansion} section |
|
1882 |
+below for details. |
|
1883 |
+ |
|
1878 | 1884 |
@item fix_bounds |
1879 | 1885 |
If true, check and fix text coords to avoid clipping. |
1880 | 1886 |
|
... | ... |
@@ -2039,6 +2044,52 @@ each other, so you can for example specify @code{y=x/dar}. |
2039 | 2039 |
If libavfilter was built with @code{--enable-fontconfig}, then |
2040 | 2040 |
@option{fontfile} can be a fontconfig pattern or omitted. |
2041 | 2041 |
|
2042 |
+@anchor{drawtext_expansion} |
|
2043 |
+@subsection Text expansion |
|
2044 |
+ |
|
2045 |
+If @option{expansion} is set to @code{strftime} (which is the default for |
|
2046 |
+now), the filter recognizes strftime() sequences in the provided text and |
|
2047 |
+expands them accordingly. Check the documentation of strftime(). This |
|
2048 |
+feature is deprecated. |
|
2049 |
+ |
|
2050 |
+If @option{expansion} is set to @code{none}, the text is printed verbatim. |
|
2051 |
+ |
|
2052 |
+If @option{expansion} is set to @code{normal} (which will be the default), |
|
2053 |
+the following expansion mechanism is used. |
|
2054 |
+ |
|
2055 |
+The backslash character '\', followed by any character, always expands to |
|
2056 |
+the second character. |
|
2057 |
+ |
|
2058 |
+Sequence of the form @code{%@{...@}} are expanded. The text between the |
|
2059 |
+braces is a function name, possibly followed by arguments separated by ':'. |
|
2060 |
+If the arguments contain special characters or delimiters (':' or '@}'), |
|
2061 |
+they should be escaped. |
|
2062 |
+ |
|
2063 |
+Note that they probably must also be escaped as the value for the |
|
2064 |
+@option{text} option in the filter argument string and as the filter |
|
2065 |
+argument in the filter graph description, and possibly also for the shell, |
|
2066 |
+that makes up to four levels of escaping; using a text file avoids these |
|
2067 |
+problems. |
|
2068 |
+ |
|
2069 |
+The following functions are available: |
|
2070 |
+ |
|
2071 |
+@table @command |
|
2072 |
+ |
|
2073 |
+@item gmtime |
|
2074 |
+The time at which the filter is running, expressed in UTC. |
|
2075 |
+It can accept an argument: a strftime() format string. |
|
2076 |
+ |
|
2077 |
+@item localtime |
|
2078 |
+The time at which the filter is running, expressed in the local time zone. |
|
2079 |
+It can accept an argument: a strftime() format string. |
|
2080 |
+ |
|
2081 |
+@item pts |
|
2082 |
+The timestamp of the current frame, in seconds, with microsecond accuracy. |
|
2083 |
+ |
|
2084 |
+@end table |
|
2085 |
+ |
|
2086 |
+@subsection Examples |
|
2087 |
+ |
|
2042 | 2088 |
Some examples follow. |
2043 | 2089 |
|
2044 | 2090 |
@itemize |
... | ... |
@@ -2104,6 +2155,12 @@ Use fontconfig to set the font. Note that the colons need to be escaped. |
2104 | 2104 |
drawtext='fontfile=Linux Libertine O-40\:style=Semibold:text=FFmpeg' |
2105 | 2105 |
@end example |
2106 | 2106 |
|
2107 |
+@item |
|
2108 |
+Print the date of a real-time encoding (see strftime(3)): |
|
2109 |
+@example |
|
2110 |
+drawtext='fontfile=FreeSans.ttf:expansion=normal:text=%@{localtime:%a %b %d %Y@}' |
|
2111 |
+@end example |
|
2112 |
+ |
|
2107 | 2113 |
@end itemize |
2108 | 2114 |
|
2109 | 2115 |
For more information about libfreetype, check: |
... | ... |
@@ -30,7 +30,7 @@ |
30 | 30 |
|
31 | 31 |
#define LIBAVFILTER_VERSION_MAJOR 3 |
32 | 32 |
#define LIBAVFILTER_VERSION_MINOR 23 |
33 |
-#define LIBAVFILTER_VERSION_MICRO 100 |
|
33 |
+#define LIBAVFILTER_VERSION_MICRO 101 |
|
34 | 34 |
|
35 | 35 |
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ |
36 | 36 |
LIBAVFILTER_VERSION_MINOR, \ |
... | ... |
@@ -113,8 +113,15 @@ enum var_name { |
113 | 113 |
VAR_VARS_NB |
114 | 114 |
}; |
115 | 115 |
|
116 |
+enum expansion_mode { |
|
117 |
+ EXP_NONE, |
|
118 |
+ EXP_NORMAL, |
|
119 |
+ EXP_STRFTIME, |
|
120 |
+}; |
|
121 |
+ |
|
116 | 122 |
typedef struct { |
117 | 123 |
const AVClass *class; |
124 |
+ enum expansion_mode exp_mode; ///< expansion mode to use for the text |
|
118 | 125 |
int reinit; ///< tells if the filter is being reinited |
119 | 126 |
uint8_t *fontfile; ///< font to be used |
120 | 127 |
uint8_t *text; ///< text to be drawn |
... | ... |
@@ -181,6 +188,12 @@ static const AVOption drawtext_options[]= { |
181 | 181 |
{"tabsize", "set tab size", OFFSET(tabsize), AV_OPT_TYPE_INT, {.i64=4}, 0, INT_MAX , FLAGS}, |
182 | 182 |
{"basetime", "set base time", OFFSET(basetime), AV_OPT_TYPE_INT64, {.i64=AV_NOPTS_VALUE}, INT64_MIN, INT64_MAX , FLAGS}, |
183 | 183 |
{"draw", "if false do not draw", OFFSET(draw_expr), AV_OPT_TYPE_STRING, {.str="1"}, CHAR_MIN, CHAR_MAX, FLAGS}, |
184 |
+ |
|
185 |
+{"expansion","set the expansion mode", OFFSET(exp_mode), AV_OPT_TYPE_INT, {.i64=EXP_STRFTIME}, 0, 2, FLAGS, "expansion"}, |
|
186 |
+{"none", "set no expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NONE}, 0, 0, FLAGS, "expansion"}, |
|
187 |
+{"normal", "set normal expansion", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_NORMAL}, 0, 0, FLAGS, "expansion"}, |
|
188 |
+{"strftime", "set strftime expansion (deprecated)", OFFSET(exp_mode), AV_OPT_TYPE_CONST, {.i64=EXP_STRFTIME}, 0, 0, FLAGS, "expansion"}, |
|
189 |
+ |
|
184 | 190 |
{"timecode", "set initial timecode", OFFSET(tc_opt_string), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS}, |
185 | 191 |
{"tc24hmax", "set 24 hours max (timecode only)", OFFSET(tc24hmax), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS}, |
186 | 192 |
{"timecode_rate", "set rate (timecode only)", OFFSET(tc_rate), AV_OPT_TYPE_RATIONAL, {.dbl=0}, 0, INT_MAX, FLAGS}, |
... | ... |
@@ -484,6 +497,10 @@ static av_cold int init(AVFilterContext *ctx, const char *args) |
484 | 484 |
} |
485 | 485 |
dtext->tabsize *= glyph->advance; |
486 | 486 |
|
487 |
+ if (dtext->exp_mode == EXP_STRFTIME && |
|
488 |
+ (strchr(dtext->text, '%') || strchr(dtext->text, '\\'))) |
|
489 |
+ av_log(ctx, AV_LOG_WARNING, "expansion=strftime is deprecated.\n"); |
|
490 |
+ |
|
487 | 491 |
av_bprint_init(&dtext->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); |
488 | 492 |
|
489 | 493 |
return 0; |
... | ... |
@@ -585,6 +602,142 @@ static int command(AVFilterContext *ctx, const char *cmd, const char *arg, char |
585 | 585 |
return AVERROR(ENOSYS); |
586 | 586 |
} |
587 | 587 |
|
588 |
+static int func_pts(AVFilterContext *ctx, AVBPrint *bp, |
|
589 |
+ char *fct, unsigned argc, char **argv, int tag) |
|
590 |
+{ |
|
591 |
+ DrawTextContext *dtext = ctx->priv; |
|
592 |
+ |
|
593 |
+ av_bprintf(bp, "%.6f", dtext->var_values[VAR_T]); |
|
594 |
+ return 0; |
|
595 |
+} |
|
596 |
+ |
|
597 |
+#if !HAVE_LOCALTIME_R |
|
598 |
+static void localtime_r(const time_t *t, struct tm *tm) |
|
599 |
+{ |
|
600 |
+ *tm = *localtime(t); |
|
601 |
+} |
|
602 |
+#endif |
|
603 |
+ |
|
604 |
+static int func_strftime(AVFilterContext *ctx, AVBPrint *bp, |
|
605 |
+ char *fct, unsigned argc, char **argv, int tag) |
|
606 |
+{ |
|
607 |
+ const char *fmt = argc ? argv[0] : "%Y-%m-%d %H:%M:%S"; |
|
608 |
+ time_t now; |
|
609 |
+ struct tm tm; |
|
610 |
+ |
|
611 |
+ time(&now); |
|
612 |
+ if (tag == 'L') |
|
613 |
+ localtime_r(&now, &tm); |
|
614 |
+ else |
|
615 |
+ tm = *gmtime(&now); |
|
616 |
+ av_bprint_strftime(bp, fmt, &tm); |
|
617 |
+ return 0; |
|
618 |
+} |
|
619 |
+ |
|
620 |
+static const struct drawtext_function { |
|
621 |
+ const char *name; |
|
622 |
+ unsigned argc_min, argc_max; |
|
623 |
+ int tag; /** opaque argument to func */ |
|
624 |
+ int (*func)(AVFilterContext *, AVBPrint *, char *, unsigned, char **, int); |
|
625 |
+} functions[] = { |
|
626 |
+ { "pts", 0, 0, 0, func_pts }, |
|
627 |
+ { "gmtime", 0, 1, 'G', func_strftime }, |
|
628 |
+ { "localtime", 0, 1, 'L', func_strftime }, |
|
629 |
+}; |
|
630 |
+ |
|
631 |
+static int eval_function(AVFilterContext *ctx, AVBPrint *bp, char *fct, |
|
632 |
+ unsigned argc, char **argv) |
|
633 |
+{ |
|
634 |
+ unsigned i; |
|
635 |
+ |
|
636 |
+ for (i = 0; i < FF_ARRAY_ELEMS(functions); i++) { |
|
637 |
+ if (strcmp(fct, functions[i].name)) |
|
638 |
+ continue; |
|
639 |
+ if (argc < functions[i].argc_min) { |
|
640 |
+ av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at least %d arguments\n", |
|
641 |
+ fct, functions[i].argc_min); |
|
642 |
+ return AVERROR(EINVAL); |
|
643 |
+ } |
|
644 |
+ if (argc > functions[i].argc_max) { |
|
645 |
+ av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at most %d arguments\n", |
|
646 |
+ fct, functions[i].argc_max); |
|
647 |
+ return AVERROR(EINVAL); |
|
648 |
+ } |
|
649 |
+ break; |
|
650 |
+ } |
|
651 |
+ if (i >= FF_ARRAY_ELEMS(functions)) { |
|
652 |
+ av_log(ctx, AV_LOG_ERROR, "%%{%s} is not known\n", fct); |
|
653 |
+ return AVERROR(EINVAL); |
|
654 |
+ } |
|
655 |
+ return functions[i].func(ctx, bp, fct, argc, argv, functions[i].tag); |
|
656 |
+} |
|
657 |
+ |
|
658 |
+static int expand_function(AVFilterContext *ctx, AVBPrint *bp, char **rtext) |
|
659 |
+{ |
|
660 |
+ const char *text = *rtext; |
|
661 |
+ char *argv[16] = { NULL }; |
|
662 |
+ unsigned argc = 0, i; |
|
663 |
+ int ret; |
|
664 |
+ |
|
665 |
+ if (*text != '{') { |
|
666 |
+ av_log(ctx, AV_LOG_ERROR, "Stray %% near '%s'\n", text); |
|
667 |
+ return AVERROR(EINVAL); |
|
668 |
+ } |
|
669 |
+ text++; |
|
670 |
+ while (1) { |
|
671 |
+ if (!(argv[argc++] = av_get_token(&text, ":}"))) { |
|
672 |
+ ret = AVERROR(ENOMEM); |
|
673 |
+ goto end; |
|
674 |
+ } |
|
675 |
+ if (!*text) { |
|
676 |
+ av_log(ctx, AV_LOG_ERROR, "Unterminated %%{} near '%s'\n", *rtext); |
|
677 |
+ ret = AVERROR(EINVAL); |
|
678 |
+ goto end; |
|
679 |
+ } |
|
680 |
+ if (argc == FF_ARRAY_ELEMS(argv)) |
|
681 |
+ av_freep(&argv[--argc]); /* error will be caught later */ |
|
682 |
+ if (*text == '}') |
|
683 |
+ break; |
|
684 |
+ text++; |
|
685 |
+ } |
|
686 |
+ |
|
687 |
+ if ((ret = eval_function(ctx, bp, argv[0], argc - 1, argv + 1)) < 0) |
|
688 |
+ goto end; |
|
689 |
+ ret = 0; |
|
690 |
+ *rtext = (char *)text + 1; |
|
691 |
+ |
|
692 |
+end: |
|
693 |
+ for (i = 0; i < argc; i++) |
|
694 |
+ av_freep(&argv[i]); |
|
695 |
+ return ret; |
|
696 |
+} |
|
697 |
+ |
|
698 |
+static int expand_text(AVFilterContext *ctx) |
|
699 |
+{ |
|
700 |
+ DrawTextContext *dtext = ctx->priv; |
|
701 |
+ char *text = dtext->text; |
|
702 |
+ AVBPrint *bp = &dtext->expanded_text; |
|
703 |
+ int ret; |
|
704 |
+ |
|
705 |
+ av_bprint_clear(bp); |
|
706 |
+ while (*text) { |
|
707 |
+ if (*text == '\\' && text[1]) { |
|
708 |
+ av_bprint_chars(bp, text[1], 1); |
|
709 |
+ text += 2; |
|
710 |
+ } else if (*text == '%') { |
|
711 |
+ text++; |
|
712 |
+ if ((ret = expand_function(ctx, bp, &text)) < 0) |
|
713 |
+ return ret; |
|
714 |
+ } else { |
|
715 |
+ av_bprint_chars(bp, *text, 1); |
|
716 |
+ text++; |
|
717 |
+ } |
|
718 |
+ } |
|
719 |
+ if (!av_bprint_is_complete(bp)) |
|
720 |
+ return AVERROR(ENOMEM); |
|
721 |
+ return 0; |
|
722 |
+} |
|
723 |
+ |
|
588 | 724 |
static int draw_glyphs(DrawTextContext *dtext, AVFilterBufferRef *picref, |
589 | 725 |
int width, int height, const uint8_t rgbcolor[4], FFDrawColor *color, int x, int y) |
590 | 726 |
{ |
... | ... |
@@ -648,13 +801,19 @@ static int draw_text(AVFilterContext *ctx, AVFilterBufferRef *picref, |
648 | 648 |
if(dtext->basetime != AV_NOPTS_VALUE) |
649 | 649 |
now= picref->pts*av_q2d(ctx->inputs[0]->time_base) + dtext->basetime/1000000; |
650 | 650 |
|
651 |
-#if HAVE_LOCALTIME_R |
|
652 |
- localtime_r(&now, <ime); |
|
653 |
-#else |
|
654 |
- if(strchr(dtext->text, '%')) |
|
655 |
- ltime= *localtime(&now); |
|
656 |
-#endif |
|
657 |
- av_bprint_strftime(bp, dtext->text, <ime); |
|
651 |
+ switch (dtext->exp_mode) { |
|
652 |
+ case EXP_NONE: |
|
653 |
+ av_bprintf(bp, "%s", dtext->text); |
|
654 |
+ break; |
|
655 |
+ case EXP_NORMAL: |
|
656 |
+ if ((ret = expand_text(ctx)) < 0) |
|
657 |
+ return ret; |
|
658 |
+ break; |
|
659 |
+ case EXP_STRFTIME: |
|
660 |
+ localtime_r(&now, <ime); |
|
661 |
+ av_bprint_strftime(bp, dtext->text, <ime); |
|
662 |
+ break; |
|
663 |
+ } |
|
658 | 664 |
|
659 | 665 |
if (dtext->tc_opt_string) { |
660 | 666 |
char tcbuf[AV_TIMECODE_STR_SIZE]; |