|
- /*
- * 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/imgutils.h"
- #include "libavutil/opt.h"
- #include "libavutil/pixdesc.h"
- #include "avformat.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))
- 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 (strncmp(pd->name, "xyz", 3) == 0)
- 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);
- if (err < 0)
- read_close_vs(s);
- 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,
- };
-
- AVInputFormat ff_vapoursynth_demuxer = {
- .name = "vapoursynth",
- .long_name = NULL_IF_CONFIG_SMALL("VapourSynth demuxer"),
- .priv_data_size = sizeof(VSContext),
- .read_probe = probe_vs,
- .read_header = read_header_vs,
- .read_packet = read_packet_vs,
- .read_close = read_close_vs,
- .read_seek = read_seek_vs,
- .priv_class = &class_vs,
- };
|