/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "oiiodecoder.h" #include #include #include #include #include #include "common/define.h" #include "common/oiioutils.h" #include "config/config.h" #include "core.h" namespace olive { QStringList OIIODecoder::supported_formats_; OIIODecoder::OIIODecoder() : image_(nullptr), buffer_(nullptr) { } QString OIIODecoder::id() const { return QStringLiteral("oiio"); } FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const { Q_UNUSED(cancelled) FootageDescription desc(id()); // Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying // to open a file that it can't if it's given one if (!FileTypeIsSupported(filename)) { return desc; } std::string std_filename = filename.toStdString(); auto in = OIIO::ImageInput::open(std_filename); if (!in) { return desc; } // Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle // it better if (!strcmp(in->format_name(), "FFmpeg movie")) { return desc; } bool stream_enabled = true; for (int i=0; in->seek_subimage(i, 0); i++) { VideoParams video_params; OIIO::ImageSpec spec = in->spec(); video_params.set_stream_index(i); video_params.set_width(spec.width); video_params.set_height(spec.height); video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype))); video_params.set_channel_count(spec.nchannels); video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec)); video_params.set_video_type(VideoParams::kVideoTypeStill); if (i > 1) { // This is a multilayer image and this image might have an offset OIIO::ImageSpec root_spec = in->spec(0); float norm_x = spec.x + float(spec.width)*0.5f - float(root_spec.width)*0.5f; float norm_y = spec.y + float(spec.height)*0.5f - float(root_spec.height)*0.5f; video_params.set_x(norm_x); video_params.set_y(norm_y); } // By default, only enable the first subimage (presumably the combined image). Later we will // ask the user if they want to enable the layers instead. video_params.set_enabled(stream_enabled); stream_enabled = false; // OIIO automatically premultiplies alpha // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this // likely reduces the fidelity? video_params.set_premultiplied_alpha(true); desc.AddVideoStream(video_params); } // If we're here, we have a successful image open in->close(); return desc; } bool OIIODecoder::OpenInternal() { // If we can open the filename provided, assume everything is working return OpenImageHandler(stream().filename(), stream().stream()); } FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ÷r, const QAtomicInt *cancelled) { Q_UNUSED(timecode) Q_UNUSED(cancelled) FramePtr frame = Frame::Create(); VideoParams vp(buffer_->spec().width, buffer_->spec().height, pix_fmt_, channel_count_, OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()), VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us? divider.divider); frame->set_video_params(vp); frame->allocate(); if (divider.divider == 1) { OIIOUtils::BufferToFrame(buffer_, frame.get()); } else { // Will need to resize the image OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format)); if (!OIIO::ImageBufAlgo::resample(dst, *buffer_)) { qWarning() << "OIIO resize failed"; } OIIOUtils::BufferToFrame(&dst, frame.get()); } return frame; } void OIIODecoder::CloseInternal() { CloseImageHandle(); } bool OIIODecoder::FileTypeIsSupported(const QString& fn) { // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG) // will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's // "extension_list" attribute and match it with the extension of the file. // Check if we've created the supported formats list, create it if not if (supported_formats_.isEmpty()) { QStringList extension_list = QString::fromStdString(OIIO::get_string_attribute("extension_list")).split(';'); // The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions foreach (const QString& ext, extension_list) { QStringList format_and_ext = ext.split(':'); supported_formats_.append(format_and_ext.at(1).split(',')); } } if (!supported_formats_.contains(QFileInfo(fn).suffix(), Qt::CaseInsensitive)) { return false; } return true; } bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage) { image_ = OIIO::ImageInput::open(fn.toStdString()); if (!image_) { return false; } if (!image_->seek_subimage(subimage, 0)) { return false; } // Check if we can work with this pixel format const OIIO::ImageSpec& spec = image_->spec(); // Store channel count channel_count_ = spec.nchannels; // We use RGBA frames because that tends to be the native format of GPUs pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast(spec.format.basetype)); if (pix_fmt_ == VideoParams::kFormatInvalid) { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; return false; } OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_); if (type == OIIO::TypeDesc::UNKNOWN) { qCritical() << "Failed to determine appropriate OIIO basetype from native format"; return false; } buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type), OIIO::InitializePixels::No); image_->read_image(type, buffer_->localpixels()); return true; } void OIIODecoder::CloseImageHandle() { if (image_) { image_->close(); image_ = nullptr; } if (buffer_) { delete buffer_; buffer_ = nullptr; } } }