817 lines
25 KiB
C
817 lines
25 KiB
C
/*
|
|
* 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 "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,
|
|
};
|
|
|
|
#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_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,
|
|
};
|