Browse code

lavfi/drawtext: implement more generic expansion.

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.

Nicolas George authored on 2012/11/11 03:46:37
Showing 4 changed files
... ...
@@ -25,6 +25,7 @@ version <next>:
25 25
 - geq filter ported from libmpcodecs
26 26
 - remove ffserver daemon mode
27 27
 - AST demuxer
28
+- new expansion syntax for drawtext
28 29
 
29 30
 
30 31
 version 1.0:
... ...
@@ -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, &ltime);
653
-#else
654
-    if(strchr(dtext->text, '%'))
655
-        ltime= *localtime(&now);
656
-#endif
657
-    av_bprint_strftime(bp, dtext->text, &ltime);
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, &ltime);
661
+        av_bprint_strftime(bp, dtext->text, &ltime);
662
+        break;
663
+    }
658 664
 
659 665
     if (dtext->tc_opt_string) {
660 666
         char tcbuf[AV_TIMECODE_STR_SIZE];