[FFmpeg-devel] [PATCH v3 2/2] avformat: add srv3 demuxer

Hubert Głuchowski fishhh at fishhh.dev
Mon Dec 30 20:06:49 EET 2024


Signed-off-by: Hubert Głuchowski <fishhh at fishhh.dev>
---
 Changelog                 |   1 +
 MAINTAINERS               |   2 +
 configure                 |   2 +
 doc/general_contents.texi |   1 +
 libavformat/Makefile      |   1 +
 libavformat/allformats.c  |   1 +
 libavformat/srv3dec.c     | 515 ++++++++++++++++++++++++++++++++++++++
 7 files changed, 523 insertions(+)
 create mode 100644 libavformat/srv3dec.c

diff --git a/Changelog b/Changelog
index 40dec96e9c..32af2cfdd3 100644
--- a/Changelog
+++ b/Changelog
@@ -9,6 +9,7 @@ version <next>:
 - libx265 alpha layer encoding
 - ADPCM IMA Xbox decoder
 - Enhanced FLV v2: Multitrack audio/video, modern codec support
+- SRV3 subtitle decoder
 
 version 7.1:
 - Raw Captions with Time (RCWT) closed caption demuxer
diff --git a/MAINTAINERS b/MAINTAINERS
index 9714581c6b..a3a8e23c85 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -249,6 +249,7 @@ Codecs:
   sonic.c                               Alex Beregszaszi
   speedhq.c                             Steinar H. Gunderson
   srt*                                  Aurelien Jacobs
+  srv3*                                 Hubert Głuchowski (CC <fishhh at fishhh.dev>)
   sunrast.c                             Ivo van Poorten
   svq3.c                                Michael Niedermayer
   truemotion1*                          Mike Melanson
@@ -467,6 +468,7 @@ Muxers/Demuxers:
   segment.c                             Stefano Sabatini
   spdif*                                Anssi Hannula
   srtdec.c                              Aurelien Jacobs
+  srv3*                                 Hubert Głuchowski (CC <fishhh at fishhh.dev>)
   swf.c                                 Baptiste Coudurier
   tta.c                                 Alex Beregszaszi
   txd.c                                 Ivo van Poorten
diff --git a/configure b/configure
index 0a7ce31e09..e5574c780d 100755
--- a/configure
+++ b/configure
@@ -3727,6 +3727,8 @@ wtv_demuxer_select="mpegts_demuxer riffdec"
 wtv_muxer_select="mpegts_muxer riffenc"
 xmv_demuxer_select="riffdec"
 xwma_demuxer_select="riffdec"
+srv3_demuxer_deps="libxml2"
+srv3_demuxer_select="srv3dec"
 
 # indevs / outdevs
 android_camera_indev_deps="android camera2ndk mediandk pthreads"
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index 5faf89815b..c182568061 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -1450,6 +1450,7 @@ performance on systems without hardware floating point support).
 @item RealText         @tab   @tab X @tab   @tab X
 @item SAMI             @tab   @tab X @tab   @tab X
 @item Spruce format (STL) @tab   @tab X @tab   @tab X
+ at item SRV3             @tab   @tab X @tab   @tab X
 @item SSA/ASS          @tab X @tab X @tab X @tab X
 @item SubRip (SRT)     @tab X @tab X @tab X @tab X
 @item SubViewer v1     @tab   @tab X @tab   @tab X
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 074efc118a..6a9744d571 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -571,6 +571,7 @@ OBJS-$(CONFIG_SPEEX_MUXER)               += oggenc.o \
                                             vorbiscomment.o
 OBJS-$(CONFIG_SRT_DEMUXER)               += srtdec.o subtitles.o
 OBJS-$(CONFIG_SRT_MUXER)                 += srtenc.o
+OBJS-$(CONFIG_SRV3_DEMUXER)              += srv3dec.o subtitles.o
 OBJS-$(CONFIG_STL_DEMUXER)               += stldec.o subtitles.o
 OBJS-$(CONFIG_STR_DEMUXER)               += psxstr.o
 OBJS-$(CONFIG_STREAMHASH_MUXER)          += hashenc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 445f13f42a..f56eb34a90 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -451,6 +451,7 @@ extern const FFInputFormat  ff_spdif_demuxer;
 extern const FFOutputFormat ff_spdif_muxer;
 extern const FFInputFormat  ff_srt_demuxer;
 extern const FFOutputFormat ff_srt_muxer;
