/*
 * 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 <gsbarbieri@yahoo.com.br>
 * 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 <bytebeat.h>
#include <stdlib.h>

#include "libavutil/avstring.h"
#include "libavutil/bprint.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/opt.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,
};

#ifdef CONFIG_LIBFREETYPE
#define BYTEBEAT_PRECISE_FONTWIDTH 1

// modified code from avfilter/vf_drawtext

#include <ft2build.h>
#include <freetype/tttables.h>
#include <freetype/freetype.h>

#if CONFIG_LIBFONTCONFIG
#include <fontconfig/fontconfig.h>
#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;
#ifdef 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_MIME + 1;
    if (result == BYTEBEAT_PROBE_UNSURE)
        return AVPROBE_SCORE_RETRY;
    return 0;
}

const AVInputFormat ff_libbytebeat_demuxer = {
    .name         = "libbytebeat",
    .long_name    = NULL_IF_CONFIG_SMALL("Generative audio (libbytebeat)"),
    .priv_class   = &class_bytebeat,
    .extensions   = "js",
    .priv_data_size = sizeof(BBContext),
    .flags_internal = FF_FMT_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,
};
