/* * bytebeat demuxer (libbytebeat) * Copyright (c) 2025 kimapr * Copyright (c) 2016 Josh de Kock * Copyright (c) 2023 Francesco Carusi * Copyright (c) 2011 Stefano Sabatini * Copyright (c) 2010 S.N. Hemanth Meenakshisundaram * Copyright (c) 2003 Gustavo Sverzut Barbieri * Copyright (c) 2006-2016 libass contributors * * 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 */ #include #include #include "libavutil/avstring.h" #include "libavutil/bprint.h" #include "libavutil/channel_layout.h" #include "libavutil/common.h" #include "libavutil/opt.h" #include "libavutil/mem.h" #include "libavcodec/ass.h" #include "avformat.h" #include "demux.h" #include "internal.h" typedef struct BBContext { const AVClass *class; bytebeat_State *state; bytebeat_Opts *opts; double duration; int sample_rate; int channel_count; int tw, th; int has_t; int vw, vh; bytebeat_Pkt *pkt; int subi; int subn; int subc; double next_video_pos; long long lowest_sst_pts; AVPacket *video_pkt; //options char *font; int font_size; } BBContext; #define OFFSET(x) offsetof(BBContext, x) static const AVOption options[] = { {"font", "set the font to use for error console", OFFSET(font), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, AV_OPT_FLAG_SUBTITLE_PARAM }, {"fontsize", "set the font size", OFFSET(font_size), AV_OPT_TYPE_INT, {.i64 = 32}, 1, 256, AV_OPT_FLAG_SUBTITLE_PARAM }, { NULL } }; static const AVClass class_bytebeat = { .class_name = "libbytebeat", .item_name = av_default_item_name, .option = options, .version = LIBAVUTIL_VERSION_INT, }; #if CONFIG_LIBFREETYPE #define BYTEBEAT_PRECISE_FONTWIDTH 1 // modified code from avfilter/vf_drawtext #include #include #include #if CONFIG_LIBFONTCONFIG #include #endif #undef __FTERRORS_H__ #define FT_ERROR_START_LIST { #define FT_ERRORDEF(e, v, s) { (e), (s) }, #define FT_ERROR_END_LIST { 0, NULL } }; static const struct ft_error { int err; const char *err_msg; } ft_errors[] = #include FT_ERRORS_H #define FT_ERRMSG(e) ft_errors[e].err_msg typedef struct DrawTextContext { const AVClass *class; #if CONFIG_LIBFONTCONFIG const uint8_t *font; ///< font to be used #endif const uint8_t *fontfile; ///< font to be used FT_Library library; ///< freetype font library handle FT_Face face; ///< freetype font face handle } DrawTextContext; static int load_font_file(DrawTextContext *s, const char *path, int index) { int err; err = FT_New_Face(s->library, path, index, &s->face); if (err) { #if !CONFIG_LIBFONTCONFIG av_log(s, AV_LOG_ERROR, "Could not load font \"%s\": %s\n", s->fontfile, FT_ERRMSG(err)); #endif return AVERROR(EINVAL); } return 0; } #if CONFIG_LIBFONTCONFIG static int load_font_fontconfig(DrawTextContext *s, int font_size) { FcConfig *fontconfig; FcPattern *pat, *best; FcResult result = FcResultMatch; FcChar8 *filename = NULL; int index; double size = font_size; int err = AVERROR(ENOENT); fontconfig = FcInitLoadConfigAndFonts(); if (!fontconfig) { av_log(s, AV_LOG_ERROR, "impossible to init fontconfig\n"); return AVERROR_UNKNOWN; } pat = FcNameParse(s->fontfile ? s->fontfile : (uint8_t *)(intptr_t)"default"); if (!pat) { av_log(s, AV_LOG_ERROR, "could not parse fontconfig pat"); return AVERROR(EINVAL); } FcPatternAddString(pat, FC_FAMILY, s->font); FcPatternAddDouble(pat, FC_SIZE, size); FcDefaultSubstitute(pat); if (!FcConfigSubstitute(fontconfig, pat, FcMatchPattern)) { av_log(s, AV_LOG_ERROR, "could not substitue fontconfig options"); /* very unlikely */ FcPatternDestroy(pat); return AVERROR(ENOMEM); } best = FcFontMatch(fontconfig, pat, &result); FcPatternDestroy(pat); if (!best || result != FcResultMatch) { av_log(s, AV_LOG_ERROR, "Cannot find a valid font for the family %s\n", s->font); goto fail; } if ( FcPatternGetInteger(best, FC_INDEX, 0, &index ) != FcResultMatch || FcPatternGetDouble (best, FC_SIZE, 0, &size ) != FcResultMatch) { av_log(s, AV_LOG_ERROR, "impossible to find font information"); return AVERROR(EINVAL); } if (FcPatternGetString(best, FC_FILE, 0, &filename) != FcResultMatch) { av_log(s, AV_LOG_ERROR, "No file path for %s\n", s->font); goto fail; } av_log(s, AV_LOG_VERBOSE, "Using \"%s\"\n", filename); err = load_font_file(s, filename, index); if (err) return err; FcConfigDestroy(fontconfig); fail: FcPatternDestroy(best); return err; } #endif static int load_font(DrawTextContext *s, int font_size) { int err; /* load the face, and set up the encoding, which is by default UTF-8 */ err = load_font_file(s, s->fontfile, 0); if (!err) return 0; #if CONFIG_LIBFONTCONFIG err = load_font_fontconfig(s, font_size); if (!err) return 0; #endif return err; } /* code from libass */ // ISC License // // Copyright (C) 2006-2016 libass contributors // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // static void set_font_metrics(FT_Face face) { // Mimicking GDI's behavior for asc/desc/height. // These fields are (apparently) sometimes used for signed values, // despite being unsigned in the spec. TT_OS2 *os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2); if (os2 && ((short)os2->usWinAscent + (short)os2->usWinDescent != 0)) { face->ascender = (short)os2->usWinAscent; face->descender = -(short)os2->usWinDescent; face->height = face->ascender - face->descender; } // If we didn't have usable Win values in the OS/2 table, // then the values from FreeType will still be in these fields. // It'll use either the OS/2 typo metrics or the hhea ones. // If the font has typo metrics but FreeType didn't use them // (either old FT or USE_TYPO_METRICS not set), we'll try those. // In the case of a very broken font that has none of those options, // we fall back on using face.bbox. // Anything without valid OS/2 Win values isn't supported by VSFilter, // so at this point compatibility's out the window and we're just // trying to render _something_ readable. if (face->ascender - face->descender == 0 || face->height == 0) { if (os2 && (os2->sTypoAscender - os2->sTypoDescender) != 0) { face->ascender = os2->sTypoAscender; face->descender = os2->sTypoDescender; face->height = face->ascender - face->descender; } else { face->ascender = face->bbox.yMax; face->descender = face->bbox.yMin; face->height = face->ascender - face->descender; } } } /* /code from libass */ static int measure_font(const char **font, int font_size, double *width, double *height, FT_Face *face, FT_Library *library) { int ret = 0; DrawTextContext s; double mul = 1; s.class = &class_bytebeat; s.fontfile = *font; #if CONFIG_LIBFONTCONFIG s.font = *font; #endif if ((ret = FT_Init_FreeType(&s.library))) { av_log(&s, AV_LOG_ERROR, "Could not load FreeType: %s\n", FT_ERRMSG(ret)); return AVERROR(EINVAL); } if ((ret = load_font(&s, font_size)) < 0) { FT_Done_FreeType(s.library); return ret; } if ((ret = FT_Set_Char_Size(s.face, 0, font_size*64, 0, 72 * 256) )) { av_log(&s, AV_LOG_ERROR, "Could not set font size\n"); FT_Done_Face(s.face); FT_Done_FreeType(s.library); return AVERROR(EINVAL); } set_font_metrics(s.face); av_freep(font); *font = av_strdup(s.face->family_name); if ((ret = FT_Load_Char(s.face, 'W', FT_LOAD_DEFAULT))) { FT_Done_Face(s.face); FT_Done_FreeType(s.library); } mul = font_size / (s.face->height * (s.face->size->metrics.y_scale/65536.) / 256. / 64.); if (width) *width = floor(s.face->glyph->metrics.horiAdvance / 256. / 64. * mul / font_size * s.face->units_per_EM) / s.face->units_per_EM * font_size; if (height) *height = s.face->height * (s.face->size->metrics.y_scale/65536.) / 256. / 64. * mul; if (width && height) av_log(&s, AV_LOG_INFO, "WxH: %f x %f\n", *width, *height); if (face && library) { *face = s.face; *library = s.library; } else { FT_Done_Face(s.face); FT_Done_FreeType(s.library); } return ret; } typedef struct { int blen; int bbuf; int llen; } ASSBinEnc; static void assbinenc_byte(ASSBinEnc *enc, AVBPrint *out, int c) { if (c != -1) { enc->bbuf |= (c & 0xFF) << (32 - (++enc->blen)*8); } if (c == -1 || enc->blen == 3) { int lim = enc->blen*8/6+!!(enc->blen*8%6); for (int i=1; i <= lim; i++) { av_bprint_chars(out, 33 + ((enc->bbuf >> (32 - i*6)) & 63), 1); if (++enc->llen >= 80 || (c == -1 && i == lim)) { enc->llen = 0; av_bprint_chars(out, '\n', 1); } } enc->blen = 0; enc->bbuf = 0; } } static unsigned long font_read(FT_Stream stream, unsigned long offset, unsigned char *buffer, unsigned long count) { if (!stream->base) return stream->read(stream, offset, buffer, count); if (count > stream->size - offset) count = stream->size - offset; memcpy(buffer, stream->base + offset, count); return count; } static void font_consume(FT_Face face, FT_Library library, AVBPrint *out) { unsigned char buf[8192]; long long pos = 0; int count = 0; ASSBinEnc enc = {0}; while ((count = font_read(face->stream, pos, buf, sizeof(buf))) > 0) { pos += count; for (int i=0; i < count; i++) { assbinenc_byte(&enc, out, buf[i]); } } assbinenc_byte(&enc, out, -1); av_bprint_chars(out, '\n', 1); FT_Done_Face(face); FT_Done_FreeType(library); } #endif static void bb_logger(const char *message, long long t, void *userdata) { if (t == -1) { av_log(userdata, AV_LOG_ERROR, "%s\n", message); } } static int read_close_bytebeat(AVFormatContext *s); static int read_header_bytebeat(AVFormatContext *s) { AVStream *ast, *sst, *vst; BBContext *bb = s->priv_data; int64_t size; char *buf; int known_duration = 0; bytebeat_MetaValue mtv; bb->opts = NULL; bb->state = NULL; bb->pkt = NULL; bb->video_pkt = NULL; bb->lowest_sst_pts = -1; bb->subi = 0; size = avio_size(s->pb); if (size <= 0) return AVERROR_INVALIDDATA; buf = av_malloc(size); if (!buf) return AVERROR(ENOMEM); size = avio_read(s->pb, buf, size); if (size < 0) { av_log(s, AV_LOG_ERROR, "Reading input buffer failed.\n"); av_freep(&buf); return size; } bb->opts = bytebeat_default_opts(); if (!bb->opts) { av_freep(&buf); read_close_bytebeat(s); return AVERROR(ENOMEM); } bb->opts->code = buf; bb->opts->code_len = size; bb->opts->logger = bb_logger; bb->state = bytebeat_open(bb->opts); if (!bb->state) { read_close_bytebeat(s); return AVERROR_INVALIDDATA; } mtv.type = BYTEBEAT_META_F64; bytebeat_meta_get(bb->state, "freq", &mtv); bb->sample_rate = mtv.v_f64; mtv.type = BYTEBEAT_META_I64; bytebeat_meta_get(bb->state, "chanc", &mtv); bb->channel_count = mtv.v_i64; mtv.type = BYTEBEAT_META_I64; bytebeat_meta_get(bb->state, "len", &mtv); bb->duration = ((double)mtv.v_i64)/bb->sample_rate; for (const char *keys[] = { "album", "album_artist", "artist", "comment", "composer", "copyright", "creation_time", "date", "disc", "encoder", "encoded_by", "filename", "genre", "language", "performer", "publisher", "service_name", "service_provider", "title", "track", NULL }, **key = keys; *key != NULL; key++) { mtv.type = BYTEBEAT_META_STR; if (bytebeat_meta_get(bb->state, *key, &mtv)) continue; av_dict_set(&s->metadata, *key, mtv.v_str, 0); bytebeat_meta_free(&mtv); } ast = avformat_new_stream(s, NULL); if (!ast) { read_close_bytebeat(s); return AVERROR(ENOMEM); } avpriv_set_pts_info(ast, 64, 1, AV_TIME_BASE); if (bb->duration >= 0 && bb->duration < ((double)INT64_MAX + 1) / AV_TIME_BASE) { known_duration = 1; ast->duration = llrint(bb->duration*AV_TIME_BASE); } ast->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; ast->codecpar->codec_id = AV_NE(AV_CODEC_ID_PCM_F32BE, AV_CODEC_ID_PCM_F32LE); ast->codecpar->sample_rate = bb->sample_rate; av_channel_layout_default(&ast->codecpar->ch_layout, bb->channel_count); if (bb->has_t = !bytebeat_get_term_size(bb->state, &bb->tw, &bb->th)) { const char *font = av_strdup(bb->font ? bb->font : "monospace"); char *sst_data; int font_size = bb->font_size; double font_width = font_size / 2.; double font_height = font_size; AVBPrint sbuf; int clearbuf; #ifdef BYTEBEAT_PRECISE_FONTWIDTH FT_Face face = NULL; FT_Library libft = NULL; #endif vst = avformat_new_stream(s, NULL); sst = avformat_new_stream(s, NULL); if ((!vst) || (!sst)) { read_close_bytebeat(s); return AVERROR(ENOMEM); } avpriv_set_pts_info(vst, 64, 1, AV_TIME_BASE); avpriv_set_pts_info(sst, 64, 1, 100); if (known_duration) { vst->duration = ast->duration; } vst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; vst->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; #ifdef BYTEBEAT_PRECISE_FONTWIDTH if (!font || measure_font(&font, font_size, &font_width, &font_height, &face, &libft)) { av_log(s, AV_LOG_WARNING, "Failed to determine real font size, using a guess\n"); } #else av_log(s, AV_LOG_WARNING, "Font size will be guessed\n"); #endif if (bb->tw < 0 || bb->th < 0 || font_width < 0 || font_height < 0 || (double)bb->tw*bb->th*font_width*font_height >= (double)SIZE_MAX + 1) { vst->codecpar->height = bb->vw = 0; vst->codecpar->width = bb->vh = 0; } else { vst->codecpar->height = bb->vh = font_height * bb->th; vst->codecpar->width = bb->vw = font_width * bb->tw; } av_log(s, AV_LOG_INFO, "Dimensions: %ix%i\n", bb->vw, bb->vh); vst->codecpar->format = AV_PIX_FMT_GRAY8; vst->codecpar->framerate = (AVRational){ 0, 1 }; bb->next_video_pos = 0; sst->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; sst->codecpar->codec_id = AV_CODEC_ID_ASS; sst->disposition = AV_DISPOSITION_FORCED | AV_DISPOSITION_DEFAULT; av_log(s, AV_LOG_INFO, "Using font: %s %fx%f\n", font ? font : "monospace", font_width, font_height); av_bprint_init(&sbuf, 0, -1); av_bprintf(&sbuf, "[Script Info]\r\n" "ScriptType: v4.00+\r\n" "ScaledBorderAndShadow: yes\r\n" "YCbCr Matrix: None\r\n" "PlayResX: %i\r\n" "PlayResY: %i\r\n" "WrapStyle: 2\r\n" "\r\n" "[V4+ Styles]\r\n" "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, " "Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, " "Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\n" "Style: Default,%s,%i,&HFFFFFFF,&HFF000000,&H00FF0000,&H0000FF00,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1\r\n" "\r\n", bb->vw, bb->vh, font ? font : "monospace", font_size); #ifdef BYTEBEAT_PRECISE_FONTWIDTH if (face) { av_bprintf(&sbuf, "[Fonts]\r\n" "fontname: font.ttf\r\n"); font_consume(face, libft, &sbuf); } #endif av_bprintf(&sbuf, "[Events]\r\n" "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n"); if (font) av_freep(&font); if ((clearbuf = !av_bprint_is_complete(&sbuf))) { av_bprint_clear(&sbuf); } if (clearbuf || av_bprint_finalize(&sbuf, &sst_data)) { read_close_bytebeat(s); return AVERROR(ENOMEM); } sst->codecpar->extradata = sst_data; sst->codecpar->extradata_size = sbuf.size; } return 0; } static int read_packet_bytebeat(AVFormatContext *s, AVPacket *pkt) { BBContext *bb = s->priv_data; bytebeat_Pos pos; size_t len; int ret; if (!bb->state) return AVERROR_INVALIDDATA; pos.type = BYTEBEAT_POSTYPE_SECS; bytebeat_get_pos(bb->state, &pos); if (bb->has_t && (bb->pkt ? (double)bb->pkt->sample_position / bb->sample_rate : pos.seconds) >= bb->next_video_pos) { if (!bb->video_pkt) { bb->video_pkt = av_packet_alloc(); if (!bb->video_pkt) return AVERROR(ENOMEM); if ((ret = av_new_packet(bb->video_pkt, (size_t)bb->vw*bb->vh))) { av_packet_free(&bb->video_pkt); return ret; } for (char *pos = bb->video_pkt->data; pos < (char*)bb->video_pkt->data + (size_t)bb->vw*bb->vh; pos++) *pos = 0; } if ((ret = av_packet_ref(pkt, bb->video_pkt))) return ret; if (bb->next_video_pos >= 0 && bb->next_video_pos < ((double)INT64_MAX + 1) / AV_TIME_BASE) pkt->pts = llrint(bb->next_video_pos * AV_TIME_BASE); pkt->duration = 1./100. * AV_TIME_BASE; if (bb->duration >= 0 && bb->duration < ((double)INT64_MAX + 1) / AV_TIME_BASE) { long long duration = llrint(bb->duration*AV_TIME_BASE) - pkt->pts; if (pkt->duration > duration) pkt->duration = duration; } if (pkt->duration > 0) { bb->next_video_pos += (double)pkt->duration / AV_TIME_BASE; pkt->stream_index = 1; return 0; } } if (!bb->pkt) { bb->pkt = bytebeat_read(bb->state); bb->subi = 0; bb->subn = 0; bb->subc = 0; } if (!bb->pkt) return AVERROR_EOF; if (bb->pkt) { size_t sublen; int sret = 0, subbed = false; double ppos; if (bb->has_t) { bytebeat_TideoFrame *tpkt = bytebeat_pkt_get_tideo(bb->pkt, &sublen); while (sublen - bb->subi > 0) { char *data; size_t i = bb->subi++; long long beginp; AVBPrint sbuf; subbed = true; av_bprint_init(&sbuf, 0, -1); if (bb->lowest_sst_pts == -1) bb->lowest_sst_pts = tpkt[i].position; beginp = tpkt[i].position - bb->lowest_sst_pts; av_bprintf(&sbuf, "%lld,0,Default,,0,0,0,,{\\pos(0,%i)}", beginp*bb->th + bb->subn, bb->subn*bb->font_size); for (char *c = tpkt[i].text + bb->subc; *c != 0; c++) { if (*c == '{' || *c == '}' || *c == '\\') av_bprintf(&sbuf, "\\%c", *c); else if (*c == '\n') { bb->subn++; if (bb->subn < bb->th) bb->subi--; bb->subc = c - tpkt[i].text + 1; break; } else av_bprint_chars(&sbuf, *c, 1); } if (bb->subi != i) { bb->subc = 0; bb->subn = 0; } if ((sret = !av_bprint_is_complete(&sbuf))) { av_bprint_clear(&sbuf); break; } if ((sret = av_bprint_finalize(&sbuf, &data))) { break; } if ((sret = av_packet_from_data(pkt, data, sbuf.len))) { av_freep(&data); break; } pkt->size = sbuf.len; pkt->pts = tpkt[i].position; pkt->duration = tpkt[i].duration; pkt->stream_index = 2; break; } if (subbed) return sret; } len = bb->channel_count * bb->pkt->sample_count * sizeof(float); if ((ret = av_new_packet(pkt, len))) { return ret; } memcpy(pkt->data, bb->pkt->data, len); pkt->size = len; ppos = (double)bb->pkt->sample_position / bb->sample_rate; if (ppos >= 0 && ppos < ((double)INT64_MAX + 1) / AV_TIME_BASE) pkt->pts = llrint(ppos * AV_TIME_BASE); bytebeat_pkt_free(bb->pkt); bb->pkt = NULL; pkt->stream_index = 0; } return 0; } static int read_close_bytebeat(AVFormatContext *s) { BBContext *bb = s->priv_data; if (bb->state) { bytebeat_close(bb->state); bb->state = NULL; } if (bb->opts) { av_freep(&bb->opts->code); free(bb->opts); bb->opts = NULL; } if (bb->pkt) { bytebeat_pkt_free(bb->pkt); bb->pkt = NULL; } if (bb->video_pkt) { av_packet_free(&bb->video_pkt); } return 0; } static int read_seek_bytebeat(AVFormatContext *s, int stream_idx, int64_t ts, int flags) { BBContext *bb = s->priv_data; bytebeat_Pos pos; double old_pos_s; pos.type = BYTEBEAT_POSTYPE_SECS; if (!bb->state) return AVERROR_INVALIDDATA; bytebeat_get_pos(bb->state, &pos); old_pos_s = pos.seconds; pos.seconds = (double)ts/AV_TIME_BASE; if (pos.seconds < 0) pos.seconds = 0; bb->next_video_pos = pos.seconds; if (bb->pkt) { bytebeat_pkt_free(bb->pkt); bb->pkt = NULL; } if (bytebeat_set_pos(bb->state, &pos)) { // seeking is NOT possible. // for backwards seeking, just reopen the song as a last resort // forwards, simply fail and let the player figure it out if (pos.seconds < old_pos_s) { av_log(bb, AV_LOG_WARNING, "seeking backwards is not possible, rebooting song (%f -> %f)\n", old_pos_s, pos.seconds); bytebeat_close(bb->state); bb->state = bytebeat_open(bb->opts); if (!bb->state) return AVERROR_INVALIDDATA; return 0; } av_log(bb, AV_LOG_WARNING, "seeking forwards is not possible (%f -> %f)\n", old_pos_s, pos.seconds); return AVERROR_UNKNOWN; } return 0; } static int read_probe_bytebeat(const AVProbeData *p) { enum bytebeat_ProbeResult result; if (!p->buf) return 0; result = bytebeat_probe(p->buf, p->buf_size); if (result == BYTEBEAT_PROBE_GOOD) return (AVPROBE_SCORE_MAX * 3) / 4 + 1; if (result == BYTEBEAT_PROBE_UNSURE) return AVPROBE_SCORE_RETRY; return 0; } const FFInputFormat ff_libbytebeat_demuxer = { .p.name = "libbytebeat", .p.long_name = NULL_IF_CONFIG_SMALL("Generative audio (libbytebeat)"), .p.priv_class = &class_bytebeat, .p.extensions = "js", .priv_data_size = sizeof(BBContext), .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP, .read_probe = read_probe_bytebeat, .read_header = read_header_bytebeat, .read_packet = read_packet_bytebeat, .read_close = read_close_bytebeat, .read_seek = read_seek_bytebeat, };