+extern const FFInputFormat  ff_srv3_demuxer;
 extern const FFInputFormat  ff_str_demuxer;
 extern const FFInputFormat  ff_stl_demuxer;
 extern const FFOutputFormat ff_streamhash_muxer;
diff --git a/libavformat/srv3dec.c b/libavformat/srv3dec.c
new file mode 100644
index 0000000000..95f68b9523
--- /dev/null
+++ b/libavformat/srv3dec.c
@@ -0,0 +1,515 @@
+/*
+ * Copyright (c) 2024 Hubert Głuchowski
+ *
+ * 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
+ * SRV3/YTT subtitle demuxer
+ * This is a YouTube specific subtitle format that utilizes XML.
+ * Because there is currently no official documentation, some information about the format
+ * was acquired by reading YTSubConverter code and YouTube's captions.js implementation.
+ * @see https://github.com/arcusmaximus/YTSubConverter
+ */
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include "libavcodec/srv3.h"
+#include "avformat.h"
+#include "demux.h"
+#include "internal.h"
+#include "subtitles.h"
+#include "libavutil/bprint.h"
+#include "libavutil/opt.h"
+#include "libavutil/mem.h"
+
+typedef struct SRV3GlobalSegments {
+    SRV3Segment *list;
+    struct SRV3GlobalSegments *next;
+} SRV3GlobalSegments;
+
+typedef struct SRV3Context {
+    FFDemuxSubtitlesQueue q;
+    SRV3Pen *pens;
+    SRV3WindowPos *wps;
+    SRV3GlobalSegments *segments;
+} SRV3Context;
+
+static SRV3Pen srv3_default_pen = {
+    .id = -1,
+
+    .font_size = 100,
+    .font_style = 0,
+    .attrs = 0,
+
+    .edge_type = SRV3_EDGE_NONE,
+    .edge_color = 0x020202,
+
+    .ruby_part = SRV3_RUBY_NONE,
+
+    .foreground_color = 0xFFFFFF,
+    .foreground_alpha = 254,
+    .background_color = 0x080808,
+    .background_alpha = 192,
+
+    .next = NULL
+};
+
+static void srv3_free_context_data(SRV3Context *ctx) {
+    void *next;
+
+#define FREE_LIST(type, list, until)                                           \
+do {                                                                           \
+    for (void *current = list; current && current != until; current = next) {  \
+        next = ((type*)current)->next;                                         \
+        av_free(current);                                                      \
+    }                                                                          \
+} while(0)
+
+    FREE_LIST(SRV3Pen, ctx->pens, &srv3_default_pen);
+    FREE_LIST(SRV3WindowPos, ctx->wps, NULL);
+
+    for (SRV3GlobalSegments *segments = ctx->segments; segments; segments = next) {
+        FREE_LIST(SRV3Segment, segments->list, NULL);
+        next = segments->next;
+        av_free(segments);
+    }
+}
+
+static SRV3Pen *srv3_get_pen(SRV3Context *ctx, int id) {
+    for (SRV3Pen *pen = ctx->pens; pen; pen = pen->next)
+        if (pen->id == id)
+            return pen;
+    return NULL;
+}
+
+static int srv3_probe(const AVProbeData *p)
+{
+    if (strstr(p->buf, "<timedtext") && strstr(p->buf, "format=\"3\">"))
+        return AVPROBE_SCORE_MAX;
+
+    return 0;
+}
+
+static int srv3_parse_numeric_value(SRV3Context *ctx, const char *parent, const char *name, const char *value, int base, int *out, int min, int max)
+{
+    char *endptr;
+    long parsed;
+
+    parsed = strtol(value, &endptr, base);
+
+    if (*endptr != 0) {
+        av_log(ctx, AV_LOG_WARNING, "Failed to parse value \"%s\" of %s attribute %s as an integer\n", value, parent, name);
+        return AVERROR_INVALIDDATA;
+    } else if (parsed < min || parsed > max) {
+        av_log(ctx, AV_LOG_WARNING, "Value %li out of range for %s attribute %s ([%i, %i])\n", parsed, parent, name, min, max);
+        return AVERROR(ERANGE);
+    } else if(out) {
+        *out = parsed;
+        return 0;
+    } else return parsed;
+}
+
+static int srv3_parse_numeric_attr(SRV3Context *ctx, const char *parent, xmlAttrPtr attr, int *out, int min, int max)
+{
+    return srv3_parse_numeric_value(ctx, parent, attr->name, attr->children->content, 10, out, min, max) == 0;
+}
+
+static void srv3_parse_color_attr(SRV3Context *ctx, const char *parent, xmlAttrPtr attr, int *out)
+{
+    srv3_parse_numeric_value(ctx, parent, attr->name, attr->children->content + (*attr->children->content == '#'), 16, out, 0, 0xFFFFFF);
+}
+
+typedef struct SRV3AttributeDef {
+    const char *name;
+    size_t offset;
+    int min, max;
+} SRV3AttributeDef;
+
+static void srv3_parse_attributes(SRV3Context *ctx, void *dst, const char *parent, const SRV3AttributeDef *defs, xmlAttrPtr attr) {
+    for (; attr; attr = attr->next) {
+        for(const SRV3AttributeDef *def = defs; def->name; ++def)
+            if(!strcmp(def->name, attr->name)) {
+                int *out = (int*)((char*)dst + def->offset);
+                if(def->min == INT_MAX) {
+                    if(def->max == INT_MAX)
+                        srv3_parse_color_attr(ctx, parent, attr, out);
+                    else
+                        *out |= (!strcmp(attr->children->content, "1")) * def->max;
+                } else
+                    srv3_parse_numeric_attr(ctx, parent, attr, out, def->min, def->max);
+                goto found;
+            }
+        av_log(ctx, AV_LOG_WARNING, "Unhandled %s property %s\n", parent, attr->name);
+found:;
+    }
+}
+
+#define NUMERIC_ATTRIBUTE(min, max) min, max
+#define COLOR_ATTRIBUTE INT_MAX, INT_MAX
+#define BITFLAG_ATTRIBUTE(value) INT_MAX, value
+
+static const SRV3AttributeDef srv3_pen_attributes[] = {
+    {"id", offsetof(SRV3Pen, id),               NUMERIC_ATTRIBUTE(0, INT_MAX)},
+    {"sz", offsetof(SRV3Pen, font_size),        NUMERIC_ATTRIBUTE(0, INT_MAX)},
+    {"fs", offsetof(SRV3Pen, font_style),       NUMERIC_ATTRIBUTE(1, 7)},
+    {"et", offsetof(SRV3Pen, edge_type),        NUMERIC_ATTRIBUTE(1, 4)},
+    {"ec", offsetof(SRV3Pen, edge_color),       COLOR_ATTRIBUTE},
+    {"fc", offsetof(SRV3Pen, foreground_color), COLOR_ATTRIBUTE},
+    {"fo", offsetof(SRV3Pen, foreground_alpha), NUMERIC_ATTRIBUTE(0, 0xFF)},
+    {"bc", offsetof(SRV3Pen, background_color), COLOR_ATTRIBUTE},
+    {"bo", offsetof(SRV3Pen, background_alpha), NUMERIC_ATTRIBUTE(0, 0xFF)},
+    {"rb", offsetof(SRV3Pen, ruby_part),        NUMERIC_ATTRIBUTE(0, 5)},
+    {"i",  offsetof(SRV3Pen, attrs),            BITFLAG_ATTRIBUTE(SRV3_PEN_ATTR_ITALIC)},
+    {"b",  offsetof(SRV3Pen, attrs),            BITFLAG_ATTRIBUTE(SRV3_PEN_ATTR_BOLD)},
+    {NULL}
+};
+
+static int srv3_read_pen(SRV3Context *ctx, xmlNodePtr element)
+{
+    SRV3Pen *pen = av_malloc(sizeof(SRV3Pen));
+    if (!pen)
+        return AVERROR(ENOMEM);
+    memcpy(pen, &srv3_default_pen, sizeof(SRV3Pen));
+    pen->next = ctx->pens;
+    ctx->pens = pen;
+
+    srv3_parse_attributes(ctx, pen, "pen", srv3_pen_attributes, element->properties);
+
+    /*
+    * For whatever reason three seems to be an unused value for this enum.
+    */
+    if (pen->ruby_part == 3) {
+        pen->ruby_part = 0;
+        av_log(ctx, AV_LOG_WARNING, "Encountered unknown ruby part 3\n");
+    }
+
+    return 0;
+}
+
+static const SRV3AttributeDef srv3_window_pos_attrs[] = {
+    {"id", offsetof(SRV3WindowPos, id),    NUMERIC_ATTRIBUTE(0, INT_MAX)},
+    {"ap", offsetof(SRV3WindowPos, point), NUMERIC_ATTRIBUTE(0, 8)},
+    {"ah", offsetof(SRV3WindowPos, x),     NUMERIC_ATTRIBUTE(0, 100)},
+    {"av", offsetof(SRV3WindowPos, y),     NUMERIC_ATTRIBUTE(0, 100)},
+    {NULL}
+};
+
+static int srv3_read_window_pos(SRV3Context *ctx, xmlNodePtr element)
+{
+    SRV3WindowPos *wp = av_mallocz(sizeof(SRV3Pen));
+    if (!wp)
+        return AVERROR(ENOMEM);
+    wp->next = ctx->wps;
+    ctx->wps = wp;
+
+    srv3_parse_attributes(ctx, wp, "window pos", srv3_window_pos_attrs, element->properties);
+
+    return 0;
+}
+
+static int srv3_read_pens(SRV3Context *ctx, xmlNodePtr head)
+{
+    int ret;
+
+    for (xmlNodePtr element = head->children; element; element = element->next) {
+        if (!strcmp(element->name, "pen")) {
+            if ((ret = srv3_read_pen(ctx, element)) < 0)
+                return ret;
+        } else if (!strcmp(element->name, "wp")) {
+            if ((ret = srv3_read_window_pos(ctx, element)) < 0)
+                return ret;
+        }
+    }
+
+    return 0;
+}
+
+#define ZERO_WIDTH_SPACE "\u200B"
+#define YTSUBCONV_PADDING_SPACE ZERO_WIDTH_SPACE " " ZERO_WIDTH_SPACE
+
+static int srv3_clean_segment_text(char *text) {
+    char *out = text;
+    const char *start = text;
+
+    while (1) {
+        const char *end = strstr(start, ZERO_WIDTH_SPACE);
+        size_t cnt = end ? end - start : strlen(start);
+
+        memmove(out, start, cnt);
+        out += cnt;
+
+        if (end) {
+            if (!av_strstart(end, YTSUBCONV_PADDING_SPACE, &start))
+                start = end + strlen(ZERO_WIDTH_SPACE);
+        } else break;
+    }
+
+    *out = '\0';
+    return out - text;
+}
+
+static int srv3_read_body(SRV3Context *ctx, xmlNodePtr body)
+{
+    int ret = 0;
+    AVBPrint textbuf;
+    char *text;
+    AVPacket *sub;
+    SRV3WindowPos *wp;
+    SRV3EventMeta *event;
+    int start, duration;
+
+    av_bprint_init(&textbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
+
+    for (xmlNodePtr element = body->children; element; element = element->next) {
+        if (!strcmp(element->name, "p")) {
+            SRV3Segment **segments_tail_next;
+            SRV3GlobalSegments *global_segments;
+            int textlen, lastlen = 0;
+            SRV3Pen *event_pen = &srv3_default_pen;
+
+            if ((event = av_mallocz(sizeof(SRV3EventMeta))) == NULL) {
+                ret = AVERROR(ENOMEM);
+                goto end;
+            }
+
+            segments_tail_next = &event->segments;
+
+            for (xmlAttrPtr attr = element->properties; attr; attr = attr->next) {
+                if (!strcmp(attr->name, "t"))
+                    srv3_parse_numeric_attr(ctx, "event", attr, &start, 0, INT_MAX);
+                else if (!strcmp(attr->name, "d"))
+                    srv3_parse_numeric_attr(ctx, "event", attr, &duration, 0, INT_MAX);
+                else if (!strcmp(attr->name, "wp")) {
+                    int id;
+                    srv3_parse_numeric_attr(ctx, "event", attr, &id, 0, INT_MAX);
+                    for (wp = ctx->wps; wp; wp = wp->next)
+                        if (wp->id == id) {
+                            event->wp = wp;
+                            break;
+                        }
+                    if (!event->wp)
+                        av_log(ctx, AV_LOG_WARNING, "Non-existent window pos %i assigned to event\n", id);
+                } else if (!strcmp(attr->name, "p")) {
+                    int id;
+                    if(srv3_parse_numeric_attr(ctx, "event", attr, &id, 0, INT_MAX)) {
+                        SRV3Pen *pen = srv3_get_pen(ctx, id);
+                        if(pen)
+                            event_pen = pen;
+                        else
+                            av_log(ctx, AV_LOG_WARNING, "Non-existent pen %i assigned to event\n", id);
+                    }
+                } else if (!strcmp(attr->name, "ws")) {
+                    // TODO: Handle window styles
+                } else {
+                    av_log(ctx, AV_LOG_WARNING, "Unhandled event property %s\n", attr->name);
+                    continue;
+                }
+            }
+
+            for (xmlNodePtr node = element->children; node; node = node->next) {
+                SRV3Segment *segment;
+
+                if (node->type != XML_ELEMENT_NODE && node->type != XML_TEXT_NODE) {
+                    av_log(ctx, AV_LOG_WARNING, "Unexpected event child node type %i\n", node->type);
+                    continue;
+                } else if(node->type == XML_ELEMENT_NODE && strcmp(node->name, "s")) {
+                    av_log(ctx, AV_LOG_WARNING, "Unknown event child node name %s\n", node->name);
+                    continue;
+                } else if (node->type == XML_ELEMENT_NODE && !node->children)
+                    continue;
+
+                text = node->type == XML_ELEMENT_NODE ? node->children->content : node->content;
+                textlen = srv3_clean_segment_text(text);
+
+                if (textlen == 0)
+                    continue;
+
+                segment = av_mallocz(sizeof(SRV3Segment));
+                if (!segment) {
+                    ret = AVERROR(ENOMEM);
+                    goto end;
+                }
+
+                segment->pen = event_pen;
+
+                if (node->type == XML_ELEMENT_NODE)
+                    for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) {
+                        if (!strcmp(attr->name, "p")) {
+                            int id;
+                            if(srv3_parse_numeric_attr(ctx, "segment", attr, &id, 0, INT_MAX)) {
+                                SRV3Pen *pen = srv3_get_pen(ctx, id);
+                                if(pen)
+                                    segment->pen = pen;
+                                else
+                                    av_log(ctx, AV_LOG_WARNING, "Non-existent pen %i assigned to segment\n", id);
+                            }
+                        } else {
+                            av_log(ctx, AV_LOG_WARNING, "Unhandled segment property %s\n", attr->name);
+                            continue;
+                        }
+                    }
+
+                av_bprint_append_data(&textbuf, text, textlen);
+
+                segment->size = textbuf.len - lastlen;
+                lastlen = textbuf.len;
+                *segments_tail_next = segment;
+                segments_tail_next = &segment->next;
+            }
+
+            if (!av_bprint_is_complete(&textbuf)) {
+                ret = AVERROR(ENOMEM);
+                goto end;
+            }
+
+            global_segments = av_mallocz(sizeof(SRV3GlobalSegments));
+            if (!global_segments) {
+                ret = AVERROR(ENOMEM);
+                goto end;
+            }
+            global_segments->list = event->segments;
+            global_segments->next = ctx->segments;
+            ctx->segments = global_segments;
+
+            sub = ff_subtitles_queue_insert(&ctx->q, textbuf.str, textbuf.len, 0);
+            if (!sub) {
+                ret = AVERROR(ENOMEM);
+                goto end;
+            }
+            sub->pts = start;
+            sub->duration = duration;
+
+            if ((ret = av_packet_add_side_data(sub, AV_PKT_DATA_SRV3_EVENT, (uint8_t*)event, sizeof(SRV3EventMeta))) < 0)
+               goto end;
+
+            av_bprint_clear(&textbuf);
+        }
+    }
+
+end:
+    av_bprint_finalize(&textbuf, NULL);
+    return ret;
+}
+
+static int srv3_read_header(AVFormatContext *s)
+{
+    int ret = 0;
+    SRV3Context *ctx = s->priv_data;
+    AVPacketSideData *head_sd;
+    SRV3Head *head;
+    AVBPrint content;
+    xmlDocPtr document = NULL;
+    xmlNodePtr root_element;
+    AVStream *st;
+
+    av_bprint_init(&content, 0, INT_MAX);
+
+    st = avformat_new_stream(s, NULL);
+    if (!st) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+    avpriv_set_pts_info(st, 64, 1, 1000);
+    st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+    st->codecpar->codec_id   = AV_CODEC_ID_SRV3;
+    st->disposition = AV_DISPOSITION_CAPTIONS;
+
+    ctx->q.keep_duplicates = 1;
+
+    if (!(head_sd = av_packet_side_data_new(&st->codecpar->coded_side_data, &st->codecpar->nb_coded_side_data, AV_PKT_DATA_SRV3_HEAD, sizeof(SRV3Head), 0))) {
+        ret = AVERROR(ENOMEM);
+        goto end;
+    }
+    head = (SRV3Head*)head_sd->data;
+
+    if ((ret = avio_read_to_bprint(s->pb, &content, SIZE_MAX)) < 0)
+        goto end;
+    if (!avio_feof(s->pb) || !av_bprint_is_complete(&content)) {
+        ret = AVERROR_INVALIDDATA;
+        goto end;
+    }
+
+    LIBXML_TEST_VERSION;
+
+    document = xmlReadMemory(content.str, content.len, s->url, NULL, 0);
+
+    if (!document) {
+        ret = AVERROR_INVALIDDATA;
+        goto end;
+    }
+
+    root_element = xmlDocGetRootElement(document);
+
+    ctx->pens = &srv3_default_pen;
+
+    for (xmlNodePtr element = root_element->children; element; element = element->next) {
+        if (!strcmp(element->name, "head"))
+            if ((ret = srv3_read_pens(ctx, element)) < 0)
+                goto end;
+    }
+
+    for (xmlNodePtr element = root_element->children; element; element = element->next) {
+        if (!strcmp(element->name, "body"))
+            if ((ret = srv3_read_body(ctx, element)) < 0)
+                goto end;
+    }
+
+    head->pens = ctx->pens;
+    ff_subtitles_queue_finalize(s, &ctx->q);
+
+end:
+    xmlFreeDoc(document);
+    av_bprint_finalize(&content, NULL);
+    return ret;
+}
+
+static int srv3_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    SRV3Context *ctx = s->priv_data;
+    return ff_subtitles_queue_read_packet(&ctx->q, pkt);
+}
+
+static int srv3_read_seek(AVFormatContext *s, int stream_index,
+                            int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
+{
+    SRV3Context *ctx = s->priv_data;
+    return ff_subtitles_queue_seek(&ctx->q, s, stream_index,
+                                   min_ts, ts, max_ts, flags);
+}
+
+static av_cold int srv3_read_close(AVFormatContext *s)
+{
+    SRV3Context *ctx = s->priv_data;
+    ff_subtitles_queue_clean(&ctx->q);
+    srv3_free_context_data(ctx);
+    return 0;
+}
+
+const FFInputFormat ff_srv3_demuxer = {
+    .p.name         = "srv3",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("SRV3 subtitle"),
+    .p.extensions   = "srv3",
+    .priv_data_size = sizeof(SRV3Context),
+    .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
+    .read_probe     = srv3_probe,
+    .read_header    = srv3_read_header,
+    .read_packet    = srv3_read_packet,
+    .read_seek2     = srv3_read_seek,
+    .read_close     = srv3_read_close,
+};
-- 
2.47.0



More information about the ffmpeg-devel mailing list