[FFmpeg-devel] [PATCH v2] lavfi/drawtext: Add localtime_ms for millisecond precision
Thilo Borgmann
thilo.borgmann at mail.de
Thu Jan 20 16:58:43 EET 2022
Am 20.01.22 um 13:04 schrieb Thilo Borgmann:
> 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.
Fixed off-by-one bug.
Allows for several occurrences of %N parameter now.
v9 attached.
Thanks,
Thilo
-------------- next part --------------
From 066ca3d644daea88803b0b7ab1d3c3c66480ddfe Mon Sep 17 00:00:00 2001
From: Thilo Borgmann <thilo.borgmann at mail.de>
Date: Thu, 20 Jan 2022 15:57:14 +0100
Subject: [PATCH v9] lavfi/drawtext: Add %N for drawing fractions of a second
Suggested-By: ffmpeg at fb.com
---
doc/filters.texi | 4 +++
libavfilter/vf_drawtext.c | 66 +++++++++++++++++++++++++++++++++++++--
2 files changed, 67 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..49414a3c0d 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,15 +1046,74 @@ 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;
+ 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]
+ char *fmt_new = NULL;
+
+ // 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);
+
+ if (fmt_new && fmt_new != argv[0])
+ av_freep(&fmt_new);
+
+ 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);
+
+ begin = fmt_new + (begin - fmt);
+ fmt = fmt_new;
+ continue;
+ }
+
+ begin = tmp;
+ }
+
av_bprint_strftime(bp, fmt, &tm);
+ if (fmt && fmt != argv[0])
+ av_freep(&fmt);
+
return 0;
}
--
2.20.1 (Apple Git-117)
More information about the ffmpeg-devel
mailing list