diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 5f63ec0123..2a321a7f50 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -526,6 +526,9 @@ OBJS-$(CONFIG_YUVTESTSRC_FILTER)             += vsrc_testsrc.o
 
 OBJS-$(CONFIG_NULLSINK_FILTER)               += vsink_nullsink.o
 
+# subtitle filters
+OBJS-$(CONFIG_SLEET_FILTER)                  += sf_sleet.o
+
 # multimedia filters
 OBJS-$(CONFIG_ABITSCOPE_FILTER)              += avf_abitscope.o
 OBJS-$(CONFIG_ADRAWGRAPH_FILTER)             += f_drawgraph.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 5bd54db2c8..efe16b8e1b 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -519,6 +519,7 @@ extern const AVFilter ff_avf_showwaves;
 extern const AVFilter ff_avf_showwavespic;
 extern const AVFilter ff_vaf_spectrumsynth;
 extern const AVFilter ff_svf_sub2video;
+extern const AVFilter ff_sf_sleet;
 
 /* multimedia sources */
 extern const AVFilter ff_avsrc_amovie;
diff --git a/libavfilter/sf_sleet.c b/libavfilter/sf_sleet.c
new file mode 100644
index 0000000000..cf7701c01f
--- /dev/null
+++ b/libavfilter/sf_sleet.c
@@ -0,0 +1,209 @@
+/*
+ * 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
+ * text subtitle filter which translates to 'leet speak'
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "libavcodec/avcodec.h"
+
+static const char* alphabet_src = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+static const char* alphabet_dst = "abcd3f6#1jklmn0pq257uvwxyzAB(D3F6#1JKLMN0PQ257UVWXYZ";
+
+
+typedef struct LeetContext {
+    const AVClass *class;
+    enum AVSubtitleType format;
+} LeetContext;
+
+static const AVOption sleet_options[] = {
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(sleet);
+
+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_ASS, 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;
+    LeetContext *s = ctx->priv;
+
+    s->format = inlink->format;
+    return 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    LeetContext *s = outlink->src->priv;
+
+    outlink->format = s->format;
+
+    return 0;
+}
+
+static void avsubtitle_free_ref(void *opaque, uint8_t *data)
+{
+    avsubtitle_free((AVSubtitle *)data);
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *src_frame)
+{
+    LeetContext *s = inlink->dst->priv;
+    AVFilterLink *outlink = inlink->dst->outputs[0];
+    AVSubtitle *sub;
+    int ret;
+    AVFrame *out;
+    unsigned int num_rects;
+    uint8_t *dst;
+
+    outlink->format = inlink->format;
+
+    out = av_frame_alloc();
+    if (!out) {
+        av_frame_free(&src_frame);
+        return AVERROR(ENOMEM);
+    }
+
+    out->format = outlink->format;
+
+    if ((ret = av_frame_get_buffer2(out, AVMEDIA_TYPE_SUBTITLE, 0)) < 0)
+        return ret;
+
+    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) {
+        AVSubtitle *out_sub = av_memdup(sub, sizeof(*out_sub));
+        if (!out_sub)
+            return AVERROR(ENOMEM);
+
+        out->buf[0] = av_buffer_create((uint8_t*)out_sub, sizeof(*out_sub), avsubtitle_free_ref, NULL, AV_BUFFER_FLAG_READONLY);
+        out->data[0] = (uint8_t*)out_sub;
+
+        if (sub->num_rects) {
+            out_sub->rects = av_malloc_array(sub->num_rects, sizeof(AVSubtitleRect *));
+        }
+
+        for (unsigned i = 0; i < sub->num_rects; i++) {
+
+            AVSubtitleRect *src_rect = sub->rects[i];
+            AVSubtitleRect *dst_rect = av_memdup(src_rect, sizeof(*dst_rect));
+            out_sub->rects[i] = dst_rect;
+
+            if (src_rect->text) {
+                dst_rect->text = av_strdup(src_rect->text);
+                if (!dst_rect->text)
+                    return AVERROR(ENOMEM);
+
+                for (size_t n = 0; n < strlen(dst_rect->text); n++) {
+                    for (size_t t = 0; t < FF_ARRAY_ELEMS(alphabet_src); t++) {
+                        if (dst_rect->text[n] == alphabet_src[t]) {
+                            dst_rect->text[n] = alphabet_dst[t];
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (src_rect->ass) {
+                dst_rect->ass = av_strdup(src_rect->ass);
+                if (!dst_rect->ass)
+                    return AVERROR(ENOMEM);
+
+                for (size_t n = 0; n < strlen(dst_rect->ass); n++) {
+                    for (size_t t = 0; t < FF_ARRAY_ELEMS(alphabet_src); t++) {
+                        if (dst_rect->ass[n] == alphabet_src[t]) {
+                            dst_rect->ass[n] = alphabet_dst[t];
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    av_frame_free(&src_frame);
+    return ff_filter_frame(outlink, out);
+}
+
+static const AVFilterPad sleet_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_SUBTITLE,
+        .filter_frame = filter_frame,
+        .config_props = config_input,
+    },
+    { NULL }
+};
+
+static const AVFilterPad sleet_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_SUBTITLE,
+        .config_props  = config_output,
+    },
+    { NULL }
+};
+
+const AVFilter ff_sf_sleet = {
+    .name          = "sleet",
+    .description   = NULL_IF_CONFIG_SMALL("Translate text subtitles to 'leet speak'"),
+    .query_formats = query_formats,
+    .priv_size     = sizeof(LeetContext),
+    .priv_class    = &sleet_class,
+    .inputs        = sleet_inputs,
+    .outputs       = sleet_outputs,
+};