[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