[FFmpeg-devel] [PATCH v2] lavfi/drawtext: Add localtime_ms for millisecond precision

Thilo Borgmann thilo.borgmann at mail.de
Thu Jan 20 14:04:43 EET 2022


Am 19.01.22 um 04:16 schrieb "zhilizhao(赵志立)":
> 
> 
>> On Jan 18, 2022, at 8:52 PM, Thilo Borgmann <thilo.borgmann at mail.de> wrote:
>>
>> Am 16.01.22 um 12:06 schrieb Nicolas George:
>>> Thilo Borgman (12022-01-14):
>>>> v6 does:
>>>>
>>>> $> ffmpeg ... drawtext="fontfile=...:text='%{localtime   \:%a %b %d %Y %S}'"           (seconds)
>>>> $> ffmpeg ... drawtext="fontfile=...:text='%{localtime_ms\:%a %b %d %Y %S}'"           (milliseconds)
>>>>
>>>> I suggest v7 should according to your remark:
>>>>
>>>> $> ffmpeg ... drawtext="fontfile=...:text='%{localtime   \:%a %b %d %Y %S}'"           (seconds)
>>>> $> ffmpeg ... drawtext="fontfile=...:text='%{localtime   \:%a %b %d %Y %S}':show_ms=1" (milliseconds)
>>>>
>>>> Good?
>>>
>>> I dislike both versions, from a user interface point of view: if there
>>> is a format string, then it stands to reason, for the user, that the
>>> resulting text is governed by the format string, not by an extra option
>>> somewhere else.
>>>
>>> There is no "use_four_digit_year=1" option, there is %Y instead of %y.
>>>
>>> There is no "use_slashes=1" option, you write %Y/%m/%d instead of
>>> %Y-%m-%d.
>>>
>>> There are no "omit_date=1" and "omit_hour=1" options, you just write
>>> what you want in the format string.
>>>
>>> My proposal goes the same way:
>>>
>>> $> ffmpeg ... drawtext="fontfile=...:text='%{localtime   \:%a %b %d %Y %S.%3N}'"
>>>
>>> It has several merits over your proposal:
>>>
>>> - It can be extended later to support printing the milliseconds at
>>>   another place than the end (for example to put the time in brackets).
>>>
>>> - It can be extended to support microseconds or centiseconds (%6N, %2N).
>>>
>>> - It is somewhat compatible with GNU date and possibly a few others.
>>>
>>> And I do not think it is harder to implement.
>>
>> Ok, did introduce a variable: %[1-6]N
>> Parsing and clipping value to valid range of 1-6.
>> Default 3.
>>
>> That way it is position independent and can show any number of decimals from 1 to 6.
>>
> 
>> diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
>> index 2a88692cbd..448b174dbb 100644
>> --- a/libavfilter/vf_drawtext.c
>> +++ b/libavfilter/vf_drawtext.c
>> @@ -51,6 +51,7 @@
>>   #include "libavutil/opt.h"
>>   #include "libavutil/random_seed.h"
>>   #include "libavutil/parseutils.h"
>> +#include "libavutil/time.h"
>>   #include "libavutil/timecode.h"
>>   #include "libavutil/time_internal.h"
>>   #include "libavutil/tree.h"
>> @@ -1045,14 +1046,82 @@ static int func_strftime(AVFilterContext *ctx, AVBPrint *bp,
>>                            char *fct, unsigned argc, char **argv, int tag)
>>   {
>>       const char *fmt = argc ? argv[0] : "%Y-%m-%d %H:%M:%S";
>> +    int64_t unow;
>>       time_t now;
>>       struct tm tm;
>> -
>> -    time(&now);
>> -    if (tag == 'L')
>> +    char *begin;
>> +    char *tmp;
>> +    int len;
>> +    char *fmt_new;
>> +    const char *fmt_tmp;
>> +    int div;
>> +
>> +    unow = av_gettime();
>> +    now  = unow / 1000000;
>> +    if (tag == 'L' || tag == 'm')
>>           localtime_r(&now, &tm);
>>       else
>>           tm = *gmtime_r(&now, &tm);
>> +
>> +    // manually parse format for %N (fractional seconds)
>> +    begin = (char*)fmt;
> 
> Make begin and tmp const char *, so the cast can be removed.
> 
>> +    while ((begin = av_stristr(begin, "%"))) {
> 
> How about strstr() since ‘%’ is caseless?
> 
>> +        tmp = begin + 1;
>> +        len = 0;
>> +        // count digits between % and possible N
>> +        while (*tmp != '\0' && av_isdigit((int)*tmp)) {
>> +            len++;
>> +            tmp++;
>> +        }
>> +        // N encountered, insert time
>> +        if (*tmp == 'N') {
>> +            int num_digits = 3; // default show millisecond [1,6]
>> +
>> +            // if digits given, parse as number in [1,6]
>> +            if (len > 0) {
>> +                av_sscanf(begin + 1, "%i", &num_digits);
>> +                num_digits = av_clip(num_digits, 1, 6); // ensure valid value
> 
> We can ignore len > 1, then the code can be simplified as
> 
> if (len == 1)
>      num_digits = av_clip(*(begin + 1) - ‘\0’, 1, 6)
> 
> 
>> +            }
>> +
>> +            len += 2; // add % and N to get length of string part
>> +
>> +            switch(num_digits) {
>> +            case 1:
>> +                fmt_tmp = "%.*s%01d%s";
>> +                div     = 100000;
>> +                break;
>> +            case 2:
>> +                fmt_tmp = "%.*s%02d%s";
>> +                div     = 10000;
>> +                break;
>> +            case 3:
>> +                fmt_tmp = "%.*s%03d%s";
>> +                div     = 1000;
>> +                break;
>> +            case 4:
>> +                fmt_tmp = "%.*s%04d%s";
>> +                div     = 100;
>> +                break;
>> +            case 5:
>> +                fmt_tmp = "%.*s%05d%s";
>> +                div     = 10;
>> +                break;
>> +            case 6:
>> +                fmt_tmp = "%.*s%06d%s";
>> +                div     = 1;
>> +                break;
>> +            }
> 
> The switch-case can be replaced by “%0*d” and pow(10, 6 - num_digits).

Indeed, simplified.


>> +
>> +            fmt_new = av_asprintf(fmt_tmp, begin - fmt, fmt, (int)(unow % 1000000) / div, begin + len);
>> +            if (!fmt_new)
>> +                return AVERROR(ENOMEM);
>> +            av_bprint_strftime(bp, fmt_new, &tm);
>> +            av_freep(&fmt_new);
>> +            return 0;
>> +        }
>> +        begin++;
> 
> Progress faster by taking account of len.

As well, also added to skip "%%".


>> +    }
>> +
>>       av_bprint_strftime(bp, fmt, &tm);
>>       return 0;
>>   }
>> -- 

