From de5955b403c9591f2495acec91ceddb7f0c7fc1b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 2 Mar 2020 14:06:44 +1100 Subject: [PATCH] oiiodecoder: implemented rudimentary image sequence support --- app/codec/ffmpeg/ffmpegdecoder.cpp | 5 +- app/codec/oiio/oiiodecoder.cpp | 296 ++++++++++++++++++++--------- app/codec/oiio/oiiodecoder.h | 12 +- app/core.cpp | 15 ++ app/core.h | 2 + 5 files changed, 229 insertions(+), 101 deletions(-) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 961f76445..9e18d49dc 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -70,10 +70,7 @@ bool FFmpegDecoder::Open() return true; } - if (!stream()) { - Error(QStringLiteral("Tried to open a decoder with no footage stream set")); - return false; - } + Q_ASSERT(stream()); int error_code; diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index 1e0e5562b..86d626659 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -22,9 +22,13 @@ #include #include +#include #include +#include #include "common/define.h" +#include "config/config.h" +#include "core.h" QStringList OIIODecoder::supported_formats_; @@ -40,6 +44,164 @@ QString OIIODecoder::id() } bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) +{ + if (!FileTypeIsSupported(f->filename())) { + return false; + } + + std::string std_filename = f->filename().toStdString(); + + auto in = OIIO::ImageInput::open(std_filename); + + if (!in) { + return false; + } + + if (!strcmp(in->format_name(), "FFmpeg movie")) { + // If this is FFmpeg via OIIO, fall-through to our native FFmpeg decoder + return false; + } + + // Heuristically determine whether this file is part of an image sequence or not + if (GetImageSequenceDigitCount(f->filename()) > 0) { + // We need user feedback here and since UI must occur in the UI thread (and we could be in any thread), we defer + // to the Core which will definitely be in the UI thread and block here until we get an answer from the user + QMetaObject::invokeMethod(Core::instance(), + "ConfirmImageSequence", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, is_sequence_), + Q_ARG(QString, f->filename())); + } else { + is_sequence_ = false; + } + + ImageStreamPtr image_stream; + + if (is_sequence_) { + VideoStreamPtr video_stream = std::make_shared(); + image_stream = video_stream; + + rational default_timebase = Config::Current()["DefaultSequenceFrameRate"].value(); + video_stream->set_timebase(default_timebase); + video_stream->set_frame_rate(default_timebase.flipped()); + + // FIXME: Get actual duration + video_stream->set_duration(200); + } else { + image_stream = std::make_shared(); + } + + image_stream->set_width(in->spec().width); + image_stream->set_height(in->spec().height); + + // Images will always have just one stream + image_stream->set_index(0); + + // OIIO automatically premultiplies alpha + // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this likely reduces the + // fidelity? + image_stream->set_premultiplied_alpha(true); + + // Get stats for this image and dump them into the Footage file + f->add_stream(image_stream); + + // If we're here, we have a successful image open + in->close(); + +#if OIIO_VERSION < 10903 + OIIO::ImageInput::destroy(in_); +#endif + + return true; +} + +bool OIIODecoder::Open() +{ + QMutexLocker locker(&mutex_); + + Q_ASSERT(stream()); + + if (stream()->type() != Stream::kVideo && !OpenImageHandler(stream()->footage()->filename())) { + return false; + } + + open_ = true; + + return true; +} + +Decoder::RetrieveState OIIODecoder::GetRetrieveState(const rational &time) +{ + QMutexLocker locker(&mutex_); + + if (!open_) { + return kFailedToOpen; + } + + return kReady; +} + +FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) +{ + QMutexLocker locker(&mutex_); + + if (!open_) { + return nullptr; + } + + if (stream()->type() == Stream::kVideo) { + int64_t ts = Timecode::time_to_timestamp(timecode, stream()->timebase()); + + // FIXME: Many sequences start at 1 but not all + ts++; + + OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), ts)); + + if (!image_) { + return nullptr; + } + } + + FramePtr frame = Frame::Create(); + + frame->set_width(buffer_->spec().width / divider); + frame->set_height(buffer_->spec().height / divider); + frame->set_format(pix_fmt_); + frame->allocate(); + + OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format), frame->data()); + + // FIXME: This function is really slow, but is necessary to do. Is there a faster way to do it since it only needs to + // be draft quality? + if (!OIIO::ImageBufAlgo::resize(dst, *buffer_)) { + qWarning() << "OIIO resize failed"; + } + + if (stream()->type() == Stream::kVideo) { + CloseImageHandle(); + } + + return frame; +} + +void OIIODecoder::Close() +{ + QMutexLocker locker(&mutex_); + + CloseImageHandle(); +} + +bool OIIODecoder::SupportsVideo() +{ + return true; +} + +QString OIIODecoder::GetIndexFilename() +{ + return QString(); +} + +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 @@ -57,54 +219,48 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) } } - // - QFileInfo file_info(f->filename()); - - if (!supported_formats_.contains(file_info.completeSuffix(), Qt::CaseInsensitive)) { + if (!supported_formats_.contains(QFileInfo(fn).completeSuffix(), Qt::CaseInsensitive)) { return false; } - std::string std_filename = f->filename().toStdString(); - - auto in = OIIO::ImageInput::open(std_filename); - - if (!in) { - return false; - } - - if (!strcmp(in->format_name(), "FFmpeg movie")) { - // If this is FFmpeg via OIIO, fall-through to our native FFmpeg decoder - return false; - } - - // Get stats for this image and dump them into the Footage file - const OIIO::ImageSpec& spec = in->spec(); - - ImageStreamPtr image_stream = std::make_shared(); - image_stream->set_width(spec.width); - image_stream->set_height(spec.height); - - // Images will always have just one stream - image_stream->set_index(0); - - // OIIO automatically premultiplies alpha - // FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this likely reduces the - // fidelity? - image_stream->set_premultiplied_alpha(true); - - f->add_stream(image_stream); - - // If we're here, we have a successful image open - in->close(); - return true; } -bool OIIODecoder::Open() +int OIIODecoder::GetImageSequenceDigitCount(const QString &filename) { - QMutexLocker locker(&mutex_); + QString basename = QFileInfo(filename).baseName(); - image_ = OIIO::ImageInput::open(stream()->footage()->filename().toStdString()); + // See if basename contains a number at the end + int digit_count = 0; + + for (int i=basename.size()-1;i>=0;i--) { + if (basename.at(i).isDigit()) { + digit_count++; + } else { + break; + } + } + + return digit_count; +} + +QString OIIODecoder::TransformImageSequenceFileName(const QString &filename, const int64_t& number) +{ + int digit_count = GetImageSequenceDigitCount(filename); + + QFileInfo file_info(filename); + + QString original_basename = file_info.baseName(); + + QString new_basename = original_basename.left(original_basename.size() - digit_count) + .append(QStringLiteral("%1").arg(number, digit_count, 10, QChar('0'))); + + return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename)); +} + +bool OIIODecoder::OpenImageHandler(const QString &fn) +{ + image_ = OIIO::ImageInput::open(fn.toStdString()); if (!image_) { return false; @@ -113,9 +269,6 @@ bool OIIODecoder::Open() // Check if we can work with this pixel format const OIIO::ImageSpec& spec = image_->spec(); - width_ = spec.width; - height_ = spec.height; - is_rgba_ = (spec.nchannels == kRGBAChannels); // Weirdly, switch statement doesn't work correctly here @@ -142,50 +295,11 @@ bool OIIODecoder::Open() #endif image_->read_image(type_, buffer_->localpixels()); - open_ = true; - return true; } -Decoder::RetrieveState OIIODecoder::GetRetrieveState(const rational &time) +void OIIODecoder::CloseImageHandle() { - QMutexLocker locker(&mutex_); - - if (!open_) { - return kFailedToOpen; - } - - return kReady; -} - -FramePtr OIIODecoder::RetrieveVideo(const rational &timecode, const int& divider) -{ - QMutexLocker locker(&mutex_); - - if (!open_) { - return nullptr; - } - - FramePtr frame = Frame::Create(); - - frame->set_width(width_ / divider); - frame->set_height(height_ / divider); - frame->set_format(pix_fmt_); - frame->allocate(); - - OIIO::ImageBuf dst(OIIO::ImageSpec(frame->width(), frame->height(), buffer_->spec().nchannels, buffer_->spec().format), frame->data()); - - if (!OIIO::ImageBufAlgo::resize(dst, *buffer_)) { - qWarning() << "OIIO resize failed"; - } - - return frame; -} - -void OIIODecoder::Close() -{ - QMutexLocker locker(&mutex_); - if (image_) { image_->close(); #if OIIO_VERSION < 10903 @@ -194,16 +308,8 @@ void OIIODecoder::Close() image_ = nullptr; } - delete buffer_; - buffer_ = nullptr; -} - -bool OIIODecoder::SupportsVideo() -{ - return true; -} - -QString OIIODecoder::GetIndexFilename() -{ - return QString(); + if (buffer_) { + delete buffer_; + buffer_ = nullptr; + } } diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index be6e5f21e..f4eff4db6 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -29,6 +29,7 @@ class OIIODecoder : public Decoder { + Q_OBJECT public: OIIODecoder(); @@ -51,10 +52,15 @@ private: #else std::unique_ptr image_; #endif + static bool FileTypeIsSupported(const QString& fn); - int width_; + static int GetImageSequenceDigitCount(const QString& filename); - int height_; + static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number); + + bool OpenImageHandler(const QString& fn); + + void CloseImageHandle(); PixelFormat::Format pix_fmt_; @@ -62,6 +68,8 @@ private: bool is_rgba_; + bool is_sequence_; + OIIO::ImageBuf* buffer_; static QStringList supported_formats_; diff --git a/app/core.cpp b/app/core.cpp index ac88b58a1..f0c522406 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -349,6 +349,21 @@ void Core::ImportTaskComplete(QUndoCommand *command) undo_stack_.pushIfHasChildren(command); } +bool Core::ConfirmImageSequence(const QString& filename) +{ + QMessageBox mb; + + mb.setIcon(QMessageBox::Question); + mb.setWindowTitle(tr("Possible image sequence detected")); + mb.setText(tr("The file '%1' looks like it might be part of an image " + "sequence. Would you like to import it as such?").arg(filename)); + + mb.addButton(QMessageBox::Yes); + mb.addButton(QMessageBox::No); + + return (mb.exec() == QMessageBox::Yes); +} + void Core::DeclareTypesForQt() { qRegisterMetaType(); diff --git a/app/core.h b/app/core.h index bb6093e0e..a15f13200 100644 --- a/app/core.h +++ b/app/core.h @@ -360,6 +360,8 @@ private slots: void ImportTaskComplete(QUndoCommand* command); + bool ConfirmImageSequence(const QString &filename); + }; #endif // CORE_H