diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 8130f7b46c..e38c6b6f6d 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -542,6 +542,7 @@ OBJS-$(CONFIG_SHOWSPECTRUMPIC_FILTER)        += avf_showspectrum.o
 OBJS-$(CONFIG_SHOWVOLUME_FILTER)             += avf_showvolume.o
 OBJS-$(CONFIG_SHOWWAVES_FILTER)              += avf_showwaves.o
 OBJS-$(CONFIG_SHOWWAVESPIC_FILTER)           += avf_showwaves.o
+OBJS-$(CONFIG_SUB2VIDEO_FILTER)              += svf_sub2video.o
 OBJS-$(CONFIG_SPECTRUMSYNTH_FILTER)          += vaf_spectrumsynth.o
 
 # multimedia sources
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index abd0a47750..5b631b3617 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -518,6 +518,7 @@ extern const AVFilter ff_avf_showvolume;
 extern const AVFilter ff_avf_showwaves;
 extern const AVFilter ff_avf_showwavespic;
 extern const AVFilter ff_vaf_spectrumsynth;
+extern const AVFilter ff_svf_sub2video;
 
 /* multimedia sources */
 extern const AVFilter ff_avsrc_amovie;
diff --git a/libavfilter/svf_sub2video.c b/libavfilter/svf_sub2video.c
new file mode 100644
index 0000000000..689c2a565c
--- /dev/null
+++ b/libavfilter/svf_sub2video.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2021 softworkz
+ *
+ * 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
+ * graphical subtitles to video conversion, based on previous sub2video
+ * implementation.
+ */
+
+#include <math.h>
+
+#include "libavutil/audio_fifo.h"
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/xga_font_data.h"
+#include "avfilter.h"
+#include "blend.h"
+#include "filters.h"
+#include "internal.h"
+#include "libavcodec/avcodec.h"
+typedef struct Sub2VideoContext {
+    const AVClass *class;
+    int w, h;
+    AVFrame *outpicref;
+    int pixstep;
+} Sub2VideoContext;
+
+#define OFFSET(x) offsetof(Sub2VideoContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+////static int alloc_out_frame(Sub2VideoContext *s2v_ctx, const int16_t *p,
+////                           const AVFilterLink *inlink, AVFilterLink *outlink,
+////                           const AVFrame *in)
+////{
+////    if (!s2v_ctx->outpicref) {
+////        int j;
+////        AVFrame *out = s2v_ctx->outpicref =
+////            ff_get_video_buffer(outlink, outlink->w, outlink->h);
+////        if (!out)
+////            return AVERROR(ENOMEM);
+////        out->width  = outlink->w;
+////        out->height = outlink->h;
+////        out->pts = in->pts + av_rescale_q((p - (int16_t *)in->data[0]) / inlink->channels,
+////                                          av_make_q(1, inlink->sample_rate),
+////                                          outlink->time_base);
+////        for (j = 0; j < outlink->h; j++)
+////            memset(out->data[0] + j*out->linesize[0], 0, outlink->w * s2v_ctx->pixstep);
+////    }
+////    return 0;
+////}
+
+
+static const AVOption sub2video_options[] = {
+    { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x512"}, 0, 0, FLAGS },
+    { "s",    "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x512"}, 0, 0, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(sub2video);
+
+static int query_formats(AVFilterContext *ctx)
+{
+    AVFilterFormats *formats = NULL;
+    AVFilterLink *inlink = ctx->inputs[0];
+    AVFilterLink *outlink = ctx->outputs[0];
+    static const enum AVSubtitleType subtitle_fmts[] = { SUBTITLE_BITMAP, SUBTITLE_NONE };
+    static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB32, AV_PIX_FMT_NONE };
+    int ret;
+
+    /* set input subtitle format */
+    formats = ff_make_format_list(subtitle_fmts);
+    if ((ret = ff_formats_ref(formats, &inlink->outcfg.formats)) < 0)
+        return ret;
+
+    /* set output video format */
+    formats = ff_make_format_list(pix_fmts);
+    if ((ret = ff_formats_ref(formats, &outlink->incfg.formats)) < 0)
+        return ret;
+
+    return 0;
+}
+
+static int config_input(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx = inlink->dst;
+    Sub2VideoContext *s = ctx->priv;
+
+    s->w = 1920; // inlink->w;
+    s->h = 1080; // inlink->h;
+    return 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    Sub2VideoContext *s = outlink->src->priv;
+    float overlap;
+
+    outlink->w = s->w;
+    outlink->h = s->h;
+    outlink->sample_aspect_ratio = (AVRational){1,1};
+
+    return 0;
+}
+
+static void sub2video_copy_rect(uint8_t *dst, int dst_linesize, int w, int h,
+                                AVSubtitleRect *r)
+{
+    uint32_t *pal, *dst2;
+    uint8_t *src, *src2;
+    int x, y;
+
+    if (r->type != SUBTITLE_BITMAP) {
+        av_log(NULL, AV_LOG_WARNING, "sub2video: non-bitmap subtitle\n");
+        return;
+    }
+    if (r->x < 0 || r->x + r->w > w || r->y < 0 || r->y + r->h > h) {
+        av_log(NULL, AV_LOG_WARNING, "sub2video: rectangle (%d %d %d %d) overflowing %d %d\n",
+            r->x, r->y, r->w, r->h, w, h
+        );
+        return;
+    }
+
+    dst += r->y * dst_linesize + r->x * 4;
+    src = r->data[0];
+    pal = (uint32_t *)r->data[1];
+    for (y = 0; y < r->h; y++) {
+        dst2 = (uint32_t *)dst;
+        src2 = src;
+        for (x = 0; x < r->w; x++)
+            *(dst2++) = pal[*(src2++)];
+        dst += dst_linesize;
+        src += r->linesize[0];
+    }
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *src_frame)
+{
+    Sub2VideoContext *s = inlink->dst->priv;
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    AVSubtitle *sub;
+    int ret;
+    int dst_linesize;
+    AVFrame *out;
+    unsigned int num_rects, i;
+    uint8_t *dst;
+
+    outlink->w = 1920;
+    outlink->h = 1080;
+
+    out = av_frame_alloc();
+    if (!out) {
+        av_frame_free(&src_frame);
+        return AVERROR(ENOMEM);
+    }
+
+    out->width = outlink->w;
+    out->height = outlink->h;
+    out->format = AV_PIX_FMT_RGB32;
+
+    if ((ret = av_frame_get_buffer(out, 0)) < 0)
+        return ret;
+
+    memset(out->data[0], 0, out->height * out->linesize[0]);
+
+    ////out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+    ////if (!out) {
+    ////    av_frame_free(&src_frame);
+    ////    return AVERROR(ENOMEM);
+    ////}
+    ////memset(out->data[0], 0, out->linesize[0] * out->height );
+
+    out->pts                    = src_frame->pts;
+    out->repeat_pict            = src_frame->repeat_pict;
+    out->pkt_dts                = src_frame->pkt_dts;
+    out->pkt_pos                = src_frame->pkt_pos;
+    out->pkt_size               = src_frame->pkt_size;
+    out->pkt_duration           = src_frame->pkt_duration;
+    out->reordered_opaque       = src_frame->reordered_opaque;
+    out->best_effort_timestamp  = src_frame->best_effort_timestamp;
+    out->flags                  = src_frame->flags;
+
+    sub = (AVSubtitle *)src_frame->data[0];
+
+    if (sub) {
+        num_rects = sub->num_rects;
+        dst          = out->data    [0];
+        dst_linesize = out->linesize[0];
+        for (i = 0; i < num_rects; i++)
+            sub2video_copy_rect(dst, dst_linesize, out->width, out->height, sub->rects[i]);
+    }
+
+    av_frame_free(&src_frame);
+    return ff_filter_frame(outlink, out);
+}
+
+////static int activate(AVFilterContext *ctx)
+////{
+////    AVFilterLink *inlink = ctx->inputs[0];
+////    AVFilterLink *outlink = ctx->outputs[0];
+////    Sub2VideoContext *s = ctx->priv;
+////
+////    do {
+////        int ret = ff_outlink_get_status(outlink);
+////        if (ret) {
+////            ff_inlink_set_status(inlink, ret);
+////            return 0;
+////        }
+////    } while (0);
+////
+////    return FFERROR_NOT_READY;
+////}
+
+static const AVFilterPad sub2video_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_SUBTITLE,
+        .filter_frame = filter_frame,
+        .config_props = config_input,
+    },
+    { NULL }
+};
+
+static const AVFilterPad sub2video_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+AVFilter ff_svf_sub2video = {
+    .name          = "sub2video",
+    .description   = NULL_IF_CONFIG_SMALL("Convert graphical subtitles to video"),
+    .query_formats = query_formats,
+    .priv_size     = sizeof(Sub2VideoContext),
+    .priv_class    = &sub2video_class,
+    .inputs        = sub2video_inputs,
+    .outputs       = sub2video_outputs,
+};
