FFmpeg/libavformat/vapoursynth.c
Andreas Rheinhardt 790f793844 avutil/common: Don't auto-include mem.h
There are lots of files that don't need it: The number of object
files that actually need it went down from 2011 to 884 here.

Keep it for external users in order to not cause breakages.

Also improve the other headers a bit while just at it.

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2024-03-31 00:08:43 +01:00

497 lines
14 KiB
C

/*
* 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
* VapourSynth demuxer
*
* Synthesizes vapour (?)
*/
#include <limits.h>
#include <VapourSynth.h>
#include <VSScript.h>
#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/eval.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"
#include "libavutil/mem.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "avformat.h"
#include "demux.h"
#include "internal.h"
struct VSState {
VSScript *vss;
};
typedef struct VSContext {
const AVClass *class;
AVBufferRef *vss_state;
const VSAPI *vsapi;
VSCore *vscore;
VSNodeRef *outnode;
int is_cfr;
int current_frame;
int c_order[4];
/* options */
int64_t max_script_size;
} VSContext;
#define OFFSET(x) offsetof(VSContext, x)
#define A AV_OPT_FLAG_AUDIO_PARAM
#define D AV_OPT_FLAG_DECODING_PARAM
static const AVOption options[] = {
{"max_script_size", "set max file size supported (in bytes)", OFFSET(max_script_size), AV_OPT_TYPE_INT64, {.i64 = 1 * 1024 * 1024}, 0, SIZE_MAX - 1, A|D},
{NULL}
};
static void free_vss_state(void *opaque, uint8_t *data)
{
struct VSState *vss = opaque;
if (vss->vss) {
vsscript_freeScript(vss->vss);
vsscript_finalize();
}
}
static av_cold int read_close_vs(AVFormatContext *s)
{
VSContext *vs = s->priv_data;
if (vs->outnode)
vs->vsapi->freeNode(vs->outnode);
av_buffer_unref(&vs->vss_state);
vs->vsapi = NULL;
vs->vscore = NULL;
vs->outnode = NULL;
return 0;
}
static av_cold int is_native_endian(enum AVPixelFormat pixfmt)
{
enum AVPixelFormat other = av_pix_fmt_swap_endianness(pixfmt);
const AVPixFmtDescriptor *pd;
if (other == AV_PIX_FMT_NONE || other == pixfmt)
return 1; // not affected by byte order
pd = av_pix_fmt_desc_get(pixfmt);
return pd && (!!HAVE_BIGENDIAN == !!(pd->flags & AV_PIX_FMT_FLAG_BE));
}
static av_cold enum AVPixelFormat match_pixfmt(const VSFormat *vsf, int c_order[4])
{
static const int yuv_order[4] = {0, 1, 2, 0};
static const int rgb_order[4] = {1, 2, 0, 0};
const AVPixFmtDescriptor *pd;
for (pd = av_pix_fmt_desc_next(NULL); pd; pd = av_pix_fmt_desc_next(pd)) {
int is_rgb, is_yuv, i;
const int *order;
enum AVPixelFormat pixfmt;
pixfmt = av_pix_fmt_desc_get_id(pd);
if (pd->flags & (AV_PIX_FMT_FLAG_BAYER | AV_PIX_FMT_FLAG_ALPHA |
AV_PIX_FMT_FLAG_HWACCEL | AV_PIX_FMT_FLAG_BITSTREAM |
AV_PIX_FMT_FLAG_XYZ))
continue;
if (pd->log2_chroma_w != vsf->subSamplingW ||
pd->log2_chroma_h != vsf->subSamplingH)
continue;
is_rgb = vsf->colorFamily == cmRGB;
if (is_rgb != !!(pd->flags & AV_PIX_FMT_FLAG_RGB))
continue;
is_yuv = vsf->colorFamily == cmYUV ||
vsf->colorFamily == cmYCoCg ||
vsf->colorFamily == cmGray;
if (!is_rgb && !is_yuv)
continue;
if (vsf->sampleType != ((pd->flags & AV_PIX_FMT_FLAG_FLOAT) ? stFloat : stInteger))
continue;
if (av_pix_fmt_count_planes(pixfmt) != vsf->numPlanes)
continue;
if (!is_native_endian(pixfmt))
continue;
order = is_yuv ? yuv_order : rgb_order;
for (i = 0; i < pd->nb_components; i++) {
const AVComponentDescriptor *c = &pd->comp[i];
if (order[c->plane] != i ||
c->offset != 0 || c->shift != 0 ||
c->step != vsf->bytesPerSample ||
c->depth != vsf->bitsPerSample)
goto cont;
}
// Use it.
memcpy(c_order, order, sizeof(int[4]));
return pixfmt;
cont: ;
}
return AV_PIX_FMT_NONE;
}
static av_cold int read_header_vs(AVFormatContext *s)
{
AVStream *st;
AVIOContext *pb = s->pb;
VSContext *vs = s->priv_data;
int64_t sz = avio_size(pb);
char *buf = NULL;
char dummy;
const VSVideoInfo *info;
struct VSState *vss_state;
int err = 0;
vss_state = av_mallocz(sizeof(*vss_state));
if (!vss_state) {
err = AVERROR(ENOMEM);
goto done;
}
vs->vss_state = av_buffer_create(NULL, 0, free_vss_state, vss_state, 0);
if (!vs->vss_state) {
err = AVERROR(ENOMEM);
av_free(vss_state);
goto done;
}
if (!vsscript_init()) {
av_log(s, AV_LOG_ERROR, "Failed to initialize VSScript (possibly PYTHONPATH not set).\n");
err = AVERROR_EXTERNAL;
goto done;
}
if (vsscript_createScript(&vss_state->vss)) {
av_log(s, AV_LOG_ERROR, "Failed to create script instance.\n");
err = AVERROR_EXTERNAL;
vsscript_finalize();
goto done;
}
if (sz < 0 || sz > vs->max_script_size) {
if (sz < 0)
av_log(s, AV_LOG_WARNING, "Could not determine file size\n");
sz = vs->max_script_size;
}
buf = av_malloc(sz + 1);
if (!buf) {
err = AVERROR(ENOMEM);
goto done;
}
sz = avio_read(pb, buf, sz);
if (sz < 0) {
av_log(s, AV_LOG_ERROR, "Could not read script.\n");
err = sz;
goto done;
}
// Data left means our buffer (the max_script_size option) is too small
if (avio_read(pb, &dummy, 1) == 1) {
av_log(s, AV_LOG_ERROR, "File size is larger than max_script_size option "
"value %"PRIi64", consider increasing the max_script_size option\n",
vs->max_script_size);
err = AVERROR_BUFFER_TOO_SMALL;
goto done;
}
buf[sz] = '\0';
if (vsscript_evaluateScript(&vss_state->vss, buf, s->url, 0)) {
const char *msg = vsscript_getError(vss_state->vss);
av_log(s, AV_LOG_ERROR, "Failed to parse script: %s\n", msg ? msg : "(unknown)");
err = AVERROR_EXTERNAL;
goto done;
}
vs->vsapi = vsscript_getVSApi();
vs->vscore = vsscript_getCore(vss_state->vss);
vs->outnode = vsscript_getOutput(vss_state->vss, 0);
if (!vs->outnode) {
av_log(s, AV_LOG_ERROR, "Could not get script output node.\n");
err = AVERROR_EXTERNAL;
goto done;
}
st = avformat_new_stream(s, NULL);
if (!st) {
err = AVERROR(ENOMEM);
goto done;
}
info = vs->vsapi->getVideoInfo(vs->outnode);
if (!info->format || !info->width || !info->height) {
av_log(s, AV_LOG_ERROR, "Non-constant input format not supported.\n");
err = AVERROR_PATCHWELCOME;
goto done;
}
if (info->fpsDen) {
vs->is_cfr = 1;
avpriv_set_pts_info(st, 64, info->fpsDen, info->fpsNum);
st->duration = info->numFrames;
} else {
// VFR. Just set "something".
avpriv_set_pts_info(st, 64, 1, AV_TIME_BASE);
s->ctx_flags |= AVFMTCTX_UNSEEKABLE;
}
st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
st->codecpar->codec_id = AV_CODEC_ID_WRAPPED_AVFRAME;
st->codecpar->width = info->width;
st->codecpar->height = info->height;
st->codecpar->format = match_pixfmt(info->format, vs->c_order);
if (st->codecpar->format == AV_PIX_FMT_NONE) {
av_log(s, AV_LOG_ERROR, "Unsupported VS pixel format %s\n", info->format->name);
err = AVERROR_EXTERNAL;
goto done;
}
av_log(s, AV_LOG_VERBOSE, "VS format %s -> pixfmt %s\n", info->format->name,
av_get_pix_fmt_name(st->codecpar->format));
if (info->format->colorFamily == cmYCoCg)
st->codecpar->color_space = AVCOL_SPC_YCGCO;
done:
av_free(buf);
return err;
}
static void free_frame(void *opaque, uint8_t *data)
{
AVFrame *frame = (AVFrame *)data;
av_frame_free(&frame);
}
static int get_vs_prop_int(AVFormatContext *s, const VSMap *map, const char *name, int def)
{
VSContext *vs = s->priv_data;
int64_t res;
int err = 1;
res = vs->vsapi->propGetInt(map, name, 0, &err);
return err || res < INT_MIN || res > INT_MAX ? def : res;
}
struct vsframe_ref_data {
const VSAPI *vsapi;
const VSFrameRef *frame;
AVBufferRef *vss_state;
};
static void free_vsframe_ref(void *opaque, uint8_t *data)
{
struct vsframe_ref_data *d = opaque;
if (d->frame)
d->vsapi->freeFrame(d->frame);
av_buffer_unref(&d->vss_state);
av_free(d);
}
static int read_packet_vs(AVFormatContext *s, AVPacket *pkt)
{
VSContext *vs = s->priv_data;
AVStream *st = s->streams[0];
AVFrame *frame = NULL;
char vserr[80];
const VSFrameRef *vsframe;
const VSVideoInfo *info = vs->vsapi->getVideoInfo(vs->outnode);
const VSMap *props;
const AVPixFmtDescriptor *desc;
AVBufferRef *vsframe_ref = NULL;
struct vsframe_ref_data *ref_data;
int err = 0;
int i;
if (vs->current_frame >= info->numFrames)
return AVERROR_EOF;
ref_data = av_mallocz(sizeof(*ref_data));
if (!ref_data) {
err = AVERROR(ENOMEM);
goto end;
}
// (the READONLY flag is important because the ref is reused for plane data)
vsframe_ref = av_buffer_create(NULL, 0, free_vsframe_ref, ref_data, AV_BUFFER_FLAG_READONLY);
if (!vsframe_ref) {
err = AVERROR(ENOMEM);
av_free(ref_data);
goto end;
}
vsframe = vs->vsapi->getFrame(vs->current_frame, vs->outnode, vserr, sizeof(vserr));
if (!vsframe) {
av_log(s, AV_LOG_ERROR, "Error getting frame: %s\n", vserr);
err = AVERROR_EXTERNAL;
goto end;
}
ref_data->vsapi = vs->vsapi;
ref_data->frame = vsframe;
ref_data->vss_state = av_buffer_ref(vs->vss_state);
if (!ref_data->vss_state) {
err = AVERROR(ENOMEM);
goto end;
}
props = vs->vsapi->getFramePropsRO(vsframe);
frame = av_frame_alloc();
if (!frame) {
err = AVERROR(ENOMEM);
goto end;
}
frame->format = st->codecpar->format;
frame->width = st->codecpar->width;
frame->height = st->codecpar->height;
frame->colorspace = st->codecpar->color_space;
// Values according to ISO/IEC 14496-10.
frame->colorspace = get_vs_prop_int(s, props, "_Matrix", frame->colorspace);
frame->color_primaries = get_vs_prop_int(s, props, "_Primaries", frame->color_primaries);
frame->color_trc = get_vs_prop_int(s, props, "_Transfer", frame->color_trc);
if (get_vs_prop_int(s, props, "_ColorRange", 1) == 0)
frame->color_range = AVCOL_RANGE_JPEG;
frame->sample_aspect_ratio.num = get_vs_prop_int(s, props, "_SARNum", 0);
frame->sample_aspect_ratio.den = get_vs_prop_int(s, props, "_SARDen", 1);
av_assert0(vs->vsapi->getFrameWidth(vsframe, 0) == frame->width);
av_assert0(vs->vsapi->getFrameHeight(vsframe, 0) == frame->height);
desc = av_pix_fmt_desc_get(frame->format);
for (i = 0; i < info->format->numPlanes; i++) {
int p = vs->c_order[i];
ptrdiff_t plane_h = frame->height;
frame->data[i] = (void *)vs->vsapi->getReadPtr(vsframe, p);
frame->linesize[i] = vs->vsapi->getStride(vsframe, p);
frame->buf[i] = av_buffer_ref(vsframe_ref);
if (!frame->buf[i]) {
err = AVERROR(ENOMEM);
goto end;
}
// Each plane needs an AVBufferRef that indicates the correct plane
// memory range. VapourSynth doesn't even give us the memory range,
// so make up a bad guess to make FFmpeg happy (even if almost nothing
// checks the memory range).
if (i == 1 || i == 2)
plane_h = AV_CEIL_RSHIFT(plane_h, desc->log2_chroma_h);
frame->buf[i]->data = frame->data[i];
frame->buf[i]->size = frame->linesize[i] * plane_h;
}
pkt->buf = av_buffer_create((uint8_t*)frame, sizeof(*frame),
free_frame, NULL, 0);
if (!pkt->buf) {
err = AVERROR(ENOMEM);
goto end;
}
frame = NULL; // pkt owns it now
pkt->data = pkt->buf->data;
pkt->size = pkt->buf->size;
pkt->flags |= AV_PKT_FLAG_TRUSTED;
if (vs->is_cfr)
pkt->pts = vs->current_frame;
vs->current_frame++;
end:
av_frame_free(&frame);
av_buffer_unref(&vsframe_ref);
return err;
}
static int read_seek_vs(AVFormatContext *s, int stream_idx, int64_t ts, int flags)
{
VSContext *vs = s->priv_data;
if (!vs->is_cfr)
return AVERROR(ENOSYS);
vs->current_frame = FFMIN(FFMAX(0, ts), s->streams[0]->duration);
return 0;
}
static av_cold int probe_vs(const AVProbeData *p)
{
// Explicitly do not support this. VS scripts are written in Python, and
// can run arbitrary code on the user's system.
return 0;
}
static const AVClass class_vs = {
.class_name = "VapourSynth demuxer",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
const FFInputFormat ff_vapoursynth_demuxer = {
.p.name = "vapoursynth",
.p.long_name = NULL_IF_CONFIG_SMALL("VapourSynth demuxer"),
.p.priv_class = &class_vs,
.priv_data_size = sizeof(VSContext),
.flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
.read_probe = probe_vs,
.read_header = read_header_vs,
.read_packet = read_packet_vs,
.read_close = read_close_vs,
.read_seek = read_seek_vs,
};