From patchwork Thu Nov 30 00:49:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefano Sabatini X-Patchwork-Id: 44846 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:6a20:a301:b0:181:818d:5e7f with SMTP id x1csp31271pzk; Wed, 29 Nov 2023 16:50:15 -0800 (PST) X-Google-Smtp-Source: AGHT+IEbZwPv2VyD7SY92Wo2cV8wB63DYy9DscVZB7D4daTqgZY0VQTYZdsc0JnIfEwkCO3g7DWR X-Received: by 2002:a2e:c4a:0:b0:2c9:b633:ba09 with SMTP id o10-20020a2e0c4a000000b002c9b633ba09mr4466286ljd.35.1701305415256; Wed, 29 Nov 2023 16:50:15 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1701305415; cv=none; d=google.com; s=arc-20160816; b=aBVEbcnj5GuI2MdCmV4gBanL7fZWKhIQI9+CcYVXf6XEcH5mRM5EEWIjj8VRkGrvlm u/XR46MgtJetp636Z7sdCO5NVQ+YkSUO0g2gtExE/vy0KJZAW8b2ySiofdR2LDi39eu7 el6H13L98ifoQKcSOXgwMCgSw4qfr/aOYeJx7KubORMCvuwPINrDPjyM9c321k69kgCI xLbm3w0KXZwhK53jUZnV8qWYZ7XHfnZH3nmf4A99wu4eEsC/W7ScE09S+7D4clJ4VRyG 6fZHNOeoeMXJyDJS5PmXc8kOpizZim8fR+GhtBOm2uHQdwxuT7itnzqZFxYUw7DJFD0L WqhQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=WHKHfNIH8/dwKlI49grLl7U1Ea8Fgh9DSN/xsqtLAO0=; fh=QdWxt2OToL83TTnLQn0lGhLakV7i1QyAJdC8te7qN0E=; b=geja2WR1fWqPQRdsm2+YUXZQMquhnnmESML9RFkYCs1wBNRSCZJUiLxifeBrl7yLIL dWzp7BE4yEwg6iVY0nBjytpgu2qlZn0FMn7TcCA7ILqUmpfTyI0AH7GNxctgpj86dM/m sV9bqj6Rcfkhhj4oAzvEdqym9Ouw0mcQBZhdLtb/TcDhrMjXmHszpli6O1rvk8wSs/Yn wqunzoaB+rmF1GIXkvDGoUzdVmCYjIkhgk9WAww39vNVnvJG1c4XuRrnNebiCEwCpye7 yBXmZq9m9wUwv1bzDCdBmaLDiHRyprRax7NjmnFN1+zI13tKqmgjdX82HDjqzPnCVxcW UHcA== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=kDHsXIU1; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id u13-20020a170906068d00b009e6a34746easi61286ejb.252.2023.11.29.16.49.52; Wed, 29 Nov 2023 16:50:15 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20230601 header.b=kDHsXIU1; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 33D7C68D05F; Thu, 30 Nov 2023 02:49:41 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-lf1-f48.google.com (mail-lf1-f48.google.com [209.85.167.48]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id E686F68D046 for ; Thu, 30 Nov 2023 02:49:33 +0200 (EET) Received: by mail-lf1-f48.google.com with SMTP id 2adb3069b0e04-50bce40bc4aso46406e87.2 for ; Wed, 29 Nov 2023 16:49:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1701305372; x=1701910172; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=282+PS+e+3gmfCVD2aeBlLgqXTq1D1fxhKXLsR7/CtM=; b=kDHsXIU1C1rgMECRmKuStKc9tRagk6JypGY/k6L08mDWPLNHcCfhzvLiSxC1LFs1cZ SmG9tSRhBDMi42+2HWlKO1cSCOhgFIiGtG8DZd0ZX27x/AN/HhBimCx2ZebHLc1WEybN rAoIxANPuY5BgoGJMmE0JL5WTXoKzL4IlxdYCAmc6vYw8QB2PMF3oCkEgL8+pXmeG249 4Hs8DJIXDwdezeCFrkg2gYz+gOF/xHjn0gJcJwVf/GlIOral5oXoafQVCOBJG+QC65sG QphHxkTCpxb/bNpi2+nkoQnWNNsnhQehC1nCzCKIRYxDNoEabpmk4ifFCX2TUGZ4POfM J+UQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1701305372; x=1701910172; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=282+PS+e+3gmfCVD2aeBlLgqXTq1D1fxhKXLsR7/CtM=; b=gYGvtu7BtN3Exg5KDH0P16jYMuteUl0+vqV5DSWpDHPBexNpjAvDQUwj0uuPGsAnLJ USSfsOFMxgPf9CEJt+g5W43fVOnPTF347DtsDv4CUnyPtQZJLDFV5bokUiOUGOp5EMwX 3CzwxQUZbuXAAoz8vOqG4FF08uIolbtmazRoo3QzTmtLUahlaVqZhecMK307LU78FSzG TE9r/xi+MPOCfrag129xahXWdH3xhwZmSXBJUaFEyV2pMQpB8jHtXGfW/bBBaUmM7ILK G9lAMTzs6bKMkBRofwFNoCvp7zupwosrm4Lsowj7UZ0PuzOnWEPEx3FAiTypAx5Vz5Tt cA6Q== X-Gm-Message-State: AOJu0YwvgoLTXRxcE9R+zYvfhAIqk8T1jrDs+DOLX8quwF4AWnqtzruC 18JauZVEOu9TN0CnNbFrY9Tg4UCNuWtvyhrP X-Received: by 2002:a05:6512:e88:b0:509:46fe:b002 with SMTP id bi8-20020a0565120e8800b0050946feb002mr12638434lfb.5.1701305371450; Wed, 29 Nov 2023 16:49:31 -0800 (PST) Received: from mariano (dynamic-adsl-84-220-189-10.clienti.tiscali.it. [84.220.189.10]) by smtp.gmail.com with ESMTPSA id l9-20020a056402344900b0054b766d237esm6030edc.17.2023.11.29.16.49.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 29 Nov 2023 16:49:30 -0800 (PST) Received: by mariano (Postfix, from userid 1000) id 0AB22BFCDC; Thu, 30 Nov 2023 01:49:29 +0100 (CET) From: Stefano Sabatini To: FFmpeg development discussions and patches Date: Thu, 30 Nov 2023 01:49:13 +0100 Message-Id: <20231130004914.329717-2-stefasab@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20231130004914.329717-1-stefasab@gmail.com> References: <20231130004914.329717-1-stefasab@gmail.com> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 1/2] lavfi: introduce textutils X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Stefano Sabatini Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: JeD9MW4cQkRk Generalize drawtext utilities to make them usable in other filters. This will be needed to introduce the QR code source and filter without duplicating functionality. --- libavfilter/Makefile | 2 +- libavfilter/textutils.c | 379 +++++++++++++++++++++++++++ libavfilter/textutils.h | 182 +++++++++++++ libavfilter/vf_drawtext.c | 533 ++++++++++---------------------------- 4 files changed, 693 insertions(+), 403 deletions(-) create mode 100644 libavfilter/textutils.c create mode 100644 libavfilter/textutils.h diff --git a/libavfilter/Makefile b/libavfilter/Makefile index de51c2a403..e49be354bb 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -290,7 +290,7 @@ OBJS-$(CONFIG_DOUBLEWEAVE_FILTER) += vf_weave.o OBJS-$(CONFIG_DRAWBOX_FILTER) += vf_drawbox.o OBJS-$(CONFIG_DRAWGRAPH_FILTER) += f_drawgraph.o OBJS-$(CONFIG_DRAWGRID_FILTER) += vf_drawbox.o -OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o +OBJS-$(CONFIG_DRAWTEXT_FILTER) += vf_drawtext.o textutils.o OBJS-$(CONFIG_EDGEDETECT_FILTER) += vf_edgedetect.o edge_common.o OBJS-$(CONFIG_ELBG_FILTER) += vf_elbg.o OBJS-$(CONFIG_ENTROPY_FILTER) += vf_entropy.o diff --git a/libavfilter/textutils.c b/libavfilter/textutils.c new file mode 100644 index 0000000000..4598861161 --- /dev/null +++ b/libavfilter/textutils.c @@ -0,0 +1,379 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * text expansion utilities + */ + +#include +#include +#include + +#include "textutils.h" +#include "libavutil/avutil.h" +#include "libavutil/error.h" +#include "libavutil/file.h" +#include "libavutil/time.h" + +static int ff_expand_text_function_internal(TextExpander *text_expander, AVBPrint *bp, + char *name, unsigned argc, char **argv) +{ + void *ctx = text_expander->ctx; + TextExpanderFunction *functions = text_expander->functions; + unsigned i; + + for (i = 0; i < text_expander->functions_nb; i++) { + if (strcmp(name, functions[i].name)) + continue; + if (argc < functions[i].argc_min) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at least %d arguments\n", + name, functions[i].argc_min); + return AVERROR(EINVAL); + } + if (argc > functions[i].argc_max) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at most %d arguments\n", + name, functions[i].argc_max); + return AVERROR(EINVAL); + } + break; + } + if (i >= text_expander->functions_nb) { + av_log(ctx, AV_LOG_ERROR, "%%{%s} is not known\n", name); + return AVERROR(EINVAL); + } + + return functions[i].func(ctx, bp, name, argc, argv); +} + +/** + * Expand text template pointed to by *rtext. + * + * Expand text template defined in text using the logic defined in a text + * expander object. + * + * This function expects the text to be in the format %{FUNCTION_NAME[:PARAMS]}, + * where PARAMS is a sequence of strings separated by : and represents the function + * arguments to use for the function evaluation. + * + * @param text_expander TextExpander object used to expand the text + * @param bp BPrint object where the expanded text is written to + * @param rtext pointer to pointer to the text to expand, it is updated to point + * to the next part of the template to process + * @return negative value corresponding to an AVERROR error code in case of + * errors, a non-negative value otherwise + */ +static int ff_expand_text_function(TextExpander *text_expander, AVBPrint *bp, char **rtext) +{ + void *ctx = text_expander->ctx; + const char *text = *rtext; + char *argv[16] = { NULL }; + unsigned argc = 0, i; + int ret; + + if (*text != '{') { + av_log(ctx, AV_LOG_ERROR, "Stray %% near '%s'\n", text); + return AVERROR(EINVAL); + } + text++; + while (1) { + if (!(argv[argc++] = av_get_token(&text, ":}"))) { + ret = AVERROR(ENOMEM); + goto end; + } + if (!*text) { + av_log(ctx, AV_LOG_ERROR, "Unterminated %%{} near '%s'\n", *rtext); + ret = AVERROR(EINVAL); + goto end; + } + if (argc == FF_ARRAY_ELEMS(argv)) + av_freep(&argv[--argc]); /* error will be caught later */ + if (*text == '}') + break; + text++; + } + + if ((ret = ff_expand_text_function_internal(text_expander, bp, argv[0], argc - 1, argv + 1)) < 0) + goto end; + ret = 0; + *rtext = (char *)text + 1; + +end: + for (i = 0; i < argc; i++) + av_freep(&argv[i]); + return ret; +} + +int ff_expand_text(TextExpander *text_expander, char *text, AVBPrint *bp) +{ + int ret; + + av_bprint_clear(bp); + while (*text) { + if (*text == '\\' && text[1]) { + av_bprint_chars(bp, text[1], 1); + text += 2; + } else if (*text == '%') { + text++; + if ((ret = ff_expand_text_function(text_expander, bp, &text)) < 0) + return ret; + } else { + av_bprint_chars(bp, *text, 1); + text++; + } + } + if (!av_bprint_is_complete(bp)) + return AVERROR(ENOMEM); + return 0; +} + +int ff_print_pts(void *log_ctx, AVBPrint *bp, double pts, const char *delta, + const char *fmt, const char *strftime_fmt) +{ + int ret; + + if (delta) { + int64_t delta_i; + if ((ret = av_parse_time(&delta_i, delta, 1)) < 0) { + av_log(log_ctx, AV_LOG_ERROR, "Invalid delta '%s'\n", delta); + return ret; + } + pts += (double)delta_i / AV_TIME_BASE; + } + + if (!strcmp(fmt, "flt")) { + av_bprintf(bp, "%.6f", pts); + } else if (!strcmp(fmt, "hms") || + !strcmp(fmt, "hms24hh")) { + if (isnan(pts)) { + av_bprintf(bp, " ??:??:??.???"); + } else { + int64_t ms = llrint(pts * 1000); + char sign = ' '; + if (ms < 0) { + sign = '-'; + ms = -ms; + } + if (!strcmp(fmt, "hms24hh")) { + /* wrap around 24 hours */ + ms %= 24 * 60 * 60 * 1000; + } + av_bprintf(bp, "%c%02d:%02d:%02d.%03d", sign, + (int)(ms / (60 * 60 * 1000)), + (int)(ms / (60 * 1000)) % 60, + (int)(ms / 1000) % 60, + (int)(ms % 1000)); + } + } else if (!strcmp(fmt, "localtime") || + !strcmp(fmt, "gmtime")) { + struct tm tm; + time_t ms = (time_t)pts; + if (!strcmp(fmt, "localtime")) + localtime_r(&ms, &tm); + else + gmtime_r(&ms, &tm); + av_bprint_strftime(bp, av_x_if_null(strftime_fmt, "%Y-%m-%d %H:%M:%S"), &tm); + } else { + av_log(log_ctx, AV_LOG_ERROR, "Invalid format '%s'\n", fmt); + return AVERROR(EINVAL); + } + return 0; +} + +int ff_print_time(void *log_ctx, AVBPrint *bp, + const char *strftime_fmt, char localtime) +{ + const char *fmt = av_x_if_null(strftime_fmt, "%Y-%m-%d %H:%M:%S"); + const char *fmt_begin = fmt; + int64_t unow; + time_t now; + struct tm tm; + const char *begin; + const char *tmp; + int len; + int div; + AVBPrint fmt_bp; + + av_bprint_init(&fmt_bp, 0, AV_BPRINT_SIZE_UNLIMITED); + + unow = av_gettime(); + now = unow / 1000000; + if (localtime) + localtime_r(&now, &tm); + else + tm = *gmtime_r(&now, &tm); + + // manually parse format for %N (fractional seconds) + begin = fmt; + while ((begin = strchr(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(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); + + av_bprintf(&fmt_bp, "%.*s%0*d", (int)(begin - fmt_begin), fmt_begin, num_digits, (int)(unow % 1000000) / div); + + begin += len; + fmt_begin = begin; + + continue; + } + + begin = tmp; + } + + av_bprintf(&fmt_bp, "%s", fmt_begin); + if (!av_bprint_is_complete(&fmt_bp)) { + av_log(log_ctx, AV_LOG_WARNING, "Format string truncated at %u/%u.", fmt_bp.size, fmt_bp.len); + } + + av_bprint_strftime(bp, fmt_bp.str, &tm); + + av_bprint_finalize(&fmt_bp, NULL); + + return 0; +} + +int ff_print_eval_expr(void *log_ctx, AVBPrint *bp, + const char *expr, + const char * const *fun_names, const ff_eval_func2 *fun_values, + const char * const *var_names, const double *var_values, + void *eval_ctx) +{ + double res; + int ret; + + ret = av_expr_parse_and_eval(&res, expr, var_names, var_values, + NULL, NULL, fun_names, fun_values, + eval_ctx, 0, log_ctx); + if (ret < 0) + av_log(log_ctx, AV_LOG_ERROR, + "Text expansion expression '%s' is not valid\n", + expr); + else + av_bprintf(bp, "%f", res); + + return ret; +} + +int ff_print_eval_expr_int_format(void *log_ctx, AVBPrint *bp, + const char *expr, + const char * const *fun_names, const ff_eval_func2 *fun_values, + const char * const *var_names, const double *var_values, + void *eval_ctx, + const char format, int positions) +{ + double res; + int intval; + int ret; + char fmt_str[30] = "%"; + + ret = av_expr_parse_and_eval(&res, expr, var_names, var_values, + NULL, NULL, fun_names, fun_values, + eval_ctx, 0, log_ctx); + if (ret < 0) { + av_log(log_ctx, AV_LOG_ERROR, + "Text expansion expression '%s' is not valid\n", + expr); + return ret; + } + + if (!strchr("xXdu", format)) { + av_log(log_ctx, AV_LOG_ERROR, "Invalid format '%c' specified," + " allowed values: 'x', 'X', 'd', 'u'\n", format); + return AVERROR(EINVAL); + } + + feclearexcept(FE_ALL_EXCEPT); + intval = res; +#if defined(FE_INVALID) && defined(FE_OVERFLOW) && defined(FE_UNDERFLOW) + if ((ret = fetestexcept(FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW))) { + av_log(log_ctx, AV_LOG_ERROR, "Conversion of floating-point result to int failed. Control register: 0x%08x. Conversion result: %d\n", ret, intval); + return AVERROR(EINVAL); + } +#endif + + if (positions >= 0) + av_strlcatf(fmt_str, sizeof(fmt_str), "0%u", positions); + av_strlcatf(fmt_str, sizeof(fmt_str), "%c", format); + + av_log(log_ctx, AV_LOG_DEBUG, "Formatting value %f (expr '%s') with spec '%s'\n", + res, expr, fmt_str); + + av_bprintf(bp, fmt_str, intval); + + return 0; +} + + +int ff_load_textfile(void *log_ctx, const char *textfile, + unsigned char **text, size_t *text_size) +{ + int err; + uint8_t *textbuf; + uint8_t *tmp; + size_t textbuf_size; + + if ((err = av_file_map(textfile, &textbuf, &textbuf_size, 0, log_ctx)) < 0) { + av_log(log_ctx, AV_LOG_ERROR, + "The text file '%s' could not be read or is empty\n", + textfile); + return err; + } + + if (textbuf_size > 0 && ff_is_newline(textbuf[textbuf_size - 1])) + textbuf_size--; + if (textbuf_size > SIZE_MAX - 1 || !(tmp = av_realloc(*text, textbuf_size + 1))) { + av_file_unmap(textbuf, textbuf_size); + return AVERROR(ENOMEM); + } + *text = tmp; + memcpy(*text, textbuf, textbuf_size); + (*text)[textbuf_size] = 0; + if (text_size) + *text_size = textbuf_size; + av_file_unmap(textbuf, textbuf_size); + + return 0; +} + diff --git a/libavfilter/textutils.h b/libavfilter/textutils.h new file mode 100644 index 0000000000..8dd8255eec --- /dev/null +++ b/libavfilter/textutils.h @@ -0,0 +1,182 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * text utilities + */ + +#ifndef AVFILTER_TEXTUTILS_H +#define AVFILTER_TEXTUTILS_H + +#include "libavutil/bprint.h" +#include "libavutil/eval.h" +#include "libavutil/log.h" +#include "libavutil/parseutils.h" + +/** + * Function used to expand a template sequence in the format + * %{FUNCTION_NAME[:PARAMS]}, defined in the TextExpander object. + */ +typedef struct TextExpanderFunction { + /** + * name of the function + */ + const char *name; + + /** + * minimum and maximum number of arguments accepted by the + * function in the PARAMS + */ + unsigned argc_min, argc_max; + + /** + * actual function used to perform the expansion + */ + int (*func)(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **args); +} TextExpanderFunction; + +/** + * Text expander object, used to encapsulate the logic to expand a + * given text template. + * + * A backslash character @samp{\} in a text template, followed by any + * character, always expands to the second character. + * Sequences of the form %{FUNCTION_NAME[:PARAMS]} are expanded using a + * function defined in the object. The text between the braces is a + * function name, possibly followed by arguments separated by ':'. If + * the arguments contain special characters or delimiters (':' or + * '}'), they should be escaped. + */ +typedef struct TextExpander { + /** + * context to pass to the function, used for logging and for accessing + * the context for the function + */ + void *ctx; + + /** + * list of functions to use to expand sequences in the format + * FUNCTION_NAME{PARAMS} + */ + TextExpanderFunction *functions; + + /** + * number of functions + */ + unsigned int functions_nb; +} TextExpander; + +/** + * Expand text template. + * + * Expand text template defined in text using the logic defined in a text + * expander object. + * + * @param text_expander TextExpander object used to expand the text + * @param text template text to expand + * @param bp BPrint object where the expanded text is written to + * @return negative value corresponding to an AVERROR error code in case of + * errors, a non-negative value otherwise + */ +int ff_expand_text(TextExpander *text_expander, char *text, AVBPrint *bp); + +/** + * Print PTS representation to an AVBPrint object. + * + * @param log_ctx pointer to av_log object + * @param bp AVBPrint object where the PTS textual representation is written to + * @param pts PTS value expressed as a double to represent + * @param delta delta time parsed by av_parse_time(), added to the PTS + * @param fmt string representing the format to use for printing, can be + * "flt" - use a float representation with 6 decimal digits, + * "hms" - use HH:MM:SS.MMM format, + * "hms24hh" - same as "hms" but wraps the hours in 24hh format + * (so that it is expressed in the range 00-23), + * "localtime" or "gmtime" - expand the PTS according to the + * @code{strftime()} function rules, using either the corresponding + * @code{localtime()} or @code{gmtime()} time + * @param strftime_fmt: @code{strftime()} format to use to represent the PTS in + * case the format "localtime" or "gmtime" was selected, if not specified + * defaults to "%Y-%m-%d %H:%M:%S" + * @return negative value corresponding to an AVERROR error code in case of + * errors, a non-negative value otherwise + */ +int ff_print_pts(void *log_ctx, AVBPrint *bp, double pts, const char *delta, + const char *fmt, const char *strftime_fmt); + +/** + * Print time representation to an AVBPrint object. + * + * @param log_ctx pointer to av_log object + + * @param bp AVBPrint object where the time textual representation is written to + * @param strftime_fmt: strftime() format to use to represent the time in case + * if not specified defaults to "%Y-%m-%d %H:%M:%S". The format string is + * extended to support the %[1-6]N after %S which prints fractions of the + * second with optionally specified number of digits, if not specified + * defaults to 3. + * @param localtime use local time to compute the time if non-zero, otherwise + * use UTC + * @return negative value corresponding to an AVERROR error code in case of + * errors, a non-negative value otherwise + */ +int ff_print_time(void *log_ctx, AVBPrint *bp, const char *strftime_fmt, char localtime); + +typedef double (*ff_eval_func2)(void *, double a, double b); + +int ff_print_eval_expr(void *log_ctx, AVBPrint *bp, + const char *expr, + const char * const *fun_names, const ff_eval_func2 *fun_values, + const char * const *var_names, const double *var_values, + void *eval_ctx); + +int ff_print_eval_expr_int_format(void *log_ctx, AVBPrint *bp, + const char *expr, + const char * const *fun_names, const ff_eval_func2 *fun_values, + const char * const *var_names, const double *var_values, + void *eval_ctx, + const char format, int positions); + +/** + * Check if the character is a newline. + * + * @param c character to check + * @return non-negative value in case c is a newline, 0 otherwise + */ +static inline int ff_is_newline(uint32_t c) +{ + return c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +/** + * Load text file into the buffer pointed by text. + * + * @param log_ctx pointer to av_log object + * @param textfile filename containing the text to load + * @param text pointer to the text buffer where the loaded text will be + * loaded + * @param text_size pointer to the value to set with the loaded text data, + * including the terminating 0 character + * @return negative value corresponding to an AVERROR error code in case of + * errors, a non-negative value otherwise + */ +int ff_load_textfile(void *log_ctx, const char *textfile, + unsigned char **text, size_t *text_size); + +#endif /* AVFILTER_TEXTUTILS__H */ diff --git a/libavfilter/vf_drawtext.c b/libavfilter/vf_drawtext.c index c5477cbff1..bc387c1a36 100644 --- a/libavfilter/vf_drawtext.c +++ b/libavfilter/vf_drawtext.c @@ -47,7 +47,6 @@ #include "libavutil/avstring.h" #include "libavutil/bprint.h" #include "libavutil/common.h" -#include "libavutil/file.h" #include "libavutil/eval.h" #include "libavutil/opt.h" #include "libavutil/random_seed.h" @@ -62,6 +61,7 @@ #include "drawutils.h" #include "formats.h" #include "internal.h" +#include "textutils.h" #include "video.h" #if CONFIG_LIBFRIBIDI @@ -253,6 +253,7 @@ typedef struct TextMetrics { typedef struct DrawTextContext { const AVClass *class; int exp_mode; ///< expansion mode to use for the text + TextExpander text_expander; ///< text expander in case exp_mode == NORMAL int reinit; ///< tells if the filter is being reinited #if CONFIG_LIBFONTCONFIG uint8_t *font; ///< font to be used @@ -631,40 +632,6 @@ static int load_font(AVFilterContext *ctx) return err; } -static inline int is_newline(uint32_t c) -{ - return c == '\n' || c == '\r' || c == '\f' || c == '\v'; -} - -static int load_textfile(AVFilterContext *ctx) -{ - DrawTextContext *s = ctx->priv; - int err; - uint8_t *textbuf; - uint8_t *tmp; - size_t textbuf_size; - - if ((err = av_file_map(s->textfile, &textbuf, &textbuf_size, 0, ctx)) < 0) { - av_log(ctx, AV_LOG_ERROR, - "The text file '%s' could not be read or is empty\n", - s->textfile); - return err; - } - - if (textbuf_size > 0 && is_newline(textbuf[textbuf_size - 1])) - textbuf_size--; - if (textbuf_size > SIZE_MAX - 1 || !(tmp = av_realloc(s->text, textbuf_size + 1))) { - av_file_unmap(textbuf, textbuf_size); - return AVERROR(ENOMEM); - } - s->text = tmp; - memcpy(s->text, textbuf, textbuf_size); - s->text[textbuf_size] = 0; - av_file_unmap(textbuf, textbuf_size); - - return 0; -} - #if CONFIG_LIBFRIBIDI static int shape_text(AVFilterContext *ctx) { @@ -885,6 +852,123 @@ static int string_to_array(const char *source, int *result, int result_size) return counter; } +static int func_pict_type(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + + av_bprintf(bp, "%c", av_get_picture_type_char(s->var_values[VAR_PICT_TYPE])); + return 0; +} + +static int func_pts(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + const char *fmt; + const char *strftime_fmt = NULL; + const char *delta = NULL; + double pts = s->var_values[VAR_T]; + + // argv: pts, FMT, [DELTA, 24HH | strftime_fmt] + + fmt = argc >= 1 ? argv[0] : "flt"; + if (argc >= 2) { + delta = argv[1]; + } + if (argc >= 3) { + if (!strcmp(fmt, "hms")) { + if (!strcmp(argv[2], "24HH")) { + av_log(ctx, AV_LOG_WARNING, "pts third argument 24HH is deprected, use pts:hms24hh instead\n"); + fmt = "hms24"; + } else { + av_log(ctx, AV_LOG_ERROR, "Invalid argument '%s', '24HH' was expected\n", argv[2]); + return AVERROR(EINVAL); + } + } else { + strftime_fmt = argv[2]; + } + } + + return ff_print_pts(ctx, bp, pts, delta, fmt, strftime_fmt); +} + +static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + + av_bprintf(bp, "%d", (int)s->var_values[VAR_N]); + return 0; +} + +static int func_metadata(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + AVDictionaryEntry *e = av_dict_get(s->metadata, argv[0], NULL, 0); + + if (e && e->value) + av_bprintf(bp, "%s", e->value); + else if (argc >= 2) + av_bprintf(bp, "%s", argv[1]); + return 0; +} + +static int func_strftime(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + const char *strftime_fmt = argc ? argv[0] : NULL; + + return ff_print_time(ctx, bp, strftime_fmt, !strcmp(function_name, "localtime")); +} + +static int func_eval_expr(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + + return ff_print_eval_expr(ctx, bp, argv[0], + fun2_names, fun2, + var_names, s->var_values, &s->prng); +} + +static int func_eval_expr_int_format(void *ctx, AVBPrint *bp, const char *function_name, unsigned argc, char **argv) +{ + DrawTextContext *s = ((AVFilterContext *)ctx)->priv; + int ret; + int positions = -1; + + /* + * argv[0] expression to be converted to `int` + * argv[1] format: 'x', 'X', 'd' or 'u' + * argv[2] positions printed (optional) + */ + + if (argc == 3) { + ret = sscanf(argv[2], "%u", &positions); + if (ret != 1) { + av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions" + " to print: '%s'\n", argv[2]); + return AVERROR(EINVAL); + } + } + + return ff_print_eval_expr_int_format(ctx, bp, argv[0], + fun2_names, fun2, + var_names, s->var_values, + &s->prng, + argv[1][0], positions); +} + +static TextExpanderFunction text_expander_functions[] = { + { "e", 1, 1, func_eval_expr }, + { "eif", 2, 3, func_eval_expr_int_format }, + { "expr", 1, 1, func_eval_expr }, + { "expr_int_format", 2, 3, func_eval_expr_int_format }, + { "frame_num", 0, 0, func_frame_num }, + { "gmtime", 0, 1, func_strftime }, + { "localtime", 0, 1, func_strftime }, + { "metadata", 1, 2, func_metadata }, + { "n", 0, 0, func_frame_num }, + { "pict_type", 0, 0, func_pict_type }, + { "pts", 0, 3, func_pts } +}; + static av_cold int init(AVFilterContext *ctx) { int err; @@ -907,7 +991,7 @@ static av_cold int init(AVFilterContext *ctx) "Both text and text file provided. Please provide only one\n"); return AVERROR(EINVAL); } - if ((err = load_textfile(ctx)) < 0) + if ((err = ff_load_textfile(ctx, (const char *)s->textfile, &s->text, NULL)) < 0) return err; } @@ -950,6 +1034,12 @@ static av_cold int init(AVFilterContext *ctx) return AVERROR(EINVAL); } + s->text_expander = (TextExpander) { + .ctx = ctx, + .functions = text_expander_functions, + .functions_nb = FF_ARRAY_ELEMS(text_expander_functions) + }; + #if CONFIG_LIBFRIBIDI if (s->text_shaping) if ((err = shape_text(ctx)) < 0) @@ -1160,367 +1250,6 @@ fail: return ret; } -static int func_pict_type(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - - av_bprintf(bp, "%c", av_get_picture_type_char(s->var_values[VAR_PICT_TYPE])); - return 0; -} - -static int func_pts(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - const char *fmt; - double pts = s->var_values[VAR_T]; - int ret; - - fmt = argc >= 1 ? argv[0] : "flt"; - if (argc >= 2) { - int64_t delta; - if ((ret = av_parse_time(&delta, argv[1], 1)) < 0) { - av_log(ctx, AV_LOG_ERROR, "Invalid delta '%s'\n", argv[1]); - return ret; - } - pts += (double)delta / AV_TIME_BASE; - } - if (!strcmp(fmt, "flt")) { - av_bprintf(bp, "%.6f", pts); - } else if (!strcmp(fmt, "hms")) { - if (isnan(pts)) { - av_bprintf(bp, " ??:??:??.???"); - } else { - int64_t ms = llrint(pts * 1000); - char sign = ' '; - if (ms < 0) { - sign = '-'; - ms = -ms; - } - if (argc >= 3) { - if (!strcmp(argv[2], "24HH")) { - ms %= 24 * 60 * 60 * 1000; - } else { - av_log(ctx, AV_LOG_ERROR, "Invalid argument '%s'\n", argv[2]); - return AVERROR(EINVAL); - } - } - av_bprintf(bp, "%c%02d:%02d:%02d.%03d", sign, - (int)(ms / (60 * 60 * 1000)), - (int)(ms / (60 * 1000)) % 60, - (int)(ms / 1000) % 60, - (int)(ms % 1000)); - } - } else if (!strcmp(fmt, "localtime") || - !strcmp(fmt, "gmtime")) { - struct tm tm; - time_t ms = (time_t)pts; - const char *timefmt = argc >= 3 ? argv[2] : "%Y-%m-%d %H:%M:%S"; - if (!strcmp(fmt, "localtime")) - localtime_r(&ms, &tm); - else - gmtime_r(&ms, &tm); - av_bprint_strftime(bp, timefmt, &tm); - } else { - av_log(ctx, AV_LOG_ERROR, "Invalid format '%s'\n", fmt); - return AVERROR(EINVAL); - } - return 0; -} - -static int func_frame_num(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - - av_bprintf(bp, "%d", (int)s->var_values[VAR_N]); - return 0; -} - -static int func_metadata(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - AVDictionaryEntry *e = av_dict_get(s->metadata, argv[0], NULL, 0); - - if (e && e->value) - av_bprintf(bp, "%s", e->value); - else if (argc >= 2) - av_bprintf(bp, "%s", argv[1]); - return 0; -} - -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"; - const char *fmt_begin = fmt; - int64_t unow; - time_t now; - struct tm tm; - const char *begin; - const char *tmp; - int len; - int div; - AVBPrint fmt_bp; - - av_bprint_init(&fmt_bp, 0, AV_BPRINT_SIZE_UNLIMITED); - - 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 = strchr(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); - - av_bprintf(&fmt_bp, "%.*s%0*d", (int)(begin - fmt_begin), fmt_begin, num_digits, (int)(unow % 1000000) / div); - - begin += len; - fmt_begin = begin; - - continue; - } - - begin = tmp; - } - - av_bprintf(&fmt_bp, "%s", fmt_begin); - if (!av_bprint_is_complete(&fmt_bp)) { - av_log(ctx, AV_LOG_WARNING, "Format string truncated at %u/%u.", fmt_bp.size, fmt_bp.len); - } - - av_bprint_strftime(bp, fmt_bp.str, &tm); - - av_bprint_finalize(&fmt_bp, NULL); - - return 0; -} - -static int func_eval_expr(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - double res; - int ret; - - ret = av_expr_parse_and_eval(&res, argv[0], var_names, s->var_values, - NULL, NULL, fun2_names, fun2, - &s->prng, 0, ctx); - if (ret < 0) - av_log(ctx, AV_LOG_ERROR, - "Expression '%s' for the expr text expansion function is not valid\n", - argv[0]); - else - av_bprintf(bp, "%f", res); - - return ret; -} - -static int func_eval_expr_int_format(AVFilterContext *ctx, AVBPrint *bp, - char *fct, unsigned argc, char **argv, int tag) -{ - DrawTextContext *s = ctx->priv; - double res; - int intval; - int ret; - unsigned int positions = 0; - char fmt_str[30] = "%"; - - /* - * argv[0] expression to be converted to `int` - * argv[1] format: 'x', 'X', 'd' or 'u' - * argv[2] positions printed (optional) - */ - - ret = av_expr_parse_and_eval(&res, argv[0], var_names, s->var_values, - NULL, NULL, fun2_names, fun2, - &s->prng, 0, ctx); - if (ret < 0) { - av_log(ctx, AV_LOG_ERROR, - "Expression '%s' for the expr text expansion function is not valid\n", - argv[0]); - return ret; - } - - if (!strchr("xXdu", argv[1][0])) { - av_log(ctx, AV_LOG_ERROR, "Invalid format '%c' specified," - " allowed values: 'x', 'X', 'd', 'u'\n", argv[1][0]); - return AVERROR(EINVAL); - } - - if (argc == 3) { - ret = sscanf(argv[2], "%u", &positions); - if (ret != 1) { - av_log(ctx, AV_LOG_ERROR, "expr_int_format(): Invalid number of positions" - " to print: '%s'\n", argv[2]); - return AVERROR(EINVAL); - } - } - - feclearexcept(FE_ALL_EXCEPT); - intval = res; -#if defined(FE_INVALID) && defined(FE_OVERFLOW) && defined(FE_UNDERFLOW) - if ((ret = fetestexcept(FE_INVALID|FE_OVERFLOW|FE_UNDERFLOW))) { - av_log(ctx, AV_LOG_ERROR, "Conversion of floating-point result to int failed. Control register: 0x%08x. Conversion result: %d\n", ret, intval); - return AVERROR(EINVAL); - } -#endif - - if (argc == 3) - av_strlcatf(fmt_str, sizeof(fmt_str), "0%u", positions); - av_strlcatf(fmt_str, sizeof(fmt_str), "%c", argv[1][0]); - - av_log(ctx, AV_LOG_DEBUG, "Formatting value %f (expr '%s') with spec '%s'\n", - res, argv[0], fmt_str); - - av_bprintf(bp, fmt_str, intval); - - return 0; -} - -static const struct drawtext_function { - const char *name; - unsigned argc_min, argc_max; - int tag; /**< opaque argument to func */ - int (*func)(AVFilterContext *, AVBPrint *, char *, unsigned, char **, int); -} functions[] = { - { "expr", 1, 1, 0, func_eval_expr }, - { "e", 1, 1, 0, func_eval_expr }, - { "expr_int_format", 2, 3, 0, func_eval_expr_int_format }, - { "eif", 2, 3, 0, func_eval_expr_int_format }, - { "pict_type", 0, 0, 0, func_pict_type }, - { "pts", 0, 3, 0, func_pts }, - { "gmtime", 0, 1, 'G', func_strftime }, - { "localtime", 0, 1, 'L', func_strftime }, - { "frame_num", 0, 0, 0, func_frame_num }, - { "n", 0, 0, 0, func_frame_num }, - { "metadata", 1, 2, 0, func_metadata }, -}; - -static int eval_function(AVFilterContext *ctx, AVBPrint *bp, char *fct, - unsigned argc, char **argv) -{ - unsigned i; - - for (i = 0; i < FF_ARRAY_ELEMS(functions); i++) { - if (strcmp(fct, functions[i].name)) - continue; - if (argc < functions[i].argc_min) { - av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at least %d arguments\n", - fct, functions[i].argc_min); - return AVERROR(EINVAL); - } - if (argc > functions[i].argc_max) { - av_log(ctx, AV_LOG_ERROR, "%%{%s} requires at most %d arguments\n", - fct, functions[i].argc_max); - return AVERROR(EINVAL); - } - break; - } - if (i >= FF_ARRAY_ELEMS(functions)) { - av_log(ctx, AV_LOG_ERROR, "%%{%s} is not known\n", fct); - return AVERROR(EINVAL); - } - return functions[i].func(ctx, bp, fct, argc, argv, functions[i].tag); -} - -static int expand_function(AVFilterContext *ctx, AVBPrint *bp, char **rtext) -{ - const char *text = *rtext; - char *argv[16] = { NULL }; - unsigned argc = 0, i; - int ret; - - if (*text != '{') { - av_log(ctx, AV_LOG_ERROR, "Stray %% near '%s'\n", text); - return AVERROR(EINVAL); - } - text++; - while (1) { - if (!(argv[argc++] = av_get_token(&text, ":}"))) { - ret = AVERROR(ENOMEM); - goto end; - } - if (!*text) { - av_log(ctx, AV_LOG_ERROR, "Unterminated %%{} near '%s'\n", *rtext); - ret = AVERROR(EINVAL); - goto end; - } - if (argc == FF_ARRAY_ELEMS(argv)) - av_freep(&argv[--argc]); /* error will be caught later */ - if (*text == '}') - break; - text++; - } - - if ((ret = eval_function(ctx, bp, argv[0], argc - 1, argv + 1)) < 0) - goto end; - ret = 0; - *rtext = (char *)text + 1; - -end: - for (i = 0; i < argc; i++) - av_freep(&argv[i]); - return ret; -} - -static int expand_text(AVFilterContext *ctx, char *text, AVBPrint *bp) -{ - int ret; - - av_bprint_clear(bp); - while (*text) { - if (*text == '\\' && text[1]) { - av_bprint_chars(bp, text[1], 1); - text += 2; - } else if (*text == '%') { - text++; - if ((ret = expand_function(ctx, bp, &text)) < 0) - return ret; - } else { - av_bprint_chars(bp, *text, 1); - text++; - } - } - if (!av_bprint_is_complete(bp)) - return AVERROR(ENOMEM); - return 0; -} - static void update_color_with_alpha(DrawTextContext *s, FFDrawColor *color, const FFDrawColor incolor) { *color = incolor; @@ -1688,7 +1417,7 @@ static int measure_text(AVFilterContext *ctx, TextMetrics *metrics) for (i = 0, p = text; 1; i++) { GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed;); continue_on_failed: - if (is_newline(code) || code == 0) { + if (ff_is_newline(code) || code == 0) { ++line_count; if (code == 0) { break; @@ -1729,7 +1458,7 @@ continue_on_failed: } GET_UTF8(code, *p ? *p++ : 0, code = 0xfffd; goto continue_on_failed2;); continue_on_failed2: - if (is_newline(code) || code == 0) { + if (ff_is_newline(code) || code == 0) { TextLine *cur_line = &s->lines[line_count]; HarfbuzzData *hb = &cur_line->hb_data; cur_line->cluster_offset = line_offset; @@ -1861,7 +1590,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame) av_bprintf(bp, "%s", s->text); break; case EXP_NORMAL: - if ((ret = expand_text(ctx, s->text, &s->expanded_text)) < 0) + if ((ret = ff_expand_text(&s->text_expander, s->text, &s->expanded_text)) < 0) return ret; break; case EXP_STRFTIME: @@ -1883,7 +1612,7 @@ static int draw_text(AVFilterContext *ctx, AVFrame *frame) if (s->fontcolor_expr[0]) { /* If expression is set, evaluate and replace the static value */ av_bprint_clear(&s->expanded_fontcolor); - if ((ret = expand_text(ctx, s->fontcolor_expr, &s->expanded_fontcolor)) < 0) + if ((ret = ff_expand_text(&s->text_expander, s->fontcolor_expr, &s->expanded_fontcolor)) < 0) return ret; if (!av_bprint_is_complete(&s->expanded_fontcolor)) return AVERROR(ENOMEM); @@ -2125,7 +1854,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *frame) } if (s->reload && !(inlink->frame_count_out % s->reload)) { - if ((ret = load_textfile(ctx)) < 0) { + if ((ret = ff_load_textfile(ctx, (const char *)s->textfile, &s->text, NULL)) < 0) { av_frame_free(&frame); return ret; }