248 lines
7.2 KiB
C++
248 lines
7.2 KiB
C++
/***
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#include "oiiodecoder.h"
|
|
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QMessageBox>
|
|
|
|
#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)
|
|
{
|
|
}
|
|
|
|
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++) {
|
|
OIIO::ImageSpec spec = in->spec();
|
|
|
|
VideoParams video_params = GetVideoParamsFromImageSpec(spec);
|
|
|
|
video_params.set_stream_index(i);
|
|
|
|
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());
|
|
}
|
|
|
|
bool OIIODecoder::RetrieveVideoInternal(TexturePtr destination, const rational &timecode, const RetrieveVideoParams ¶ms, const QAtomicInt *cancelled)
|
|
{
|
|
Q_UNUSED(timecode)
|
|
Q_UNUSED(cancelled)
|
|
|
|
VideoParams vp = GetVideoParamsFromImageSpec(image_->spec());
|
|
vp.set_divider(params.divider);
|
|
|
|
if (!buffer_.is_allocated() || last_params_ != params) {
|
|
last_params_ = params;
|
|
|
|
buffer_.destroy();
|
|
buffer_.set_video_params(vp);
|
|
buffer_.allocate();
|
|
|
|
if (params.divider == 1) {
|
|
// Just upload straight to the buffer
|
|
image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes());
|
|
} else {
|
|
OIIO::ImageBuf buf(image_->spec());
|
|
image_->read_image(image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride());
|
|
|
|
// Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here)
|
|
int px_sz = vp.GetBytesPerPixel();
|
|
for (int dst_y=0; dst_y<buffer_.height(); dst_y++) {
|
|
int src_y = dst_y * buf.spec().height / buffer_.height();
|
|
|
|
for (int dst_x=0; dst_x<buffer_.width(); dst_x++) {
|
|
int src_x = dst_x * buf.spec().width / buffer_.width();
|
|
memcpy(buffer_.data() + buffer_.linesize_bytes() * dst_y + px_sz * dst_x,
|
|
static_cast<uint8_t*>(buf.localpixels()) + buf.scanline_stride() * src_y + px_sz * src_x,
|
|
px_sz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
destination->Upload(buffer_.data(), buffer_.linesize_pixels());
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
|
|
// We use RGBA frames because that tends to be the native format of GPUs
|
|
pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype));
|
|
|
|
if (pix_fmt_ == VideoParams::kFormatInvalid) {
|
|
qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format";
|
|
return false;
|
|
}
|
|
|
|
oiio_pix_fmt_ = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
|
|
|
|
if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) {
|
|
qCritical() << "Failed to determine appropriate OIIO basetype from native format";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OIIODecoder::CloseImageHandle()
|
|
{
|
|
if (image_) {
|
|
image_->close();
|
|
image_ = nullptr;
|
|
}
|
|
|
|
buffer_.destroy();
|
|
}
|
|
|
|
VideoParams OIIODecoder::GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec)
|
|
{
|
|
VideoParams video_params;
|
|
|
|
video_params.set_width(spec.width);
|
|
video_params.set_height(spec.height);
|
|
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(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);
|
|
|
|
return video_params;
|
|
}
|
|
|
|
}
|