From 899302bb5f6ac0484fedc2865ee3beca021eba85 Mon Sep 17 00:00:00 2001 From: Stefano Sabatini Date: Tue, 28 Nov 2023 23:58:15 +0100 Subject: [PATCH] lavfi: add qrencode source and filter --- Changelog | 1 + configure | 7 + doc/filters.texi | 427 ++++++++++++++++++++ libavfilter/Makefile | 2 + libavfilter/allfilters.c | 2 + libavfilter/qrencode.c | 820 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 1259 insertions(+) create mode 100644 libavfilter/qrencode.c diff --git a/Changelog b/Changelog index a638c03250..b483bb9c69 100644 --- a/Changelog +++ b/Changelog @@ -13,6 +13,7 @@ version : - IAMF raw demuxer and muxer - D3D12VA hardware accelerated H264, HEVC, VP9, AV1, MPEG-2 and VC1 decoding - tiltandshift filter +- qrencode filter and qrencodesrc source version 6.1: - libaribcaption decoder diff --git a/configure b/configure index 037c08afff..b1853204ef 100755 --- a/configure +++ b/configure @@ -256,6 +256,7 @@ External library support: --enable-libopus enable Opus de/encoding via libopus [no] --enable-libplacebo enable libplacebo library [no] --enable-libpulse enable Pulseaudio input via libpulse [no] + --enable-libqrencode enable QR encode generation via libqrencode [no] --enable-librabbitmq enable RabbitMQ library [no] --enable-librav1e enable AV1 encoding via rav1e [no] --enable-librist enable RIST via librist [no] @@ -1881,6 +1882,7 @@ EXTERNAL_LIBRARY_LIST=" libopus libplacebo libpulse + libqrencode librabbitmq librav1e librist @@ -3789,6 +3791,8 @@ nnedi_filter_deps="gpl" ocr_filter_deps="libtesseract" ocv_filter_deps="libopencv" openclsrc_filter_deps="opencl" +qrencode_filter_deps="libqrencode" +qrencodesrc_filter_deps="libqrencode" overlay_opencl_filter_deps="opencl" overlay_qsv_filter_deps="libmfx" overlay_qsv_filter_select="qsvvpp" @@ -6840,6 +6844,7 @@ enabled libopus && { } enabled libplacebo && require_pkg_config libplacebo "libplacebo >= 4.192.0" libplacebo/vulkan.h pl_vulkan_create enabled libpulse && require_pkg_config libpulse libpulse pulse/pulseaudio.h pa_context_new +enabled libqrencode && require_pkg_config libqrencode libqrencode qrencode.h QRcode_encodeString enabled librabbitmq && require_pkg_config librabbitmq "librabbitmq >= 0.7.1" amqp.h amqp_new_connection enabled librav1e && require_pkg_config librav1e "rav1e >= 0.5.0" rav1e.h rav1e_context_new enabled librist && require_pkg_config librist "librist >= 0.2.7" librist/librist.h rist_receiver_create @@ -7668,6 +7673,8 @@ enabled mcdeint_filter && prepend avfilter_deps "avcodec" enabled movie_filter && prepend avfilter_deps "avformat avcodec" enabled pan_filter && prepend avfilter_deps "swresample" enabled pp_filter && prepend avfilter_deps "postproc" +enabled qrencode_filter && prepend_avfilter_deps "swscale" +enabled qrencodesrc_filter && prepend_avfilter_deps "swscale" enabled removelogo_filter && prepend avfilter_deps "avformat avcodec swscale" enabled sab_filter && prepend avfilter_deps "swscale" enabled scale_filter && prepend avfilter_deps "swscale" diff --git a/doc/filters.texi b/doc/filters.texi index a63a635ec8..9b120ce0a1 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -20096,6 +20096,302 @@ qp=2+2*sin(PI*qp) @end example @end itemize +@section qrencode +Generate a QR code using the libqrencode library (see +@url{https://fukuchi.org/works/qrencode/}), and overlay it on top of the current +frame. + +To enable the compilation of this filter, you need to configure FFmpeg with with +@code{--enable-libqrencode}. + +The QR code is generated from the provided text or text pattern. The +corresponding QR code is scaled and overlayed into the video output according to +the specified options. + +In case no text is specified, no QR code is overlaied. + +This filter accepts the following options: + +@table @option + +@item qrcode_width, q +@item padded_qrcode_width, Q +Specify an expression for the width of the rendered QR code, with and without +padding. The @var{qrcode_width} expression can reference the value set by the +@var{padded_qrcode_width} expression, and vice versa. +By default @var{padded_qrcode_width} is set to @var{qrcode_width}, meaning that +there is no padding. + +These expressions are evaluated for each new frame. + +See the @ref{qrencode_expressions,,qrencode Expressions} section for details. + +@item x +@item y +Specify an expression for positioning the padded QR code top-left corner. The +@var{x} expression can reference the value set by the @var{y} expression, and +vice. + +By default @var{x} and @var{y} are set set to @var{0}, meaning that the QR code +is placed in the top left corner of the input. + +These expressions are evaluated for each new frame. + +See the @ref{qrencode_expressions,,qrencode Expressions} section for details. + +@item case_sensitive, cs +Instruct libqrencode to use case sensitive encoding. This is enabled by +default. This can be disabled to reduce the QR encoding size. + +@item level, l +Specify the QR encoding error correction level. With an higher correction level, +the encoding size will increase but the code will be more robust to corruption. +Lower level is @var{L}. + +It accepts the following values: +@table @samp +@item L +@item M +@item Q +@item H +@end table + +@item expansion +Select how the input text is expanded. Can be either @code{none}, or +@code{normal} (default). See the @ref{qrencode_text_expansion,,qrencode Text expansion} +section below for details. + +@item text +@item textfile +Define the text to be rendered. In case neither is specified, no QR is encoded +(just an empty colored frame). + +In case expansion is enabled, the text is treated as a text template, using the +qrencode expansion mechanism. See the @ref{qrencode_text_expansion,,qrencode +Text expansion} section below for details. + +@item background_color, bc +@item foreground_color, fc +Set the QR code and background color. The default value of +@var{foreground_color} is "black", the default value of @var{background_color} +is "white". + +For the syntax of the color options, check the @ref{color syntax,,"Color" +section in the ffmpeg-utils manual,ffmpeg-utils}. +@end table + +@anchor{qrencode_expressions} +@subsection qrencode Expressions + +The expressions set by the options contain the following constants and functions. + +@table @option +@item dar +input display aspect ratio, it is the same as (@var{w} / @var{h}) * @var{sar} + +@item duration +the current frame's duration, in seconds + +@item hsub +@item vsub +horizontal and vertical chroma subsample values. For example for the +pixel format "yuv422p" @var{hsub} is 2 and @var{vsub} is 1. + +@item main_h, H +the input height + +@item main_w, W +the input width + +@item n +the number of input frame, starting from 0 + +@item pict_type +a number representing the picture type + +@item qr_w, w +the width of the encoded QR code + +@item rendered_qr_w, q +@item rendered_padded_qr_w, Q +the width of the rendered QR code, without and without padding. + +These parameters allow the @var{q} and @var{Q} expressions to refer to each +other, so you can for example specify @code{q=3/4*Q}. + +@item rand(min, max) +return a random number included between @var{min} and @var{max} + +@item sar +the input sample aspect ratio + +@item t +timestamp expressed in seconds, NAN if the input timestamp is unknown + +@item x +@item y +the x and y offset coordinates where the text is drawn. + +These parameters allow the @var{x} and @var{y} expressions to refer to each +other, so you can for example specify @code{y=x/dar}. +@end table + +@anchor{qrencode_text_expansion} +@subsection qrencode Text expansion + +If @option{expansion} is set to @code{none}, the text is printed verbatim. + +If @option{expansion} is set to @code{normal} (which is the default), +the following expansion mechanism is used. + +The backslash character @samp{\}, followed by any character, always expands to +the second character. + +Sequences of the form @code{%@{...@}} are expanded. 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. + +Note that they probably must also be escaped as the value for the @option{text} +option in the filter argument string and as the filter argument in the +filtergraph description, and possibly also for the shell, that makes up to four +levels of escaping; using a text file with the @option{textfile} option avoids +these problems. + +The following functions are available: + +@table @command +@item n, frame_num +return the frame number + +@item pts +Return the presentation timestamp of the current frame. + +It can take up to two arguments. + +The first argument is the format of the timestamp; it defaults to @code{flt} for +seconds as a decimal number with microsecond accuracy; @code{hms} stands for a +formatted @var{[-]HH:MM:SS.mmm} timestamp with millisecond accuracy. +@code{gmtime} stands for the timestamp of the frame formatted as UTC time; +@code{localtime} stands for the timestamp of the frame formatted as local time +zone time. If the format is set to @code{hms24hh}, the time is formatted in 24h +format (00-23). + +The second argument is an offset added to the timestamp. + +If the format is set to @code{localtime} or @code{gmtime}, a third argument may +be supplied: a @code{strftime} C function format string. By default, +@var{YYYY-MM-DD HH:MM:SS} format will be used. + +@item expr, e +Evaluate the expression's value and output as a double. + +It must take one argument specifying the expression to be evaluated, accepting +the constants and functions defined in @ref{qrencode_expressions}. + +@item expr_formatted, ef +Evaluate the expression's value and output as a formatted string. + +The first argument is the expression to be evaluated, just as for the @var{expr} function. +The second argument specifies the output format. Allowed values are @samp{x}, +@samp{X}, @samp{d} and @samp{u}. They are treated exactly as in the +@code{printf} function. +The third parameter is optional and sets the number of positions taken by the output. +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 @code{strftime} C function 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 @code{strftime} C function 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. + +The first argument is mandatory and specifies the metadata key. + +The second argument is optional and specifies a default value, used when the +metadata key is not found or empty. + +Available metadata can be identified by inspecting entries starting with TAG +included within each frame section printed by running @code{ffprobe +-show_frames}. + +String metadata generated in filters leading to the qrencode filter are also +available. + +@item rand(min, max) +return a random number included between @var{min} and @var{max} +@end table + +@subsection Examples + +@itemize +@item +Generate a QR code encoding the specified text with the default size, overalaid +in the top left corner of the input video, with the default size: +@example +qrencode=text=www.ffmpeg.org +@end example + +@item +Same as below, but select blue on pink colors: +@example +qrencode=text=www.ffmpeg.org:bc=pink@@0.5:fc=blue +@end example + +@item +Place the QR code in the bottom right corner of the input video: +@example +qrencode=text=www.ffmpeg.org:x=W-Q:y=H-Q +@end example + +@item +Generate a QR code with width of 200 pixels and padding, making the padded width +4/3 of the QR code width: +@example +qrencode=text=www.ffmpeg.org:q=200:Q=4/3*q +@end example + +@item +Generate a QR code with padded width of 200 pixels and padding, making the QR +code width 3/4 of the padded width: +@example +qrencode=text=www.ffmpeg.org:Q=200:q=3/4*Q +@end example + +@item +Make the QR code a fraction of the input video width: +@example +qrencode=text=www.ffmpeg.org:q=W/5 +@end example + +@item +Generate a QR code encoding the frame number: +@example +qrencode=text=%@{n@} +@end example + +@item +Generate a QR code encoding the GMT timestamp: +@example +qrencode=text=%@{gmtime@} +@end example + +@item +Generate a QR code encoding the timestamp expressed as a float: +@example +qrencode=text=%@{pts@} +@end example + +@end itemize + @section random Flush video frames from internal cache of frames into a random order. @@ -28749,6 +29045,137 @@ ffplay -f lavfi life=s=300x200:mold=10:r=60:ratio=0.1:death_color=#C83232:life_c @end example @end itemize +@section qrencodesrc + +Generate a QR code using the libqrencode library (see +@url{https://fukuchi.org/works/qrencode/}). + +To enable the compilation of this source, you need to configure FFmpeg with with +@code{--enable-libqrencode}. + +The QR code is generated from the provided text or text pattern. The +corresponding QR code is scaled and put in the video output according to the +specified output size options. + +In case no text is specified, the QR code is not generated, but an empty colored +output is returned instead. + +This source accepts the following options: + +@table @option + +@item qrcode_width, q +@item padded_qrcode_width, Q +Specify an expression for the width of the rendered QR code, with and without +padding. The @var{qrcode_width} expression can reference the value set by the +@var{padded_qrcode_width} expression, and vice versa. +By default @var{padded_qrcode_width} is set to @var{qrcode_width}, meaning that +there is no padding. + +These expressions are evaluated only once, when initializing the source. +See the @ref{qrencode_expressions,,qrencode Expressions} section for details. + +Note that some of the constants are missing for the source (for example the +@var{x} or @var{t} or ΒΈ@var{n}), since they only makes sense when evaluating the +expression for each frame rather than at initialization time. + +@item rate, r +Specify the frame rate of the sourced video, as the number of frames +generated per second. It has to be a string in the format +@var{frame_rate_num}/@var{frame_rate_den}, an integer number, a floating point +number or a valid video frame rate abbreviation. The default value is +"25". + +@item case_sensitive, cs +Instruct libqrencode to use case sensitive encoding. This is enabled by +default. This can be disabled to reduce the QR encoding size. + +@item level, l +Specify the QR encoding error correction level. With an higher correction level, +the encoding size will increase but the code will be more robust to corruption. +Lower level is @var{L}. + +It accepts the following values: +@table @samp +@item L +@item M +@item Q +@item H +@end table + +@item expansion +Select how the input text is expanded. Can be either @code{none}, or +@code{normal} (default). See the @ref{qrencode_text_expansion,,qrencode Text expansion} +section for details. + +@item text +@item textfile +Define the text to be rendered. In case neither is specified, no QR is encoded +(just an empty colored frame). + +In case expansion is enabled, the text is treated as a text template, using the +qrencode expansion mechanism. See the @ref{qrencode_text_expansion,,qrencode +Text expansion} section for details. + +@item background_color, bc +@item foreground_color, fc +Set the QR code and background color. The default value of +@var{foreground_color} is "black", the default value of @var{background_color} +is "white". + +For the syntax of the color options, check the @ref{color syntax,,"Color" +section in the ffmpeg-utils manual,ffmpeg-utils}. +@end table + +@subsection Examples + +@itemize +@item +Generate a QR code encoding the specified text with the default size: +@example +qrencodesrc=text=www.ffmpeg.org +@end example + +@item +Same as below, but select blue on pink colors: +@example +qrencodesrc=text=www.ffmpeg.org:bc=pink:fc=blue +@end example + +@item +Generate a QR code with width of 200 pixels and padding, making the padded width +4/3 of the QR code width: +@example +qrencodesrc=text=www.ffmpeg.org:q=200:Q=4/3*q +@end example + +@item +Generate a QR code with padded width of 200 pixels and padding, making the QR +code width 3/4 of the padded width: +@example +qrencodesrc=text=www.ffmpeg.org:Q=200:q=3/4*Q +@end example + +@item +Generate a QR code encoding the frame number: +@example +qrencodesrc=text=%@{n@} +@end example + +@item +Generate a QR code encoding the GMT timestamp: +@example +qrencodesrc=text=%@{gmtime@} +@end example + +@item +Generate a QR code encoding the timestamp expressed as a float: +@example +qrencodesrc=text=%@{pts@} +@end example + +@end itemize + @anchor{allrgb} @anchor{allyuv} @anchor{color} diff --git a/libavfilter/Makefile b/libavfilter/Makefile index badaa43859..31371ceb1a 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -599,6 +599,8 @@ OBJS-$(CONFIG_NULLSRC_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_OPENCLSRC_FILTER) += vf_program_opencl.o opencl.o OBJS-$(CONFIG_PAL75BARS_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_PAL100BARS_FILTER) += vsrc_testsrc.o +OBJS-$(CONFIG_QRENCODE_FILTER) += qrencode.o textutils.o +OBJS-$(CONFIG_QRENCODESRC_FILTER) += qrencode.o textutils.o OBJS-$(CONFIG_RGBTESTSRC_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_SIERPINSKI_FILTER) += vsrc_sierpinski.o OBJS-$(CONFIG_SMPTEBARS_FILTER) += vsrc_testsrc.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 135794ba36..20feb37967 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -411,6 +411,7 @@ extern const AVFilter ff_vf_pseudocolor; extern const AVFilter ff_vf_psnr; extern const AVFilter ff_vf_pullup; extern const AVFilter ff_vf_qp; +extern const AVFilter ff_vf_qrencode; extern const AVFilter ff_vf_random; extern const AVFilter ff_vf_readeia608; extern const AVFilter ff_vf_readvitc; @@ -561,6 +562,7 @@ extern const AVFilter ff_vsrc_mandelbrot; extern const AVFilter ff_vsrc_mptestsrc; extern const AVFilter ff_vsrc_nullsrc; extern const AVFilter ff_vsrc_openclsrc; +extern const AVFilter ff_vsrc_qrencodesrc; extern const AVFilter ff_vsrc_pal75bars; extern const AVFilter ff_vsrc_pal100bars; extern const AVFilter ff_vsrc_rgbtestsrc; diff --git a/libavfilter/qrencode.c b/libavfilter/qrencode.c new file mode 100644 index 0000000000..09af8dfb4e --- /dev/null +++ b/libavfilter/qrencode.c @@ -0,0 +1,820 @@ +/* + * 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 QR encoder source and filter. + * + * A QR code (quick-response code) is a type of two-dimensional matrix + * barcode, invented in 1994, by Japanese company Denso Wave for + * labelling automobile parts. + * + * This source uses the libqrencode library to generate QR code: + * https://fukuchi.org/works/qrencode/ + */ + +//#define DEBUG + +#include "config_components.h" + +#include "libavutil/internal.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/lfg.h" +#include "libavutil/random_seed.h" + +#include "avfilter.h" +#include "drawutils.h" +#include "internal.h" +#include "formats.h" +#include "textutils.h" +#include "video.h" +#include "libswscale/swscale.h" + +#include + +enum var_name { + VAR_dar, + VAR_duration, + VAR_hsub, VAR_vsub, + VAR_main_h, VAR_H, + VAR_main_w, VAR_W, + VAR_n, + VAR_pict_type, + VAR_qr_w, VAR_w, + VAR_rendered_padded_qr_w, VAR_Q, + VAR_rendered_qr_w, VAR_q, + VAR_sar, + VAR_t, + VAR_x, + VAR_y, + VAR_VARS_NB +}; + +static const char *const var_names[] = { + "dar", + "duration", + "hsub", "vsub", + "main_h", "H", ///< height of the input video + "main_w", "W", ///< width of the input video + "n", ///< number of frame + "pict_type", + "qr_w", "w", ///< width of the QR code + "rendered_padded_qr_w", "Q", ///< width of the rendered QR code + "rendered_qr_w", "q", ///< width of the rendered QR code + "sar", + "t", ///< timestamp expressed in seconds + "x", + "y", + NULL +}; + +#define V(name_) qr->var_values[VAR_##name_] + +enum Expansion { + EXPANSION_NONE, + EXPANSION_NORMAL +}; + +typedef struct QREncodeContext { + const AVClass *class; + + char is_source; + char *x_expr; + char *y_expr; + AVExpr *x_pexpr, *y_pexpr; + + char *rendered_qrcode_width_expr; + char *rendered_padded_qrcode_width_expr; + AVExpr *rendered_qrcode_width_pexpr, *rendered_padded_qrcode_width_pexpr; + + int rendered_qrcode_width; + int rendered_padded_qrcode_width; + + unsigned char *text; + char *textfile; + uint64_t pts; + + int level; + char case_sensitive; + + uint8_t foreground_color[4]; + uint8_t background_color[4]; + + FFDrawContext draw; + FFDrawColor draw_foreground_color; ///< foreground color + FFDrawColor draw_background_color; ///< background color + + /* these are only used when nothing must be encoded */ + FFDrawContext draw0; + FFDrawColor draw0_background_color; ///< background color + + uint8_t *qrcode_data[4]; + int qrcode_linesize[4]; + uint8_t *qrcode_mask_data[4]; + int qrcode_mask_linesize[4]; + + /* only used for filter to contain scaled image to blend on top of input */ + uint8_t *rendered_qrcode_data[4]; + int rendered_qrcode_linesize[4]; + + int qrcode_width; + int padded_qrcode_width; + + AVRational frame_rate; + + int expansion; ///< expansion mode to use for the text + FFExpandTextContext expand_text; ///< expand text in case expansion is enabled + AVBPrint expanded_text; ///< used to contain the expanded text + + double var_values[VAR_VARS_NB]; + AVLFG lfg; ///< random generator + AVDictionary *metadata; +} QREncodeContext; + +#define OFFSET(x) offsetof(QREncodeContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM +#define TFLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +#define COMMON_OPTIONS \ + { "qrcode_width", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \ + { "q", "set rendered QR code width expression", OFFSET(rendered_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "64"}, 0, INT_MAX, FLAGS }, \ + { "padded_qrcode_width", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \ + { "Q", "set rendered padded QR code width expression", OFFSET(rendered_padded_qrcode_width_expr), AV_OPT_TYPE_STRING, {.str = "q"}, 0, INT_MAX, FLAGS }, \ + { "case_sensitive", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \ + { "cs", "generate code which is case sensitive", OFFSET(case_sensitive), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, \ + \ + { "level", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"}, \ + { "l", "error correction level, lowest is L", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = AVCOL_SPC_UNSPECIFIED }, 0, QR_ECLEVEL_H, .flags = FLAGS, "level"}, \ + { "L", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_L }, 0, 0, FLAGS, "level" }, \ + { "M", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_M }, 0, 0, FLAGS, "level" }, \ + { "Q", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_Q }, 0, 0, FLAGS, "level" }, \ + { "H", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = QR_ECLEVEL_H }, 0, 0, FLAGS, "level" }, \ + \ + {"expansion", "set the expansion mode", OFFSET(expansion), AV_OPT_TYPE_INT, {.i64=EXPANSION_NORMAL}, 0, 2, FLAGS, "expansion"}, \ + {"none", "set no expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NONE}, 0, 0, FLAGS, "expansion"}, \ + {"normal", "set normal expansion", OFFSET(expansion), AV_OPT_TYPE_CONST, {.i64 = EXPANSION_NORMAL}, 0, 0, FLAGS, "expansion"}, \ + \ + { "foreground_color", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \ + { "fc", "set QR foreground color", OFFSET(foreground_color), AV_OPT_TYPE_COLOR, {.str = "black"}, 0, 0, FLAGS }, \ + { "background_color", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \ + { "bc", "set QR background color", OFFSET(background_color), AV_OPT_TYPE_COLOR, {.str = "white"}, 0, 0, FLAGS }, \ + \ + {"text", "set text to encode", OFFSET(text), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \ + {"textfile", "set text file to encode", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS}, \ + +static const char *const fun2_names[] = { + "rand" +}; + +static double drand(void *opaque, double min, double max) +{ + return min + (max-min) / UINT_MAX * av_lfg_get(opaque); +} + +static const ff_eval_func2 fun2[] = { + drand, + NULL +}; + +static int func_pts(void *ctx, AVBPrint *bp, const char *function_name, + unsigned argc, char **argv) +{ + QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; + const char *fmt; + const char *strftime_fmt = NULL; + const char *delta = NULL; + double t = qr->var_values[VAR_t]; + + // argv: pts, FMT, [DELTA, strftime_fmt] + + fmt = argc >= 1 ? argv[0] : "flt"; + if (argc >= 2) { + delta = argv[1]; + } + if (argc >= 3) { + strftime_fmt = argv[2]; + } + + return ff_print_pts(ctx, bp, t, delta, fmt, strftime_fmt); +} + +static int func_frame_num(void *ctx, AVBPrint *bp, const char *function_name, + unsigned argc, char **argv) +{ + QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; + + av_bprintf(bp, "%d", (int)V(n)); + 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_frame_metadata(void *ctx, AVBPrint *bp, const char *function_name, + unsigned argc, char **argv) +{ + QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; + AVDictionaryEntry *e = av_dict_get(qr->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_eval_expr(void *ctx, AVBPrint *bp, const char *function_name, + unsigned argc, char **argv) +{ + QREncodeContext *qr = ((AVFilterContext *)ctx)->priv; + + return ff_print_eval_expr(ctx, bp, argv[0], + fun2_names, fun2, + var_names, qr->var_values, &qr->lfg); +} + +static int func_eval_expr_formatted(void *ctx, AVBPrint *bp, const char *function_name, + unsigned argc, char **argv) +{ + QREncodeContext *qr = ((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_formatted_eval_expr(ctx, bp, argv[0], + fun2_names, fun2, + var_names, qr->var_values, + &qr->lfg, + argv[1][0], positions); +} + +static FFExpandTextFunction expand_text_functions[] = { + { "expr", 1, 1, func_eval_expr }, + { "e", 1, 1, func_eval_expr }, + { "expr_formatted", 2, 3, func_eval_expr_formatted }, + { "ef", 2, 3, func_eval_expr_formatted }, + { "metadata", 1, 2, func_frame_metadata }, + { "frame_num", 0, 0, func_frame_num }, + { "n", 0, 0, func_frame_num }, + { "gmtime", 0, 1, func_strftime }, + { "localtime", 0, 1, func_strftime }, + { "pts", 0, 3, func_pts } +}; + +static av_cold int init(AVFilterContext *ctx) +{ + QREncodeContext *qr = ctx->priv; + int ret; + + av_lfg_init(&qr->lfg, av_get_random_seed()); + + qr->qrcode_width = -1; + qr->rendered_padded_qrcode_width = -1; + + if (qr->textfile) { + if (qr->text) { + av_log(ctx, AV_LOG_ERROR, + "Both text and text file provided. Please provide only one\n"); + return AVERROR(EINVAL); + } + if ((ret = ff_load_textfile(ctx, (const char *)qr->textfile, &(qr->text), NULL)) < 0) + return ret; + } + + qr->expand_text = (FFExpandTextContext) { + .log_ctx = ctx, + .functions = expand_text_functions, + .functions_nb = FF_ARRAY_ELEMS(expand_text_functions) + }; + + av_bprint_init(&qr->expanded_text, 0, AV_BPRINT_SIZE_UNLIMITED); + + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + QREncodeContext *qr = ctx->priv; + + av_expr_free(qr->x_pexpr); + av_expr_free(qr->y_pexpr); + + av_bprint_finalize(&qr->expanded_text, NULL); + + av_freep(&qr->qrcode_data[0]); + av_freep(&qr->rendered_qrcode_data[0]); + av_freep(&qr->qrcode_mask_data[0]); +} + +#ifdef DEBUG +static void show_qrcode(AVFilterContext *ctx, const QRcode *qrcode) +{ + int i, j; + char *line = av_malloc(qrcode->width + 1); + const char *p = qrcode->data; + + if (!line) + return; + for (i = 0; i < qrcode->width; i++) { + for (j = 0; j < qrcode->width; j++) + line[j] = (*p++)&1 ? '@' : ' '; + line[j] = 0; + av_log(ctx, AV_LOG_DEBUG, "%3d: %s\n", i, line); + } + av_free(line); +} +#endif + +static int draw_qrcode(AVFilterContext *ctx, AVFrame *frame) +{ + QREncodeContext *qr = ctx->priv; + struct SwsContext *sws = NULL; + QRcode *qrcode = NULL; + int i, j; + char qrcode_width_changed; + int ret; + int offset; + uint8_t *srcp; + uint8_t *dstp0, *dstp; + + av_bprint_clear(&qr->expanded_text); + + switch (qr->expansion) { + case EXPANSION_NONE: + av_bprintf(&qr->expanded_text, "%s", qr->text); + break; + case EXPANSION_NORMAL: + if ((ret = ff_expand_text(&qr->expand_text, qr->text, &qr->expanded_text)) < 0) + return ret; + break; + } + + if (!qr->expanded_text.str || qr->expanded_text.str[0] == 0) { + if (qr->is_source) { + ff_fill_rectangle(&qr->draw0, &qr->draw0_background_color, + frame->data, frame->linesize, + 0, 0, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width); + } + + return 0; + } + + av_log(ctx, AV_LOG_DEBUG, "Encoding string '%s'\n", qr->expanded_text.str); + qrcode = QRcode_encodeString(qr->expanded_text.str, 1, qr->level, QR_MODE_8, + qr->case_sensitive); + if (!qrcode) { + ret = AVERROR(errno); + av_log(ctx, AV_LOG_ERROR, + "Failed to encode string with error \'%s\'\n", av_err2str(ret)); + goto end; + } + + av_log(ctx, AV_LOG_DEBUG, + "Encoded QR with width:%d version:%d\n", qrcode->width, qrcode->version); +#ifdef DEBUG + show_qrcode(ctx, (const QRcode *)qrcode); +#endif + + qrcode_width_changed = qr->qrcode_width != qrcode->width; + qr->qrcode_width = qrcode->width; + + // realloc mask if needed + if (qrcode_width_changed) { + av_freep(&qr->qrcode_mask_data[0]); + ret = av_image_alloc(qr->qrcode_mask_data, qr->qrcode_mask_linesize, + qrcode->width, qrcode->width, + AV_PIX_FMT_GRAY8, 16); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, + "Failed to allocate image for QR code with width %d\n", qrcode->width); + goto end; + } + } + + /* fill mask */ + dstp0 = qr->qrcode_mask_data[0]; + srcp = qrcode->data; + + for (i = 0; i < qrcode->width; i++) { + dstp = dstp0; + for (j = 0; j < qrcode->width; j++) + *dstp++ = (*srcp++ & 1) ? 255 : 0; + dstp0 += qr->qrcode_mask_linesize[0]; + } + + if (qr->is_source) { + if (qrcode_width_changed) { + /* realloc padded image */ + + // compute virtual non-rendered padded size + // Q/q = W/w + qr->padded_qrcode_width = + ((double)qr->rendered_padded_qrcode_width / qr->rendered_qrcode_width) * qrcode->width; + + av_freep(&qr->qrcode_data[0]); + ret = av_image_alloc(qr->qrcode_data, qr->qrcode_linesize, + qr->padded_qrcode_width, qr->padded_qrcode_width, + AV_PIX_FMT_ARGB, 16); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, + "Failed to allocate image for QR code with width %d\n", + qr->padded_qrcode_width); + goto end; + } + } + + /* fill padding */ + ff_fill_rectangle(&qr->draw, &qr->draw_background_color, + qr->qrcode_data, qr->qrcode_linesize, + 0, 0, qr->padded_qrcode_width, qr->padded_qrcode_width); + + /* blend mask */ + offset = (qr->padded_qrcode_width - qr->qrcode_width) / 2; + ff_blend_mask(&qr->draw, &qr->draw_foreground_color, + qr->qrcode_data, qr->qrcode_linesize, + qr->padded_qrcode_width, qr->padded_qrcode_width, + qr->qrcode_mask_data[0], qr->qrcode_mask_linesize[0], qrcode->width, qrcode->width, + 3, 0, offset, offset); + + /* scale padded QR over the frame */ + sws = sws_alloc_context(); + if (!sws) { + ret = AVERROR(ENOMEM); + goto end; + } + + av_opt_set_int(sws, "srcw", qr->padded_qrcode_width, 0); + av_opt_set_int(sws, "srch", qr->padded_qrcode_width, 0); + av_opt_set_int(sws, "src_format", AV_PIX_FMT_ARGB, 0); + av_opt_set_int(sws, "dstw", qr->rendered_padded_qrcode_width, 0); + av_opt_set_int(sws, "dsth", qr->rendered_padded_qrcode_width, 0); + av_opt_set_int(sws, "dst_format", frame->format, 0); + av_opt_set_int(sws, "sws_flags", SWS_POINT, 0); + + if ((ret = sws_init_context(sws, NULL, NULL)) < 0) + goto end; + + sws_scale(sws, + (const uint8_t *const *)&qr->qrcode_data, qr->qrcode_linesize, + 0, qr->padded_qrcode_width, + frame->data, frame->linesize); + } else { +#define EVAL_EXPR(name_) \ + av_expr_eval(qr->name_##_pexpr, qr->var_values, &qr->lfg); + + V(qr_w) = V(w) = qrcode->width; + + V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width); + V(rendered_padded_qr_w) = V(Q) = EVAL_EXPR(rendered_padded_qrcode_width); + /* It is necessary if q is expressed from Q */ + V(rendered_qr_w) = V(q) = EVAL_EXPR(rendered_qrcode_width); + + V(x) = EVAL_EXPR(x); + V(y) = EVAL_EXPR(y); + /* It is necessary if x is expressed from y */ + V(x) = EVAL_EXPR(x); + + av_log(ctx, AV_LOG_DEBUG, + "Rendering QR code with values n:%d w:%d q:%d Q:%d x:%d y:%d t:%f\n", + (int)V(n), (int)V(w), (int)V(q), (int)V(Q), (int)V(x), (int)V(y), V(t)); + + /* blend rectangle over the target */ + ff_blend_rectangle(&qr->draw, &qr->draw_background_color, + frame->data, frame->linesize, frame->width, frame->height, + V(x), V(y), V(Q), V(Q)); + + if (V(q) != qr->rendered_qrcode_width) { + av_freep(&qr->rendered_qrcode_data[0]); + qr->rendered_qrcode_width = V(q); + + ret = av_image_alloc(qr->rendered_qrcode_data, qr->rendered_qrcode_linesize, + qr->rendered_qrcode_width, qr->rendered_qrcode_width, + AV_PIX_FMT_GRAY8, 16); + if (ret < 0) { + av_log(ctx, AV_LOG_ERROR, + "Failed to allocate image for rendered QR code with width %d\n", + qr->rendered_qrcode_width); + goto end; + } + } + + /* scale mask */ + sws = sws_alloc_context(); + if (!sws) { + ret = AVERROR(ENOMEM); + goto end; + } + + av_opt_set_int(sws, "srcw", qr->qrcode_width, 0); + av_opt_set_int(sws, "srch", qr->qrcode_width, 0); + av_opt_set_int(sws, "src_format", AV_PIX_FMT_GRAY8, 0); + av_opt_set_int(sws, "dstw", qr->rendered_qrcode_width, 0); + av_opt_set_int(sws, "dsth", qr->rendered_qrcode_width, 0); + av_opt_set_int(sws, "dst_format", AV_PIX_FMT_GRAY8, 0); + av_opt_set_int(sws, "sws_flags", SWS_POINT, 0); + + if ((ret = sws_init_context(sws, NULL, NULL)) < 0) + goto end; + + sws_scale(sws, + (const uint8_t *const *)&qr->qrcode_mask_data, qr->qrcode_mask_linesize, + 0, qr->qrcode_width, + qr->rendered_qrcode_data, qr->rendered_qrcode_linesize); + + /* blend mask over the input frame */ + offset = (V(Q) - V(q)) / 2; + ff_blend_mask(&qr->draw, &qr->draw_foreground_color, + frame->data, frame->linesize, frame->width, frame->height, + qr->rendered_qrcode_data[0], qr->rendered_qrcode_linesize[0], + qr->rendered_qrcode_width, qr->rendered_qrcode_width, + 3, 0, V(x) + offset, V(y) + offset); + } + +end: + sws_freeContext(sws); + QRcode_free(qrcode); + + return ret; +} + +#if CONFIG_QRENCODESRC_FILTER + +static const AVOption qrencodesrc_options[] = { + COMMON_OPTIONS + { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, + { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(qrencodesrc); + +static int qrencodesrc_config_props(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + QREncodeContext *qr = ctx->priv; + int ret; + + qr->is_source = 1; + V(x) = V(y) = 0; + +#define PARSE_AND_EVAL_EXPR(var_name_, expr_name_) \ + ret = av_expr_parse_and_eval(&qr->var_values[VAR_##var_name_], \ + qr->expr_name_##_expr, \ + var_names, qr->var_values, \ + NULL, NULL, \ + fun2_names, fun2, \ + &qr->lfg, 0, ctx); \ + if (ret < 0) { \ + av_log(ctx, AV_LOG_ERROR, \ + "Could not evaluate expression '%s'\n", \ + qr->expr_name_##_expr); \ + return ret; \ + } + + /* undefined for the source */ + V(main_w) = V(W) = NAN; + V(main_h) = V(H) = NAN; + V(x) = V(y) = V(t) = V(n) = NAN; + V(dar) = V(sar) = 1.0; + + PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width); + V(q) = V(rendered_qr_w); + PARSE_AND_EVAL_EXPR(rendered_padded_qr_w, rendered_padded_qrcode_width); + V(Q) = V(rendered_padded_qr_w); + PARSE_AND_EVAL_EXPR(rendered_qr_w, rendered_qrcode_width); + V(q) = V(rendered_qr_w); + + qr->rendered_qrcode_width = V(rendered_qr_w); + qr->rendered_padded_qrcode_width = V(rendered_padded_qr_w); + + av_log(ctx, AV_LOG_VERBOSE, + "q:%d Q:%d case_sensitive:%d level:%d\n", + (int)qr->rendered_qrcode_width, (int)qr->rendered_padded_qrcode_width, + qr->case_sensitive, qr->level); + + if (qr->rendered_padded_qrcode_width < qr->rendered_qrcode_width) { + av_log(ctx, AV_LOG_ERROR, + "Resulting padded QR code width (%d) is lesser than the QR code width (%d)\n", + qr->rendered_padded_qrcode_width, qr->rendered_qrcode_width); + return AVERROR(EINVAL); + } + + ff_draw_init(&qr->draw, AV_PIX_FMT_ARGB, FF_DRAW_PROCESS_ALPHA); + ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color); + ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color); + + ff_draw_init(&qr->draw0, outlink->format, FF_DRAW_PROCESS_ALPHA); + ff_draw_color(&qr->draw0, &qr->draw0_background_color, (const uint8_t *)&qr->background_color); + + outlink->w = qr->rendered_padded_qrcode_width; + outlink->h = qr->rendered_padded_qrcode_width; + outlink->time_base = av_inv_q(qr->frame_rate); + outlink->frame_rate = qr->frame_rate; + + return 0; +} + +static int request_frame(AVFilterLink *outlink) +{ + AVFilterContext *ctx = (AVFilterContext *)outlink->src; + QREncodeContext *qr = ctx->priv; + AVFrame *frame = + ff_get_video_buffer(outlink, qr->rendered_padded_qrcode_width, qr->rendered_padded_qrcode_width); + int ret; + + if (!frame) + return AVERROR(ENOMEM); + frame->sample_aspect_ratio = (AVRational) {1, 1}; + V(n) = frame->pts = qr->pts++; + V(t) = qr->pts * av_q2d(outlink->time_base); + + if ((ret = draw_qrcode(ctx, frame)) < 0) + return ret; + + return ff_filter_frame(outlink, frame); +} + +static int qrencodesrc_query_formats(AVFilterContext *ctx) +{ + enum AVPixelFormat pix_fmt; + FFDrawContext draw; + AVFilterFormats *fmts = NULL; + int ret; + + // this is needed to support both the no-draw and draw cases + // for the no-draw case we use FFDrawContext to write on the input picture ref + for (pix_fmt = 0; av_pix_fmt_desc_get(pix_fmt); pix_fmt++) + if (ff_draw_init(&draw, pix_fmt, 0) >= 0 && + sws_isSupportedOutput(pix_fmt) && + (ret = ff_add_format(&fmts, pix_fmt)) < 0) + return ret; + + return ff_set_common_formats(ctx, fmts); +} + +static const AVFilterPad qrencodesrc_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .request_frame = request_frame, + .config_props = qrencodesrc_config_props, + } +}; + +const AVFilter ff_vsrc_qrencodesrc = { + .name = "qrencodesrc", + .description = NULL_IF_CONFIG_SMALL("Generate a QR code."), + .priv_size = sizeof(QREncodeContext), + .priv_class = &qrencodesrc_class, + .init = init, + .uninit = uninit, + .inputs = NULL, + FILTER_OUTPUTS(qrencodesrc_outputs), + FILTER_QUERY_FUNC(qrencodesrc_query_formats), +}; + +#endif // CONFIG_QRENCODESRC_FILTER + +#if CONFIG_QRENCODE_FILTER + +static const AVOption qrencode_options[] = { + COMMON_OPTIONS + {"x", "set x expression", OFFSET(x_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS}, + {"y", "set y expression", OFFSET(y_expr), AV_OPT_TYPE_STRING, {.str="0"}, 0, 0, TFLAGS}, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(qrencode); + +static int qrencode_config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + QREncodeContext *qr = ctx->priv; + char *expr; + int ret; + + qr->is_source = 0; + + ff_draw_init(&qr->draw, inlink->format, FF_DRAW_PROCESS_ALPHA); + + V(W) = V(main_w) = inlink->w; + V(H) = V(main_h) = inlink->h; + V(sar) = inlink->sample_aspect_ratio.num ? av_q2d(inlink->sample_aspect_ratio) : 1; + V(dar) = (double)inlink->w / inlink->h * V(sar); + V(hsub) = 1 << qr->draw.hsub_max; + V(vsub) = 1 << qr->draw.vsub_max; + V(t) = NAN; + V(x) = V(y) = NAN; + + qr->x_pexpr = qr->y_pexpr = NULL; + qr->x_pexpr = qr->y_pexpr = NULL; + +#define PARSE_EXPR(name_) \ + ret = av_expr_parse(&qr->name_##_pexpr, expr = qr->name_##_expr, var_names, \ + NULL, NULL, fun2_names, fun2, 0, ctx); \ + if (ret < 0) { \ + av_log(ctx, AV_LOG_ERROR, \ + "Could not to parse expression '%s' for '%s'\n", \ + expr, #name_); \ + return AVERROR(EINVAL); \ + } + + PARSE_EXPR(x); + PARSE_EXPR(y); + PARSE_EXPR(rendered_qrcode_width); + PARSE_EXPR(rendered_padded_qrcode_width); + + ff_draw_init(&qr->draw, inlink->format, FF_DRAW_PROCESS_ALPHA); + ff_draw_color(&qr->draw, &qr->draw_foreground_color, (const uint8_t *)&qr->foreground_color); + ff_draw_color(&qr->draw, &qr->draw_background_color, (const uint8_t *)&qr->background_color); + + qr->rendered_qrcode_width = -1; + + return 0; +} + +static int qrencode_query_formats(AVFilterContext *ctx) +{ + return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + QREncodeContext *qr = ctx->priv; + int ret; + + V(n) = inlink->frame_count_out; + V(t) = frame->pts == AV_NOPTS_VALUE ? + NAN : frame->pts * av_q2d(inlink->time_base); + V(pict_type) = frame->pict_type; + V(duration) = frame->duration * av_q2d(inlink->time_base); + + qr->metadata = frame->metadata; + + if ((ret = draw_qrcode(ctx, frame)) < 0) + return ret; + + return ff_filter_frame(outlink, frame); +} + +static const AVFilterPad avfilter_vf_qrencode_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .flags = AVFILTERPAD_FLAG_NEEDS_WRITABLE, + .filter_frame = filter_frame, + .config_props = qrencode_config_input, + }, +}; + +const AVFilter ff_vf_qrencode = { + .name = "qrencode", + .description = NULL_IF_CONFIG_SMALL("Draw a QR code on top of video frames."), + .priv_size = sizeof(QREncodeContext), + .priv_class = &qrencode_class, + .init = init, + .uninit = uninit, + FILTER_INPUTS(avfilter_vf_qrencode_inputs), + FILTER_OUTPUTS(ff_video_default_filterpad), + FILTER_QUERY_FUNC(qrencode_query_formats), + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, +}; + +#endif // CONFIG_QRENCODE_FILTER