v8 attached.

Thanks,
Thilo
-------------- next part --------------
From 2f42ca23b35a5e2ecedfd60203298cf7dcafdba5 Mon Sep 17 00:00:00 2001
From: Thilo Borgmann <thilo.borgmann at mail.de>
Date: Thu, 20 Jan 2022 13:02:05 +0100
Subject: [PATCH v8] lavfi/drawtext: Add %N for drawing fractions of a second

Suggested-By: ffmpeg at fb.com
---
 doc/filters.texi          |  4 +++
 libavfilter/vf_drawtext.c | 58 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/doc/filters.texi b/doc/filters.texi
index 05d4b1a56e..c3895138e0 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -11378,10 +11378,14 @@ It can be used to add padding with zeros from the left.
 @item gmtime
 The time at which the filter is running, expressed in UTC.
 It can accept an argument: a strftime() format string.
+The format string is extended to support the variable @var{%[1-6]N}
+which prints fractions of the second with optionally specified number of digits.
 
 @item localtime
 The time at which the filter is running, expressed in the local time zone.
 It can accept an argument: a strftime() format string.
+The format string is extended to support the variable @var{%[1-6]N}
+which prints fractions of the second with optionally specified number of digits.
 
 @item metadata
 Frame metadata. Takes one or two arguments.
diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c
index 2a88692cbd..06d0c77c55 100644
--- a/libavfilter/vf_drawtext.c
+++ b/libavfilter/vf_drawtext.c
@@ -51,6 +51,7 @@
 #include "libavutil/opt.h"
 #include "libavutil/random_seed.h"
 #include "libavutil/parseutils.h"
+#include "libavutil/time.h"
 #include "libavutil/timecode.h"
 #include "libavutil/time_internal.h"
 #include "libavutil/tree.h"
@@ -1045,14 +1046,65 @@ static int func_strftime(AVFilterContext *ctx, AVBPrint *bp,
                          char *fct, unsigned argc, char **argv, int tag)
 {
     const char *fmt = argc ? argv[0] : "%Y-%m-%d %H:%M:%S";
+    int64_t unow;
     time_t now;
     struct tm tm;
-
-    time(&now);
-    if (tag == 'L')
+    const char *begin;
+    const char *tmp;
+    int len;
+    char *fmt_new;
+    int div;
+
+    unow = av_gettime();
+    now  = unow / 1000000;
+    if (tag == 'L' || tag == 'm')
         localtime_r(&now, &tm);
     else
         tm = *gmtime_r(&now, &tm);
+
+    // manually parse format for %N (fractional seconds)
+    begin = fmt;
+    while ((begin = av_stristr(begin, "%"))) {
+        tmp = begin + 1;
+        len = 0;
+
+        // skip escaped "%%"
+        if (*tmp == '%') {
+            begin = tmp + 1;;
+            continue;
+        }
+
+        // count digits between % and possible N
+        while (*tmp != '\0' && av_isdigit((int)*tmp)) {
+            len++;
+            tmp++;
+        }
+        // N encountered, insert time
+        if (*tmp == 'N') {
+            int num_digits = 3; // default show millisecond [1,6]
+
+            // if digit given, expect [1,6], warn & clamp otherwise
+            if (len == 1) {
+                num_digits = av_clip(*(begin + 1) - '0', 1, 6);
+            } else if (len > 1) {
+                av_log(ctx, AV_LOG_WARNING, "Invalid number of decimals for %%N, using default of %i\n", num_digits);
+            }
+
+            len += 2; // add % and N to get length of string part
+
+            div = pow(10, 6 - num_digits);
+            fmt_new = av_asprintf("%.*s%0*d%s", (int)(begin - fmt), fmt, num_digits, (int)(unow % 1000000) / div, begin + len);
+
+            if (!fmt_new)
+                return AVERROR(ENOMEM);
+            av_bprint_strftime(bp, fmt_new, &tm);
+            av_freep(&fmt_new);
+            return 0;
+        }
+
+        begin = tmp + 1; 
+    }
+
     av_bprint_strftime(bp, fmt, &tm);
     return 0;
 }
-- 
2.20.1 (Apple Git-117)



More information about the ffmpeg-devel mailing list