From d7c5ac4b44d8c9d1cac9a484ee024f4bc7cd979d Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 15 Feb 2021 21:31:49 +1100 Subject: [PATCH] implement entire project in nodes Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly. --- app/codec/decoder.cpp | 124 ++-- app/codec/decoder.h | 102 ++- app/codec/ffmpeg/ffmpegdecoder.cpp | 246 +++---- app/codec/ffmpeg/ffmpegdecoder.h | 7 +- app/codec/footagemeta.h | 10 + app/codec/oiio/oiiodecoder.cpp | 72 +- app/codec/oiio/oiiodecoder.h | 4 +- app/common/filefunctions.cpp | 2 +- app/common/xmlutils.h | 10 +- app/config/config.cpp | 1 + app/core.cpp | 56 +- app/core.h | 4 +- app/dialog/CMakeLists.txt | 2 +- .../CMakeLists.txt | 8 +- app/dialog/configbase/configdialogbase.cpp | 93 +++ .../configdialogbase.h} | 39 +- .../configdialogbasetab.cpp} | 4 +- .../configdialogbasetab.h} | 7 +- app/dialog/export/export.cpp | 54 +- app/dialog/export/export.h | 5 +- .../footageproperties/footageproperties.cpp | 197 ------ .../footageproperties/footageproperties.h | 133 ---- .../streamproperties/CMakeLists.txt | 26 - .../streamproperties/streamproperties.cpp | 30 - .../videostreamproperties.cpp | 251 ------- .../streamproperties/videostreamproperties.h | 143 ---- .../footagerelink/footagerelinkdialog.cpp | 4 +- app/dialog/preferences/preferences.cpp | 52 +- app/dialog/preferences/preferences.h | 21 +- app/dialog/preferences/tabs/CMakeLists.txt | 2 - .../tabs/preferencesappearancetab.cpp | 6 +- .../tabs/preferencesappearancetab.h | 6 +- .../preferences/tabs/preferencesaudiotab.cpp | 4 +- .../preferences/tabs/preferencesaudiotab.h | 6 +- .../tabs/preferencesbehaviortab.cpp | 4 +- .../preferences/tabs/preferencesbehaviortab.h | 6 +- .../preferences/tabs/preferencesdisktab.cpp | 4 +- .../preferences/tabs/preferencesdisktab.h | 6 +- .../tabs/preferencesgeneraltab.cpp | 4 +- .../preferences/tabs/preferencesgeneraltab.h | 6 +- .../tabs/preferenceskeyboardtab.cpp | 4 +- .../preferences/tabs/preferenceskeyboardtab.h | 6 +- app/dialog/sequence/presetmanager.h | 15 +- app/dialog/sequence/sequence.cpp | 12 +- .../sequence/sequencedialogpresettab.cpp | 21 +- app/dialog/sequence/sequencedialogpresettab.h | 12 +- app/dialog/sequence/sequencepreset.h | 26 +- app/node/block/block.cpp | 2 +- app/node/block/block.h | 2 +- app/node/block/clip/clip.cpp | 4 +- app/node/block/clip/clip.h | 2 +- app/node/block/transition/transition.cpp | 4 +- app/node/block/transition/transition.h | 2 +- app/node/factory.cpp | 14 +- app/node/factory.h | 4 +- app/node/graph.cpp | 6 +- app/node/graph.h | 2 +- app/node/input/CMakeLists.txt | 1 - app/node/input/media/media.cpp | 100 --- app/node/input/media/media.h | 82 --- app/node/input/time/timeinput.cpp | 4 +- app/node/input/time/timeinput.h | 2 +- app/node/math/merge/merge.cpp | 6 +- app/node/math/merge/merge.h | 2 +- app/node/node.cpp | 111 ++- app/node/node.h | 15 +- app/node/nodecopypaste.cpp | 7 +- app/node/output/track/track.cpp | 6 +- app/node/output/track/track.h | 4 +- app/node/output/track/tracklist.cpp | 8 +- app/node/output/track/tracklist.h | 6 +- app/node/output/viewer/viewer.cpp | 273 +------- app/node/output/viewer/viewer.h | 130 +--- app/node/traverser.cpp | 36 +- app/node/traverser.h | 5 +- app/node/value.cpp | 21 +- app/node/value.h | 30 +- app/panel/footageviewer/footageviewer.cpp | 2 +- app/panel/project/project.cpp | 12 +- app/panel/project/project.h | 2 +- app/panel/timebased/timebased.cpp | 4 +- app/panel/timebased/timebased.h | 4 +- app/project/item/folder/folder.cpp | 95 ++- app/project/item/folder/folder.h | 106 ++- app/project/item/footage/CMakeLists.txt | 8 +- app/project/item/footage/audiostream.cpp | 97 --- app/project/item/footage/audiostream.h | 68 -- app/project/item/footage/footage.cpp | 653 +++++++++++++++--- app/project/item/footage/footage.h | 337 +++++++-- app/project/item/footage/stream.cpp | 221 +++--- app/project/item/footage/stream.h | 337 +++++++-- app/project/item/footage/videostream.cpp | 198 ------ app/project/item/footage/videostream.h | 179 ----- app/project/item/item.cpp | 113 +-- app/project/item/item.h | 74 +- app/project/item/sequence/sequence.cpp | 553 +++++++++------ app/project/item/sequence/sequence.h | 197 +++++- app/project/project.cpp | 67 +- app/project/project.h | 4 +- app/project/projectviewmodel.cpp | 336 ++++----- app/project/projectviewmodel.h | 125 +--- app/render/colorprocessorcache.h | 1 - app/render/job/CMakeLists.txt | 1 + .../job/footagejob.h} | 36 +- app/render/opengl/openglrenderer.cpp | 4 +- app/render/playbackcache.cpp | 7 +- app/render/previewautocacher.cpp | 11 +- app/render/previewautocacher.h | 11 +- app/render/rendercache.h | 3 +- app/render/renderer.h | 1 + app/render/rendermanager.cpp | 10 +- app/render/rendermanager.h | 8 +- app/render/renderprocessor.cpp | 81 ++- app/render/renderprocessor.h | 6 +- app/render/stillimagecache.h | 6 +- app/task/conform/conform.cpp | 7 +- app/task/conform/conform.h | 8 +- app/task/export/export.cpp | 2 +- app/task/export/export.h | 2 +- app/task/precache/precachetask.cpp | 24 +- app/task/precache/precachetask.h | 7 +- app/task/project/import/import.cpp | 66 +- app/task/project/load/load.cpp | 31 +- app/task/project/save/save.cpp | 8 +- app/task/render/render.cpp | 2 +- app/task/render/render.h | 6 +- app/widget/CMakeLists.txt | 2 +- app/widget/curvewidget/curvewidget.cpp | 2 +- app/widget/curvewidget/curvewidget.h | 2 +- .../media => widget/filefield}/CMakeLists.txt | 4 +- app/widget/filefield/filefield.cpp | 69 ++ .../filefield/filefield.h} | 40 +- app/widget/footagecombobox/CMakeLists.txt | 22 - .../footagecombobox/footagecombobox.cpp | 127 ---- app/widget/footagecombobox/footagecombobox.h | 71 -- app/widget/keyframeview/keyframeviewundo.cpp | 4 +- app/widget/nodecombobox/nodecombobox.cpp | 1 + app/widget/nodeparamview/nodeparamview.cpp | 2 +- app/widget/nodeparamview/nodeparamview.h | 2 +- .../nodeparamview/nodeparamviewundo.cpp | 14 +- .../nodeparamviewwidgetbridge.cpp | 52 +- .../nodetableview/nodetabletraverser.cpp | 15 +- app/widget/nodetableview/nodetabletraverser.h | 4 +- app/widget/nodetreeview/nodetreeview.cpp | 22 + app/widget/nodetreeview/nodetreeview.h | 20 + app/widget/nodeview/nodeview.cpp | 4 + app/widget/nodeview/nodeviewedge.cpp | 4 +- app/widget/nodeview/nodeviewundo.cpp | 36 +- app/widget/nodeview/nodeviewundo.h | 36 +- .../projectexplorer/projectexplorer.cpp | 247 +++---- app/widget/projectexplorer/projectexplorer.h | 8 +- .../projectexplorer/projectexplorerundo.h | 41 -- app/widget/timebased/timebasedwidget.cpp | 24 +- app/widget/timebased/timebasedwidget.h | 12 +- app/widget/timelinewidget/timelineundo.h | 78 +-- app/widget/timelinewidget/timelinewidget.cpp | 40 +- app/widget/timelinewidget/timelinewidget.h | 12 +- app/widget/timelinewidget/tool/add.cpp | 2 +- app/widget/timelinewidget/tool/import.cpp | 68 +- app/widget/timelinewidget/tool/pointer.cpp | 2 +- app/widget/timelinewidget/tool/ripple.cpp | 4 +- app/widget/timelinewidget/tool/tool.cpp | 5 + app/widget/timelinewidget/tool/tool.h | 2 + app/widget/timelinewidget/tool/transition.cpp | 2 +- .../timelinewidget/view/timelineview.cpp | 1 - .../view/timelineviewghostitem.h | 8 + app/widget/viewer/footageviewer.cpp | 89 ++- app/widget/viewer/footageviewer.h | 7 +- app/widget/viewer/gizmotraverser.cpp | 8 +- app/widget/viewer/gizmotraverser.h | 2 +- app/widget/viewer/viewer.cpp | 10 +- app/widget/viewer/viewer.h | 8 +- app/window/mainwindow/mainwindow.cpp | 45 +- app/window/mainwindow/mainwindow.h | 4 +- 174 files changed, 3515 insertions(+), 4658 deletions(-) create mode 100644 app/codec/footagemeta.h rename app/dialog/{footageproperties => configbase}/CMakeLists.txt (82%) create mode 100644 app/dialog/configbase/configdialogbase.cpp rename app/dialog/{footageproperties/streamproperties/audiostreamproperties.cpp => configbase/configdialogbase.h} (55%) rename app/dialog/{preferences/tabs/preferencestab.cpp => configbase/configdialogbasetab.cpp} (91%) rename app/dialog/{preferences/tabs/preferencestab.h => configbase/configdialogbasetab.h} (84%) delete mode 100644 app/dialog/footageproperties/footageproperties.cpp delete mode 100644 app/dialog/footageproperties/footageproperties.h delete mode 100644 app/dialog/footageproperties/streamproperties/CMakeLists.txt delete mode 100644 app/dialog/footageproperties/streamproperties/streamproperties.cpp delete mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.cpp delete mode 100644 app/dialog/footageproperties/streamproperties/videostreamproperties.h delete mode 100644 app/node/input/media/media.cpp delete mode 100644 app/node/input/media/media.h delete mode 100644 app/project/item/footage/audiostream.cpp delete mode 100644 app/project/item/footage/audiostream.h delete mode 100644 app/project/item/footage/videostream.cpp delete mode 100644 app/project/item/footage/videostream.h rename app/{dialog/footageproperties/streamproperties/audiostreamproperties.h => render/job/footagejob.h} (59%) rename app/{node/input/media => widget/filefield}/CMakeLists.txt (92%) create mode 100644 app/widget/filefield/filefield.cpp rename app/{dialog/footageproperties/streamproperties/streamproperties.h => widget/filefield/filefield.h} (58%) delete mode 100644 app/widget/footagecombobox/CMakeLists.txt delete mode 100644 app/widget/footagecombobox/footagecombobox.cpp delete mode 100644 app/widget/footagecombobox/footagecombobox.h diff --git a/app/codec/decoder.cpp b/app/codec/decoder.cpp index 0a4b441cc..7da4bd0d8 100644 --- a/app/codec/decoder.cpp +++ b/app/codec/decoder.cpp @@ -22,7 +22,6 @@ #include #include -#include #include "codec/ffmpeg/ffmpegdecoder.h" #include "codec/oiio/oiiodecoder.h" @@ -43,18 +42,19 @@ QMutex Decoder::currently_conforming_mutex_; QWaitCondition Decoder::currently_conforming_wait_cond_; QVector Decoder::currently_conforming_; -Decoder::Decoder() : - stream_(nullptr) +const rational Decoder::kAnyTimecode = RATIONAL_MIN; + +Decoder::Decoder() { } -bool Decoder::Open(Stream *fs) +bool Decoder::Open(const CodecStream &stream) { QMutexLocker locker(&mutex_); - if (stream_) { + if (stream_.IsValid()) { // Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not. - if (stream_ == fs) { + if (stream_ == stream) { return true; } else { qWarning() << "Tried to open a decoder that was already open with another stream"; @@ -62,27 +62,29 @@ bool Decoder::Open(Stream *fs) } } else { // Stream was not open, try opening it now - if (fs == nullptr) { + if (!stream.IsValid()) { // Cannot open null stream qCritical() << "Decoder attempted to open null stream"; return false; } - if (fs->footage()->decoder() != id()) { - qCritical() << "Tried to open footage in incorrect decoder"; + if (!stream.Exists()) { + // Cannot open file that doesn't exist + qCritical() << "Decoder attempted to open file that doesn't exist"; return false; } // Set stream - stream_ = fs; + stream_ = stream; // Try open internal if (OpenInternal()) { return true; } else { // Unset stream + qCritical() << "Failed to open" << stream_.filename() << "stream" << stream_.stream(); CloseInternal(); - stream_ = nullptr; + stream_.Reset(); return false; } } @@ -92,7 +94,7 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r) { QMutexLocker locker(&mutex_); - if (!stream_) { + if (!stream_.IsValid()) { qCritical() << "Can't retrieve video on a closed decoder"; return nullptr; } @@ -102,19 +104,14 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r) return nullptr; } - if (stream_->type() != Stream::kVideo) { - qCritical() << "Tried to retrieve video from a non-video stream"; - return nullptr; - } - return RetrieveVideoInternal(timecode, divider); } -SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QAtomicInt *cancelled) +SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, const QAtomicInt *cancelled) { QMutexLocker locker(&mutex_); - if (!stream_) { + if (!stream_.IsValid()) { qCritical() << "Can't retrieve audio on a closed decoder"; return nullptr; } @@ -124,13 +121,8 @@ SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams return nullptr; } - if (stream_->type() != Stream::kAudio) { - qCritical() << "Tried to retrieve audio from a non-audio stream"; - return nullptr; - } - // Determine if we already have a conformed version - QString conform_filename = GetConformedFilename(params); + QString conform_filename = GetConformedFilename(cache_path, params); CurrentlyConforming want_conform = {stream_, params}; currently_conforming_mutex_.lock(); @@ -179,9 +171,9 @@ void Decoder::Close() { QMutexLocker locker(&mutex_); - if (stream_) { + if (stream_.IsValid()) { CloseInternal(); - stream_ = nullptr; + stream_.Reset(); } else { qWarning() << "Tried to close a decoder that wasn't open"; } @@ -191,7 +183,7 @@ void Decoder::Close() * DECODER STATIC PUBLIC MEMBERS */ -QVector ReceiveListOfAllDecoders() +QVector Decoder::ReceiveListOfAllDecoders() { QVector decoders; @@ -203,55 +195,6 @@ QVector ReceiveListOfAllDecoders() return decoders; } -Footage* Decoder::Probe(Project* project, const QString &filename, const QAtomicInt* cancelled) -{ - // Check for a valid filename - if (filename.isEmpty()) { - qWarning() << "Tried to probe media with an empty filename"; - return nullptr; - } - - // Check file exists - if (!QFileInfo::exists(filename)) { - qWarning() << "Tried to probe file that doesn't exist:" << filename; - return nullptr; - } - - // Create list to iterate through - QVector decoder_list = ReceiveListOfAllDecoders(); - - // Pass Footage through each Decoder's probe function - for (int i=0;iProbe(filename, cancelled); - - if (footage) { - QFileInfo file_info(filename); - footage->set_name(file_info.fileName()); - footage->set_filename(filename); - - footage->set_decoder(decoder->id()); - footage->set_project(project); - footage->set_timestamp(file_info.lastModified().toMSecsSinceEpoch()); - - footage->SetValid(); - - // FIXME: Cache the results so we don't have to probe if this media is added a second time - - return footage; - } - } - - // We aren't able to use this Footage - return nullptr; -} - DecoderPtr Decoder::CreateFromID(const QString &id) { if (id.isEmpty()) { @@ -270,9 +213,12 @@ DecoderPtr Decoder::CreateFromID(const QString &id) return nullptr; } -QString Decoder::GetConformedFilename(const AudioParams ¶ms) +QString Decoder::GetConformedFilename(const QString& cache_path, const AudioParams ¶ms) { - QString index_fn = GetIndexFilename(); + QString index_fn = QStringLiteral("%1.%2:%3").arg(FileFunctions::GetUniqueFileIdentifier(stream_.filename()), + QString::number(stream_.stream())); + + index_fn = QDir(cache_path).filePath(index_fn); index_fn.append('.'); index_fn.append(QString::number(params.sample_rate())); @@ -284,15 +230,20 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms) return index_fn; } -QString Decoder::GetIndexFilename() +int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time) { - return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index()))); + return Timecode::time_to_timestamp(time, timebase) + start_time; } -void Decoder::SignalProcessingProgress(const int64_t &ts) +Decoder::CodecStream Decoder::GetCodecStreamFromStreamReference(const Footage::StreamReference &ref) { - if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) { - emit IndexProgress(static_cast(ts) / static_cast(stream()->duration())); + return CodecStream(ref.footage()->filename(), ref.footage()->GetRealStreamIndex(ref)); +} + +void Decoder::SignalProcessingProgress(int64_t ts, int64_t duration) +{ + if (duration != AV_NOPTS_VALUE && duration != 0) { + emit IndexProgress(static_cast(ts) / static_cast(duration)); } } @@ -377,4 +328,9 @@ SampleBufferPtr Decoder::RetrieveAudioFromConform(const QString &conform_filenam return nullptr; } +uint qHash(Decoder::CodecStream stream, uint seed) +{ + return qHash(stream.filename(), seed) ^ qHash(stream.stream(), seed); +} + } diff --git a/app/codec/decoder.h b/app/codec/decoder.h index 79e1a986b..37a084450 100644 --- a/app/codec/decoder.h +++ b/app/codec/decoder.h @@ -25,6 +25,7 @@ extern "C" { #include } +#include #include #include #include @@ -35,6 +36,7 @@ extern "C" { #include "codec/waveoutput.h" #include "common/rational.h" #include "project/item/footage/footage.h" +#include "project/item/footage/stream.h" namespace olive { @@ -77,6 +79,57 @@ public: virtual bool SupportsVideo(){return false;} virtual bool SupportsAudio(){return false;} + class CodecStream + { + public: + CodecStream() : + stream_(-1) + { + } + + CodecStream(const QString& filename, int stream) : + filename_(filename), + stream_(stream) + { + } + + bool IsValid() const + { + return !filename_.isEmpty() && stream_ >= 0; + } + + bool Exists() const + { + return QFileInfo::exists(filename_); + } + + void Reset() + { + *this = CodecStream(); + } + + bool operator==(const CodecStream& rhs) const + { + return filename_ == rhs.filename_ && stream_ == rhs.stream_; + } + + const QString& filename() const + { + return filename_; + } + + int stream() const + { + return stream_; + } + + private: + QString filename_; + + int stream_; + + }; + /** * @brief Open stream for decoding * @@ -86,7 +139,9 @@ public: * already open and the stream == the stream provided. Returns FALSE if the stream couldn't * be opened OR if already open and the stream is NOT the same. */ - bool Open(Stream* fs); + bool Open(const CodecStream& stream); + + static const rational kAnyTimecode; /** * @brief Retrieves a video frame from footage @@ -108,28 +163,7 @@ public: * * This function is thread safe and can only run while the decoder is open. \see Open() */ - SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QAtomicInt *cancelled); - - /** - * @brief Try to probe a Footage file by passing it through all available Decoders - * - * This is a helper function designed to abstract the process of communicating with several Decoders from the rest of - * the application. This function will take a Footage file and manually pass it through the available Decoders' Probe() - * functions until one indicates that it can decode this file. That Decoder will then dump information about the file - * into the Footage object for use throughout the program. - * - * Probing may be a lengthy process and it's recommended to run this in a separate thread. - * - * @param f - * - * A Footage object with a valid filename. If the Footage does not have a valid filename (e.g. is empty or file doesn't - * exist), this function will return FALSE. - * - * @return - * - * TRUE if a Decoder was successfully able to parse and probe this file. FALSE if not. - */ - static Footage *Probe(Project *project, const QString& filename, const QAtomicInt *cancelled); + SampleBufferPtr RetrieveAudio(const TimeRange& range, const AudioParams& params, const QString &cache_path, const QAtomicInt *cancelled); /** * @brief Generate a Footage object from a file @@ -142,7 +176,7 @@ public: * * This function is re-entrant. */ - virtual Footage *Probe(const QString& filename, const QAtomicInt* cancelled) const = 0; + virtual Streams Probe(const QString& filename, const QAtomicInt* cancelled) const = 0; /** * @brief Closes media/deallocates memory @@ -166,6 +200,10 @@ public: static int64_t GetImageSequenceIndex(const QString& filename); + static QVector ReceiveListOfAllDecoders(); + + static CodecStream GetCodecStreamFromStreamReference(const Footage::StreamReference& ref); + protected: /** * @brief Internal open function @@ -199,17 +237,15 @@ protected: virtual bool ConformAudioInternal(const QString& filename, const AudioParams ¶ms, const QAtomicInt* cancelled); - void SignalProcessingProgress(const int64_t& ts); + void SignalProcessingProgress(int64_t ts, int64_t duration); /** * @brief Get the destination filename of an audio stream conformed to a set of parameters */ - QString GetConformedFilename(const AudioParams ¶ms); - - QString GetIndexFilename(); + QString GetConformedFilename(const QString &cache_path, const AudioParams ¶ms); struct CurrentlyConforming { - Stream* stream; + CodecStream stream; AudioParams params; bool operator==(const CurrentlyConforming& rhs) const @@ -223,11 +259,13 @@ protected: * * This function is NOT thread safe and should therefore only be called by thread safe functions. */ - Stream* stream() const + const CodecStream& stream() const { return stream_; } + static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time); + static QMutex currently_conforming_mutex_; static QWaitCondition currently_conforming_wait_cond_; static QVector currently_conforming_; @@ -242,12 +280,14 @@ signals: private: SampleBufferPtr RetrieveAudioFromConform(const QString& conform_filename, const TimeRange &range); - Stream* stream_; + CodecStream stream_; QMutex mutex_; }; +uint qHash(Decoder::CodecStream stream, uint seed = 0); + } Q_DECLARE_METATYPE(olive::Decoder::RetrieveState) diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index ba27072e0..76c033f50 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -64,13 +64,13 @@ FFmpegDecoder::~FFmpegDecoder() bool FFmpegDecoder::OpenInternal() { - if (instance_.Open(stream()->footage()->filename().toUtf8(), stream()->index())) { + if (instance_.Open(stream().filename().toUtf8(), stream().stream())) { AVStream* s = instance_.avstream(); // Store one second in the source's timebase second_ts_ = qRound64(av_q2d(av_inv_q(s->time_base))); - if (stream()->type() == Stream::kVideo) { + if (s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // Get an Olive compatible AVPixelFormat ideal_pix_fmt_ = FFmpegUtils::GetCompatiblePixelFormat(static_cast(s->codecpar->format)); @@ -92,20 +92,18 @@ bool FFmpegDecoder::OpenInternal() return false; } -FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) +/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r) { // This is a still image - VideoStream* is = static_cast(stream()); - - QString img_filename = stream()->footage()->filename(); + QString img_filename = stream().filename(); int64_t ts; // If it's an image sequence, we'll probably need to transform the filename - if (is->video_type() == VideoStream::kVideoTypeImageSequence) { - ts = static_cast(stream())->get_time_in_timebase_units(timecode); + if (stream().GetStream().video_type() == Stream::kVideoTypeImageSequence) { + ts = stream().GetTimeInTimebaseUnits(timecode); - img_filename = TransformImageSequenceFileName(stream()->footage()->filename(), ts); + img_filename = TransformImageSequenceFileName(stream().filename(), ts); } else { ts = 0; } @@ -115,19 +113,21 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int & FramePtr output_frame = nullptr; Instance i; - i.Open(img_filename.toUtf8(), stream()->index()); + i.Open(img_filename.toUtf8(), stream().GetRealStreamIndex()); int ret = i.GetFrame(pkt, frame); if (ret >= 0) { + VideoParams video_params = stream().video_params(); + // Create frame to return output_frame = Frame::Create(); output_frame->set_video_params(VideoParams(frame->width, frame->height, native_pix_fmt_, native_channel_count_, - is->pixel_aspect_ratio(), - is->interlacing(), + video_params.pixel_aspect_ratio(), + video_params.interlacing(), divider)); output_frame->set_timestamp(timecode); output_frame->allocate(); @@ -146,59 +146,48 @@ FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int & av_packet_free(&pkt); return output_frame; -} +}*/ FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const int ÷r) { - VideoStream* vs = static_cast(stream()); - if (scale_divider_ != divider) { FreeScaler(); InitScaler(divider); } - if (vs->video_type() == VideoStream::kVideoTypeStill - || vs->video_type() == VideoStream::kVideoTypeImageSequence) { + AVStream* s = instance_.avstream(); - return RetrieveStillImage(timecode, divider); + int divided_width = VideoParams::GetScaledDimension(s->codecpar->width, divider); + int divided_height = VideoParams::GetScaledDimension(s->codecpar->height, divider); - } else { + if (pool_.width() != divided_width || pool_.height() != divided_height) { + // Clear all instance queues + ClearFrameCache(); - int64_t target_ts = vs->get_time_in_timebase_units(timecode); + // Set new frame pool parameters + pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_); + } - int divided_width = VideoParams::GetScaledDimension(vs->width(), divider); - int divided_height = VideoParams::GetScaledDimension(vs->height(), divider); + // Retrieve frame + FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, divider); - if (pool_.width() != divided_width || pool_.height() != divided_height) { - // Clear all instance queues - ClearFrameCache(); + // We found the frame, we'll return a copy + if (return_frame) { + FramePtr copy = Frame::Create(); + copy->set_video_params(VideoParams(s->codecpar->width, + s->codecpar->height, + native_pix_fmt_, + native_channel_count_, + av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect, + VideoParams::kInterlaceNone, // May be incorrect + divider)); + copy->set_timestamp(timecode); + copy->allocate(); - // Set new frame pool parameters - pool_.SetParameters(divided_width, divided_height, native_pix_fmt_, native_channel_count_); - } - - // Retrieve frame - FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(target_ts, divider); - - // We found the frame, we'll return a copy - if (return_frame) { - FramePtr copy = Frame::Create(); - copy->set_video_params(VideoParams(vs->width(), - vs->height(), - native_pix_fmt_, - native_channel_count_, - vs->pixel_aspect_ratio(), - vs->interlacing(), - divider)); - copy->set_timestamp(timecode); - copy->allocate(); - - // This data will already match the frame - memcpy(copy->data(), return_frame->data(), copy->allocated_size()); - - return copy; - } + // This data will already match the frame + memcpy(copy->data(), return_frame->data(), copy->allocated_size()); + return copy; } return nullptr; @@ -218,19 +207,16 @@ QString FFmpegDecoder::id() return QStringLiteral("ffmpeg"); } -Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const +Streams FFmpegDecoder::Probe(const QString &filename, const QAtomicInt *cancelled) const { - Q_UNUSED(cancelled) + // Return value + Streams streams; // Variable for receiving errors from FFmpeg int error_code; - // Result to return - Footage* footage = nullptr; - // Convert QString to a C string - QByteArray ba = filename.toUtf8(); - const char* filename_c = ba.constData(); + QByteArray filename_c = filename.toUtf8(); // Open file in a format context AVFormatContext* fmt_ctx = nullptr; @@ -244,18 +230,18 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell int64_t footage_duration = fmt_ctx->duration; - QVector streams(fmt_ctx->nb_streams); - // Dump it into the Footage object for (unsigned int i=0;inb_streams;i++) { + // FFmpeg AVStream AVStream* avstream = fmt_ctx->streams[i]; + // Our native stream class + Stream stream; + // Find decoder for this stream, if it exists we can proceed AVCodec* decoder = avcodec_find_decoder(avstream->codecpar->codec_id); - Stream* str; - if (decoder && (avstream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || avstream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)) { @@ -274,7 +260,7 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell { Instance instance; - instance.Open(filename.toUtf8(), avstream->index); + instance.Open(filename_c, avstream->index); // Read first frame and retrieve some metadata if (instance.GetFrame(pkt, frame) >= 0) { @@ -332,47 +318,35 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell av_packet_free(&pkt); } - VideoStream* video_stream = new VideoStream(); - - if (image_is_still) { - video_stream->set_video_type(VideoStream::kVideoTypeStill); - } else { - video_stream->set_video_type(VideoStream::kVideoTypeVideo); - - video_stream->set_frame_rate(frame_rate); - video_stream->set_start_time(avstream->start_time); - } - - video_stream->set_width(avstream->codecpar->width); - video_stream->set_height(avstream->codecpar->height); - video_stream->set_interlacing(interlacing); - video_stream->set_pixel_aspect_ratio(pixel_aspect_ratio); - AVPixelFormat compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)); - video_stream->set_format(GetNativePixelFormat(compatible_pix_fmt)); - video_stream->set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); - str = video_stream; + stream = Stream(Stream::kVideo); + stream.set_width(avstream->codecpar->width); + stream.set_height(avstream->codecpar->height); + stream.set_video_type((image_is_still) ? Stream::kVideoTypeStill : Stream::kVideoTypeVideo); + stream.set_pixel_format(GetNativePixelFormat(compatible_pix_fmt)); + stream.set_channel_count(GetNativeChannelCount(compatible_pix_fmt)); + stream.set_interlacing(interlacing); + stream.set_pixel_aspect_ratio(pixel_aspect_ratio); + stream.set_frame_rate(frame_rate); + stream.set_start_time(avstream->start_time); + + // Defaults to false, requires user intervention if incorrect + stream.set_premultiplied_alpha(false); } else { // Create an audio stream object - AudioStream* audio_stream = new AudioStream(); - uint64_t channel_layout = avstream->codecpar->channel_layout; if (!channel_layout) { channel_layout = static_cast(av_get_default_channel_layout(avstream->codecpar->channels)); } - audio_stream->set_channel_layout(channel_layout); - audio_stream->set_channels(avstream->codecpar->channels); - audio_stream->set_sample_rate(avstream->codecpar->sample_rate); - if (avstream->duration == AV_NOPTS_VALUE) { // Loop through stream until we get the whole duration if (footage_duration == AV_NOPTS_VALUE) { Instance instance; - instance.Open(filename.toUtf8(), avstream->index); + instance.Open(filename_c, avstream->index); AVPacket* pkt = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); @@ -396,67 +370,53 @@ Footage *FFmpegDecoder::Probe(const QString& filename, const QAtomicInt* cancell } } - str = audio_stream; + stream = Stream(Stream::kAudio); + stream.set_channel_layout(channel_layout); + stream.set_channel_count(avstream->codecpar->channels); + stream.set_sample_rate(avstream->codecpar->sample_rate); } } else { // This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file - str = new Stream(); + Stream::Type type; // Set the correct codec type based on FFmpeg's result switch (avstream->codecpar->codec_type) { - case AVMEDIA_TYPE_UNKNOWN: - str->set_type(Stream::kUnknown); - break; case AVMEDIA_TYPE_DATA: - str->set_type(Stream::kData); + type = Stream::kData; break; case AVMEDIA_TYPE_SUBTITLE: - str->set_type(Stream::kSubtitle); + type = Stream::kSubtitle; break; case AVMEDIA_TYPE_ATTACHMENT: - str->set_type(Stream::kAttachment); + type = Stream::kAttachment; break; + case AVMEDIA_TYPE_UNKNOWN: default: // Fallback to an unknown stream - str->set_type(Stream::kUnknown); + type = Stream::kUnknown; break; } + stream = Stream(type); + } - str->set_index(avstream->index); - str->set_timebase(avstream->time_base); - str->set_duration(avstream->duration); + stream.set_timebase(avstream->time_base); + stream.set_duration(avstream->duration); + + streams.append(stream); - streams[i] = str; } - // Check if we could pick up any streams in this file - bool found_valid_streams = false; - - foreach (Stream* stream, streams) { - if (stream->type() != Stream::kUnknown) { - found_valid_streams = true; - break; - } - } - - if (found_valid_streams) { - // We actually have footage we can return instead of nullptr - footage = new Footage(); - - // Add streams - footage->add_streams(streams); - } } // Free all memory avformat_close_input(&fmt_ctx); - return footage; + return streams; } QString FFmpegDecoder::FFmpegError(int error_code) @@ -549,7 +509,7 @@ bool FFmpegDecoder::ConformAudioInternal(const QString &filename, const AudioPar delete [] data; } - SignalProcessingProgress(frame->pts); + SignalProcessingProgress(frame->pts, instance_.avstream()->duration); } wave_out.close(); @@ -677,27 +637,30 @@ void FFmpegDecoder::ClearFrameCache() cache_at_zero_ = false; } -FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_ts, int divider) +FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, int divider) { + int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time); int64_t seek_ts = target_ts; bool still_seeking = false; - // If the frame wasn't in the frame cache, see if this frame cache is too old to use - if (cached_frames_.isEmpty() - || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) { - ClearFrameCache(); + if (time != kAnyTimecode) { + // If the frame wasn't in the frame cache, see if this frame cache is too old to use + if (cached_frames_.isEmpty() + || (target_ts < cached_frames_.first()->timestamp() || target_ts > cached_frames_.last()->timestamp() + 2*second_ts_)) { + ClearFrameCache(); - instance_.Seek(seek_ts); - if (seek_ts == 0) { - cache_at_zero_ = true; - } + instance_.Seek(seek_ts); + if (seek_ts == 0) { + cache_at_zero_ = true; + } - still_seeking = true; - } else { - // Search cache for frame - FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts); - if (cached_frame) { - return cached_frame; + still_seeking = true; + } else { + // Search cache for frame + FFmpegFramePool::ElementPtr cached_frame = GetFrameFromCache(target_ts); + if (cached_frame) { + return cached_frame; + } } } @@ -786,7 +749,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_t cached_frames_.append(cached); // If this is a valid frame, see if this or the frame before it are the one we need - if (cached->timestamp() == target_ts) { + if (cached->timestamp() == target_ts || time == kAnyTimecode) { return_frame = cached; break; } else if (cached->timestamp() > target_ts) { @@ -809,13 +772,14 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const int64_t& target_t void FFmpegDecoder::InitScaler(int divider) { - VideoStream* vs = static_cast(stream()); + int src_width = instance_.avstream()->codecpar->width; + int src_height = instance_.avstream()->codecpar->height; - int scaled_width = VideoParams::GetScaledDimension(vs->width(), divider); - int scaled_height = VideoParams::GetScaledDimension(vs->height(), divider); + int scaled_width = VideoParams::GetScaledDimension(src_width, divider); + int scaled_height = VideoParams::GetScaledDimension(src_height, divider); - scale_ctx_ = sws_getContext(vs->width(), - vs->height(), + scale_ctx_ = sws_getContext(src_width, + src_height, static_cast(instance_.avstream()->codecpar->format), scaled_width, scaled_height, diff --git a/app/codec/ffmpeg/ffmpegdecoder.h b/app/codec/ffmpeg/ffmpegdecoder.h index 154e446e0..e2b8f91aa 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.h +++ b/app/codec/ffmpeg/ffmpegdecoder.h @@ -35,7 +35,6 @@ extern "C" { #include "codec/decoder.h" #include "codec/waveoutput.h" #include "ffmpegframepool.h" -#include "project/item/footage/videostream.h" namespace olive { @@ -57,7 +56,7 @@ public: virtual bool SupportsVideo() override{return true;} virtual bool SupportsAudio() override{return true;} - virtual Footage* Probe(const QString& filename, const QAtomicInt* cancelled) const override; + virtual Streams Probe(const QString &filename, const QAtomicInt *cancelled) const override; protected: virtual bool OpenInternal() override; @@ -122,7 +121,7 @@ private: void InitScaler(int divider); void FreeScaler(); - FramePtr RetrieveStillImage(const rational& timecode, const int& divider); + //FramePtr RetrieveStillImage(const rational& timecode, const int& divider); static VideoParams::Format GetNativePixelFormat(AVPixelFormat pix_fmt); static int GetNativeChannelCount(AVPixelFormat pix_fmt); @@ -135,7 +134,7 @@ private: void ClearFrameCache(); - FFmpegFramePool::ElementPtr RetrieveFrame(const int64_t &target_ts, int divider); + FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, int divider); void RemoveFirstFrame(); diff --git a/app/codec/footagemeta.h b/app/codec/footagemeta.h new file mode 100644 index 000000000..4d23025f1 --- /dev/null +++ b/app/codec/footagemeta.h @@ -0,0 +1,10 @@ +#ifndef FOOTAGEMETA_H +#define FOOTAGEMETA_H + +struct FootageData { + struct StreamData { + + }; +}; + +#endif // FOOTAGEMETA_H diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index dc00aaa40..70d78e9b8 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -51,14 +51,16 @@ QString OIIODecoder::id() return QStringLiteral("oiio"); } -Footage *OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled) const +Streams OIIODecoder::Probe(const QString &filename, const QAtomicInt* cancelled) const { Q_UNUSED(cancelled) + Streams streams; + // 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 nullptr; + return streams; } std::string std_filename = filename.toStdString(); @@ -66,82 +68,46 @@ Footage *OIIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled auto in = OIIO::ImageInput::open(std_filename); if (!in) { - return nullptr; + return streams; } // 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 nullptr; + return streams; } - Footage* footage = new Footage(); + Stream stream(Stream::kVideo); - VideoStream* image_stream = new VideoStream(); - - image_stream->set_width(in->spec().width); - image_stream->set_height(in->spec().height); - image_stream->set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(in->spec().format.basetype))); - image_stream->set_channel_count(in->spec().nchannels); - image_stream->set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec())); - image_stream->set_video_type(VideoStream::kVideoTypeStill); - - // Images will always have just one stream - image_stream->set_index(0); + stream.set_width(in->spec().width); + stream.set_height(in->spec().height); + stream.set_pixel_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast(in->spec().format.basetype))); + stream.set_channel_count(in->spec().nchannels); + stream.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(in->spec())); + stream.set_video_type(Stream::kVideoTypeStill); // 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); + stream.set_premultiplied_alpha(true); - // Get stats for this image and dump them into the Footage file - footage->add_stream(image_stream); + streams.append(stream); // If we're here, we have a successful image open in->close(); - return footage; + return streams; } bool OIIODecoder::OpenInternal() { - // If we can open the filename provided, assume everything is working (even if this is an image - // sequence with potentially missing frame) - if (OpenImageHandler(stream()->footage()->filename())) { - VideoStream* video_stream = static_cast(stream()); - - if (video_stream->video_type() == VideoStream::kVideoTypeStill) { - last_sequence_index_ = 0; - } else { - last_sequence_index_ = GetImageSequenceIndex(stream()->footage()->filename()); - } - - return true; - } - return false; + // If we can open the filename provided, assume everything is working + return OpenImageHandler(stream().filename()); } FramePtr OIIODecoder::RetrieveVideoInternal(const rational &timecode, const int& divider) { - VideoStream* video_stream = static_cast(stream()); - - int64_t sequence_index; - - if (video_stream->video_type() == VideoStream::kVideoTypeStill) { - sequence_index = 0; - } else { - sequence_index = video_stream->get_time_in_timebase_units(timecode); - } - - if (last_sequence_index_ != sequence_index) { - CloseImageHandle(); - - if (!OpenImageHandler(TransformImageSequenceFileName(stream()->footage()->filename(), sequence_index))) { - return nullptr; - } - - last_sequence_index_ = sequence_index; - } + Q_UNUSED(timecode) FramePtr frame = Frame::Create(); diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index 04a1397b1..63774a38d 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -40,7 +40,7 @@ public: virtual bool SupportsVideo() override{return true;} - virtual Footage* Probe(const QString& filename, const QAtomicInt* cancelled) const override; + virtual Streams Probe(const QString& filename, const QAtomicInt* cancelled) const override; protected: virtual bool OpenInternal() override; @@ -56,8 +56,6 @@ private: void CloseImageHandle(); - int64_t last_sequence_index_; - VideoParams::Format pix_fmt_; int channel_count_; diff --git a/app/common/filefunctions.cpp b/app/common/filefunctions.cpp index 8314acb4c..31fe563b1 100644 --- a/app/common/filefunctions.cpp +++ b/app/common/filefunctions.cpp @@ -43,7 +43,7 @@ QString FileFunctions::GetUniqueFileIdentifier(const QString &filename) hash.addData(info.absoluteFilePath().toUtf8()); - hash.addData(info.lastModified().toString().toUtf8()); + hash.addData(QString::number(info.lastModified().toMSecsSinceEpoch()).toUtf8()); QByteArray result = hash.result(); diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h index 20c1585cc..b0a1b08f9 100644 --- a/app/common/xmlutils.h +++ b/app/common/xmlutils.h @@ -24,7 +24,6 @@ #include #include "node/param.h" -#include "project/item/footage/stream.h" #include "undo/undocommand.h" namespace olive { @@ -58,6 +57,15 @@ struct XMLNodeData { void XMLConnectNodes(const XMLNodeData& xml_node_data, MultiUndoCommand *command = nullptr); +/** + * @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document + * + * Since Qt's default function doesn't exit at the end of the document, it ends up consistently + * throwing a "premature end of document" error. We have our own function here that does essentially + * the same thing but fixes that issue. + * + * See also: https://stackoverflow.com/questions/46346450/qt-qxmlstreamreader-always-returns-premature-end-of-document-error + */ bool XMLReadNextStartElement(QXmlStreamReader* reader); void XMLLinkBlocks(const XMLNodeData& xml_node_data); diff --git a/app/config/config.cpp b/app/config/config.cpp index 7929a08a7..4f4bffc59 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -106,6 +106,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("CatColor8"), NodeValue::kInt, 8); SetEntryInternal(QStringLiteral("CatColor9"), NodeValue::kInt, 9); SetEntryInternal(QStringLiteral("CatColor10"), NodeValue::kInt, 10); + SetEntryInternal(QStringLiteral("CatColor11"), NodeValue::kInt, 11); SetEntryInternal(QStringLiteral("AudioOutput"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("AudioInput"), NodeValue::kText, QString()); diff --git a/app/core.cpp b/app/core.cpp index b6bd2009e..0eb5b9aff 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -351,12 +351,10 @@ void Core::DialogProjectPropertiesShow() void Core::DialogExportShow() { - ViewerOutput* viewer = GetSequenceToExport(); + Sequence* viewer = GetSequenceToExport(); if (viewer) { - Sequence* sequence = dynamic_cast(viewer->parent()); - - ExportDialog* ed = new ExportDialog(viewer, sequence, main_window_); + ExportDialog* ed = new ExportDialog(viewer, main_window_); connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater); ed->open(); } @@ -381,14 +379,15 @@ void Core::CreateNewFolder() Folder* new_folder = new Folder(); // Set a default name - new_folder->set_name(tr("New Folder")); + new_folder->SetLabel(tr("New Folder")); // Create an undoable command - ProjectViewModel::AddItemCommand* aic = new ProjectViewModel::AddItemCommand(active_project_panel->model(), - folder, - new_folder); + MultiUndoCommand* command = new MultiUndoCommand(); - Core::instance()->undo_stack()->push(aic); + command->add_child(new NodeAddCommand(active_project, new_folder)); + command->add_child(new NodeEdgeAddCommand(folder, NodeInput(new_folder, Item::kParentInput))); + + Core::instance()->undo_stack()->push(command); // Trigger an automatic rename so users can enter the folder name active_project_panel->Edit(new_folder); @@ -418,13 +417,15 @@ void Core::CreateNewSequence() if (sd.exec() == QDialog::Accepted) { // Create an undoable command - ProjectViewModel::AddItemCommand* aic = new ProjectViewModel::AddItemCommand(GetActiveProjectModel(), - GetSelectedFolderInActiveProject(), - new_sequence); + MultiUndoCommand* command = new MultiUndoCommand(); - new_sequence->add_default_nodes(); + command->add_child(new NodeAddCommand(active_project, new_sequence)); + command->add_child(new NodeEdgeAddCommand(GetSelectedFolderInActiveProject(), NodeInput(new_sequence, Item::kParentInput))); - Core::instance()->undo_stack()->push(aic); + // Create and connect default nodes to new sequence + new_sequence->add_default_nodes(command); + + Core::instance()->undo_stack()->push(command); Core::instance()->main_window()->OpenSequence(new_sequence); @@ -543,6 +544,7 @@ bool Core::StartHeadlessExport() ProjectLoadTask plm(startup_project); CLITaskDialog task_dialog(&plm); + /* if (task_dialog.Run()) { std::unique_ptr p = std::unique_ptr(plm.GetLoadedProject()); QVector items = p->get_items_of_type(Item::kSequence); @@ -559,7 +561,7 @@ bool Core::StartHeadlessExport() if (items.size() > 1) { qInfo().noquote() << tr("This project has multiple sequences. Which do you wish to export?"); for (int i=0;iname().toStdString(); + std::cout << "[" << i << "] " << items.at(i)->GetLabel().toStdString(); } QTextStream stream(stdin); @@ -605,6 +607,11 @@ bool Core::StartHeadlessExport() qCritical().noquote() << tr("Project failed to load: %1").arg(plm.GetError()); return false; } + */ + + + + return false; } void Core::OpenStartupProject() @@ -720,7 +727,7 @@ void Core::SaveProjectInternal(Project* project) task_dialog->open(); } -ViewerOutput *Core::GetSequenceToExport() +Sequence *Core::GetSequenceToExport() { // First try the most recently focused time based window TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -1064,7 +1071,7 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value; } -void Core::LabelNodes(const QVector &nodes) const +void Core::LabelNodes(const QVector &nodes) { if (nodes.isEmpty()) { return; @@ -1090,9 +1097,13 @@ void Core::LabelNodes(const QVector &nodes) const &ok); if (ok) { + NodeRenameCommand* rename_command = new NodeRenameCommand(); + foreach (Node* n, nodes) { - n->SetLabel(s); + rename_command->AddNode(n, s); } + + undo_stack_.push(rename_command); } } @@ -1107,7 +1118,7 @@ Sequence *Core::CreateNewSequenceForProject(Project* project) const sequence_name = tr("Sequence %1").arg(sequence_number); sequence_number++; } while (project->root()->ChildExistsWithName(sequence_name)); - new_sequence->set_name(sequence_name); + new_sequence->SetLabel(sequence_name); return new_sequence; } @@ -1291,13 +1302,10 @@ void Core::CacheActiveSequence(bool in_out_only) bool Core::ValidateFootageInLoadedProject(Project* project, const QString& project_saved_url) { + QVector project_footage = project->root()->ListOutputsOfType(); QVector footage_we_couldnt_validate; - QVector project_footage = project->get_items_of_type(Item::kFootage); - - foreach (Item* item, project_footage) { - Footage* footage = static_cast(item); - + foreach (Footage* footage, project_footage) { if (!QFileInfo::exists(footage->filename()) && !project_saved_url.isEmpty()) { // If the footage doesn't exist, it might have moved with the project const QString& project_current_url = project->filename(); diff --git a/app/core.h b/app/core.h index c05fb5a23..0797b4340 100644 --- a/app/core.h +++ b/app/core.h @@ -241,7 +241,7 @@ public: /** * @brief Show a dialog to the user to rename a set of nodes */ - void LabelNodes(const QVector &nodes) const; + void LabelNodes(const QVector &nodes); /** * @brief Create a new sequence named appropriately for the active project @@ -488,7 +488,7 @@ private: /** * @brief Retrieves the currently most active sequence for exporting */ - ViewerOutput* GetSequenceToExport(); + Sequence* GetSequenceToExport(); /** * @brief Internal main window object diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index 239e46b8f..f041cbeeb 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -17,9 +17,9 @@ add_subdirectory(about) add_subdirectory(actionsearch) add_subdirectory(color) +add_subdirectory(configbase) add_subdirectory(diskcache) add_subdirectory(export) -add_subdirectory(footageproperties) add_subdirectory(footagerelink) add_subdirectory(keyframeproperties) add_subdirectory(preferences) diff --git a/app/dialog/footageproperties/CMakeLists.txt b/app/dialog/configbase/CMakeLists.txt similarity index 82% rename from app/dialog/footageproperties/CMakeLists.txt rename to app/dialog/configbase/CMakeLists.txt index 0a3d6a7e2..cb6e319d5 100644 --- a/app/dialog/footageproperties/CMakeLists.txt +++ b/app/dialog/configbase/CMakeLists.txt @@ -14,11 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(streamproperties) - set(OLIVE_SOURCES ${OLIVE_SOURCES} - dialog/footageproperties/footageproperties.h - dialog/footageproperties/footageproperties.cpp + dialog/configbase/configdialogbase.cpp + dialog/configbase/configdialogbase.h + dialog/configbase/configdialogbasetab.cpp + dialog/configbase/configdialogbasetab.h PARENT_SCOPE ) diff --git a/app/dialog/configbase/configdialogbase.cpp b/app/dialog/configbase/configdialogbase.cpp new file mode 100644 index 000000000..f1bf3e569 --- /dev/null +++ b/app/dialog/configbase/configdialogbase.cpp @@ -0,0 +1,93 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "configdialogbase.h" + +#include +#include +#include + +#include "core.h" + +namespace olive { + +ConfigDialogBase::ConfigDialogBase(QWidget* parent) : + QDialog(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + + QSplitter* splitter = new QSplitter(); + splitter->setChildrenCollapsible(false); + layout->addWidget(splitter); + + list_widget_ = new QListWidget(); + + preference_pane_stack_ = new QStackedWidget(this); + + splitter->addWidget(list_widget_); + splitter->addWidget(preference_pane_stack_); + + QDialogButtonBox* button_box = new QDialogButtonBox(this); + button_box->setOrientation(Qt::Horizontal); + button_box->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + + layout->addWidget(button_box); + + connect(button_box, &QDialogButtonBox::accepted, this, &ConfigDialogBase::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &ConfigDialogBase::reject); + + connect(list_widget_, + &QListWidget::currentRowChanged, + preference_pane_stack_, + &QStackedWidget::setCurrentIndex); +} + +void ConfigDialogBase::accept() +{ + foreach (ConfigDialogBaseTab* tab, tabs_) { + if (!tab->Validate()) { + return; + } + } + + MultiUndoCommand* command = new MultiUndoCommand(); + + foreach (ConfigDialogBaseTab* tab, tabs_) { + tab->Accept(command); + } + + if (command->child_count() == 0) { + delete command; + } else { + Core::instance()->undo_stack()->push(command); + } + + QDialog::accept(); +} + +void ConfigDialogBase::AddTab(ConfigDialogBaseTab *tab, const QString &title) +{ + list_widget_->addItem(title); + preference_pane_stack_->addWidget(tab); + + tabs_.append(tab); +} + +} diff --git a/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp b/app/dialog/configbase/configdialogbase.h similarity index 55% rename from app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp rename to app/dialog/configbase/configdialogbase.h index b11289edf..73de960ff 100644 --- a/app/dialog/footageproperties/streamproperties/audiostreamproperties.cpp +++ b/app/dialog/configbase/configdialogbase.h @@ -18,18 +18,41 @@ ***/ -#include "audiostreamproperties.h" +#ifndef CONFIGBASE_H +#define CONFIGBASE_H + +#include +#include +#include + +#include "configdialogbasetab.h" namespace olive { -AudioStreamProperties::AudioStreamProperties(AudioStream *stream) : - stream_(stream) +class ConfigDialogBase : public QDialog { -} + Q_OBJECT +public: + ConfigDialogBase(QWidget* parent = nullptr); -void AudioStreamProperties::Accept(MultiUndoCommand*) -{ - Q_UNUSED(stream_) -} +private slots: + /** + * @brief Override of accept to save preferences to Config. + */ + virtual void accept() override; + +protected: + void AddTab(ConfigDialogBaseTab* tab, const QString& title); + +private: + QListWidget* list_widget_; + + QStackedWidget* preference_pane_stack_; + + QList tabs_; + +}; } + +#endif // CONFIGBASE_H diff --git a/app/dialog/preferences/tabs/preferencestab.cpp b/app/dialog/configbase/configdialogbasetab.cpp similarity index 91% rename from app/dialog/preferences/tabs/preferencestab.cpp rename to app/dialog/configbase/configdialogbasetab.cpp index 4dc6d46d8..7081abdb9 100644 --- a/app/dialog/preferences/tabs/preferencestab.cpp +++ b/app/dialog/configbase/configdialogbasetab.cpp @@ -18,11 +18,11 @@ ***/ -#include "preferencestab.h" +#include "configdialogbasetab.h" namespace olive { -bool PreferencesTab::Validate() +bool ConfigDialogBaseTab::Validate() { return true; } diff --git a/app/dialog/preferences/tabs/preferencestab.h b/app/dialog/configbase/configdialogbasetab.h similarity index 84% rename from app/dialog/preferences/tabs/preferencestab.h rename to app/dialog/configbase/configdialogbasetab.h index 8bfaec15d..17318e5de 100644 --- a/app/dialog/preferences/tabs/preferencestab.h +++ b/app/dialog/configbase/configdialogbasetab.h @@ -24,17 +24,18 @@ #include #include "config/config.h" +#include "undo/undocommand.h" namespace olive { -class PreferencesTab : public QWidget +class ConfigDialogBaseTab : public QWidget { public: - PreferencesTab() = default; + ConfigDialogBaseTab() = default; virtual bool Validate(); - virtual void Accept() = 0; + virtual void Accept(MultiUndoCommand *parent) = 0; }; } diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index b122e8985..fb7dead2c 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -39,10 +39,9 @@ namespace olive { -ExportDialog::ExportDialog(ViewerOutput *viewer_node, TimelinePoints *points, QWidget *parent) : +ExportDialog::ExportDialog(Sequence *sequence, QWidget *parent) : QDialog(parent), - viewer_node_(viewer_node), - points_(points) + sequence_(sequence) { QHBoxLayout* layout = new QHBoxLayout(this); @@ -105,9 +104,8 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, TimelinePoints *points, QW range_combobox_ = new QComboBox(); range_combobox_->addItem(tr("Entire Sequence")); range_combobox_->addItem(tr("In to Out")); - if (!points_) { - range_combobox_->setEnabled(false); - } + range_combobox_->setEnabled(sequence_->timeline_points()->workarea()->enabled()); + preferences_layout->addWidget(range_combobox_, row, 1, 1, 3); row++; @@ -138,7 +136,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, TimelinePoints *points, QW QTabWidget* preferences_tabs = new QTabWidget(); QScrollArea* video_area = new QScrollArea(); - color_manager_ = static_cast(viewer_node_->parent())->project()->color_manager(); + color_manager_ = sequence_->project()->color_manager(); video_tab_ = new ExportVideoTab(color_manager_); video_area->setWidgetResizable(true); video_area->setWidget(video_tab_); @@ -190,18 +188,18 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, TimelinePoints *points, QW &ExportDialog::FormatChanged); FormatChanged(ExportFormat::kFormatMPEG4); - video_tab_->width_slider()->SetValue(viewer_node_->video_params().width()); - video_tab_->width_slider()->SetDefaultValue(viewer_node_->video_params().width()); - video_tab_->height_slider()->SetValue(viewer_node_->video_params().height()); - video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height()); - video_tab_->frame_rate_combobox()->SetFrameRate(viewer_node_->video_params().time_base().flipped()); - video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(viewer_node_->video_params().pixel_aspect_ratio()); + video_tab_->width_slider()->SetValue(sequence_->video_params().width()); + video_tab_->width_slider()->SetDefaultValue(sequence_->video_params().width()); + video_tab_->height_slider()->SetValue(sequence_->video_params().height()); + video_tab_->height_slider()->SetDefaultValue(sequence_->video_params().height()); + video_tab_->frame_rate_combobox()->SetFrameRate(sequence_->video_params().time_base().flipped()); + video_tab_->pixel_aspect_combobox()->SetPixelAspectRatio(sequence_->video_params().pixel_aspect_ratio()); video_tab_->pixel_format_field()->SetPixelFormat(static_cast(Config::Current()["OnlinePixelFormat"].toInt())); - video_tab_->interlaced_combobox()->SetInterlaceMode(viewer_node_->video_params().interlacing()); - audio_tab_->sample_rate_combobox()->SetSampleRate(viewer_node_->audio_params().sample_rate()); - audio_tab_->channel_layout_combobox()->SetChannelLayout(viewer_node_->audio_params().channel_layout()); + video_tab_->interlaced_combobox()->SetInterlaceMode(sequence_->video_params().interlacing()); + audio_tab_->sample_rate_combobox()->SetSampleRate(sequence_->audio_params().sample_rate()); + audio_tab_->channel_layout_combobox()->SetChannelLayout(sequence_->audio_params().channel_layout()); - video_aspect_ratio_ = static_cast(viewer_node_->video_params().width()) / static_cast(viewer_node_->video_params().height()); + video_aspect_ratio_ = static_cast(sequence_->video_params().width()) / static_cast(sequence_->video_params().height()); connect(video_tab_->width_slider(), &IntegerSlider::ValueChanged, @@ -229,8 +227,8 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, TimelinePoints *points, QW static_cast(&ViewerWidget::SetColorTransform)); // Set viewer to view the node - preview_viewer_->ConnectViewerNode(viewer_node_); - preview_viewer_->ruler()->ConnectTimelinePoints(points_); + preview_viewer_->ConnectViewerNode(sequence_); + preview_viewer_->ruler()->ConnectTimelinePoints(sequence_->timeline_points()); preview_viewer_->SetColorMenuEnabled(false); preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace()); } @@ -317,7 +315,7 @@ void ExportDialog::StartExport() return; } - ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams()); + ExportTask* task = new ExportTask(sequence_, color_manager_, GenerateParams()); TaskDialog* td = new TaskDialog(task, tr("Export"), this); connect(td, &TaskDialog::TaskSucceeded, this, &ExportDialog::ExportFinished); td->open(); @@ -430,8 +428,7 @@ void ExportDialog::LoadPresets() void ExportDialog::SetDefaultFilename() { - Sequence* s = static_cast(viewer_node_->parent()); - Project* p = s->project(); + Project* p = sequence_->project(); QDir doc_location; @@ -441,7 +438,7 @@ void ExportDialog::SetDefaultFilename() doc_location = QFileInfo(p->filename()).dir(); } - QString file_location = doc_location.filePath(s->name()); + QString file_location = doc_location.filePath(sequence_->GetLabel()); filename_edit_->setText(file_location); } @@ -462,12 +459,11 @@ ExportParams ExportDialog::GenerateParams() const ExportParams params; params.SetFilename(filename_edit_->text().trimmed()); - params.SetExportLength(viewer_node_->GetLength()); + params.SetExportLength(sequence_->GetLength()); if (range_combobox_->currentIndex() == kRangeInToOut - && points_ - && points_->workarea()->enabled()) { - params.set_custom_range(points_->workarea()->range()); + && sequence_->timeline_points()->workarea()->enabled()) { + params.set_custom_range(sequence_->timeline_points()->workarea()->range()); } if (video_tab_->scaling_method_combobox()->isEnabled()) { @@ -504,8 +500,8 @@ void ExportDialog::UpdateViewerDimensions() QMatrix4x4 transform = ExportParams::GenerateMatrix(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), - viewer_node_->video_params().width(), - viewer_node_->video_params().height(), + sequence_->video_params().width(), + sequence_->video_params().height(), static_cast(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue())); diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 37144b6b3..71f7196c9 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -40,7 +40,7 @@ class ExportDialog : public QDialog { Q_OBJECT public: - ExportDialog(ViewerOutput* viewer_node, TimelinePoints* points = nullptr, QWidget* parent = nullptr); + ExportDialog(Sequence* sequence, QWidget* parent = nullptr); protected: virtual void closeEvent(QCloseEvent *e) override; @@ -51,8 +51,7 @@ private: ExportParams GenerateParams() const; - ViewerOutput* viewer_node_; - TimelinePoints* points_; + Sequence* sequence_; ExportFormat::Format previously_selected_format_; diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp deleted file mode 100644 index d1e160866..000000000 --- a/app/dialog/footageproperties/footageproperties.cpp +++ /dev/null @@ -1,197 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "footageproperties.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "core.h" -#include "render/colormanager.h" -#include "streamproperties/audiostreamproperties.h" -#include "streamproperties/videostreamproperties.h" - -namespace olive { - -FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *footage) : - QDialog(parent), - footage_(footage) -{ - QGridLayout* layout = new QGridLayout(this); - - setWindowTitle(tr("\"%1\" Properties").arg(footage_->name())); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - int row = 0; - - layout->addWidget(new QLabel(tr("Name:")), row, 0); - - footage_name_field_ = new QLineEdit(footage_->name()); - layout->addWidget(footage_name_field_, row, 1); - row++; - - layout->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); - row++; - - track_list = new QListWidget(); - layout->addWidget(track_list, row, 0, 1, 2); - - row++; - - stacked_widget_ = new QStackedWidget(); - layout->addWidget(stacked_widget_, row, 0, 1, 2); - - int first_usable_stream = -1; - - for (int i=0;istreams().size();i++) { - Stream* stream = footage_->stream(i); - - QListWidgetItem* item = new QListWidgetItem(stream->description(), track_list); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(stream->enabled() ? Qt::Checked : Qt::Unchecked); - track_list->addItem(item); - - switch (stream->type()) { - case Stream::kVideo: - stacked_widget_->addWidget(new VideoStreamProperties(static_cast(stream))); - break; - case Stream::kAudio: - stacked_widget_->addWidget(new AudioStreamProperties(static_cast(stream))); - break; - default: - stacked_widget_->addWidget(new StreamProperties()); - } - - if (first_usable_stream == -1 - && (stream->type() == Stream::kVideo - || stream->type() == Stream::kAudio)) { - first_usable_stream = i; - } - } - - row++; - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - buttons->setCenterButtons(true); - layout->addWidget(buttons, row, 0, 1, 2); - - connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); - - connect(track_list, &QListWidget::currentRowChanged, stacked_widget_, &QStackedWidget::setCurrentIndex); - - // Auto-select first item that actually has properties - if (first_usable_stream >= 0) { - track_list->setCurrentRow(first_usable_stream); - } - track_list->setFocus(); -} - -void FootagePropertiesDialog::accept() { - // Perform sanity check on all pages - for (int i=0;icount();i++) { - if (!static_cast(stacked_widget_->widget(i))->SanityCheck()) { - // Switch to the failed panel in question - stacked_widget_->setCurrentIndex(i); - - // Do nothing (it's up to the property panel itself to throw the error message) - return; - } - } - - MultiUndoCommand* command = new MultiUndoCommand(); - - if (footage_->name() != footage_name_field_->text()) { - command->add_child(new FootageChangeCommand(footage_, - footage_name_field_->text())); - } - - for (int i=0;istreams().size();i++) { - bool stream_enabled = (track_list->item(i)->checkState() == Qt::Checked); - - if (footage_->stream(i)->enabled() != stream_enabled) { - command->add_child(new StreamEnableChangeCommand(footage_->stream(i), - stream_enabled)); - } - } - - for (int i=0;icount();i++) { - static_cast(stacked_widget_->widget(i))->Accept(command); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); - - QDialog::accept(); -} - -FootagePropertiesDialog::FootageChangeCommand::FootageChangeCommand(Footage *footage, const QString &name) : - footage_(footage), - new_name_(name) -{ -} - -Project *FootagePropertiesDialog::FootageChangeCommand::GetRelevantProject() const -{ - return footage_->project(); -} - -void FootagePropertiesDialog::FootageChangeCommand::redo() -{ - old_name_ = footage_->name(); - - footage_->set_name(new_name_); -} - -void FootagePropertiesDialog::FootageChangeCommand::undo() -{ - footage_->set_name(old_name_); -} - -FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(Stream *stream, bool enabled) : - stream_(stream), - old_enabled_(stream->enabled()), - new_enabled_(enabled) -{ -} - -Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const -{ - return stream_->footage()->project(); -} - -void FootagePropertiesDialog::StreamEnableChangeCommand::redo() -{ - stream_->set_enabled(new_enabled_); -} - -void FootagePropertiesDialog::StreamEnableChangeCommand::undo() -{ - stream_->set_enabled(old_enabled_); -} - -} diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h deleted file mode 100644 index b5b177e64..000000000 --- a/app/dialog/footageproperties/footageproperties.h +++ /dev/null @@ -1,133 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef MEDIAPROPERTIESDIALOG_H -#define MEDIAPROPERTIESDIALOG_H - -#include -#include -#include -#include -#include -#include -#include - -#include "project/item/footage/footage.h" -#include "undo/undocommand.h" - -namespace olive { - -/** - * @brief The MediaPropertiesDialog class - * - * A dialog for setting properties on Media. This can be loaded from any part of the application provided it's given - * a valid Media object. - */ -class FootagePropertiesDialog : public QDialog { - Q_OBJECT -public: - /** - * @brief MediaPropertiesDialog Constructor - * - * @param parent - * - * QWidget parent. Usually MainWindow or Project panel. - * - * @param i - * - * Media object to set properties for. - */ - FootagePropertiesDialog(QWidget *parent, Footage* footage); -private: - class FootageChangeCommand : public UndoCommand { - public: - FootageChangeCommand(Footage* footage, - const QString& name); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - virtual void undo() override; - - private: - Footage* footage_; - - QString new_name_; - QString old_name_; - }; - - class StreamEnableChangeCommand : public UndoCommand { - public: - StreamEnableChangeCommand(Stream* stream, - bool enabled); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - virtual void undo() override; - - private: - Stream* stream_; - - bool old_enabled_; - bool new_enabled_; - }; - - /** - * @brief Stack of widgets that changes based on whether the stream is a video or audio stream - */ - QStackedWidget* stacked_widget_; - - /** - * @brief ComboBox for interlacing setting - */ - QComboBox* interlacing_box; - - /** - * @brief Media name text field - */ - QLineEdit* footage_name_field_; - - /** - * @brief Internal pointer to Media object (set in constructor) - */ - Footage* footage_; - - /** - * @brief A list widget for listing the tracks in Media - */ - QListWidget* track_list; - - /** - * @brief Frame rate to conform to - */ - QDoubleSpinBox* conform_fr; - -private slots: - /** - * @brief Overridden accept function for saving the properties back to the Media class - */ - void accept(); - -}; - -} - -#endif // MEDIAPROPERTIESDIALOG_H diff --git a/app/dialog/footageproperties/streamproperties/CMakeLists.txt b/app/dialog/footageproperties/streamproperties/CMakeLists.txt deleted file mode 100644 index 3228e9520..000000000 --- a/app/dialog/footageproperties/streamproperties/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2020 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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - dialog/footageproperties/streamproperties/streamproperties.h - dialog/footageproperties/streamproperties/streamproperties.cpp - dialog/footageproperties/streamproperties/audiostreamproperties.h - dialog/footageproperties/streamproperties/audiostreamproperties.cpp - dialog/footageproperties/streamproperties/videostreamproperties.h - dialog/footageproperties/streamproperties/videostreamproperties.cpp - PARENT_SCOPE -) diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.cpp b/app/dialog/footageproperties/streamproperties/streamproperties.cpp deleted file mode 100644 index 96f3bbd5a..000000000 --- a/app/dialog/footageproperties/streamproperties/streamproperties.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "streamproperties.h" - -namespace olive { - -StreamProperties::StreamProperties(QWidget *parent) : - QWidget(parent) -{ -} - -} diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp deleted file mode 100644 index 69bcde5bf..000000000 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ /dev/null @@ -1,251 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "videostreamproperties.h" - -#include -#include -#include -#include -#include - -#include "common/ocioutils.h" -#include "core.h" -#include "project/item/footage/footage.h" -#include "project/project.h" -#include "undo/undostack.h" - -namespace olive { - -VideoStreamProperties::VideoStreamProperties(VideoStream *stream) : - stream_(stream), - video_premultiply_alpha_(nullptr) -{ - QGridLayout* video_layout = new QGridLayout(this); - video_layout->setMargin(0); - - int row = 0; - - video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0); - - pixel_aspect_combo_ = new PixelAspectRatioComboBox(); - pixel_aspect_combo_->SetPixelAspectRatio(stream->pixel_aspect_ratio()); - video_layout->addWidget(pixel_aspect_combo_, row, 1); - - row++; - - video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0); - - video_interlace_combo_ = new InterlacedComboBox(); - video_interlace_combo_->SetInterlaceMode(stream->interlacing()); - - video_layout->addWidget(video_interlace_combo_, row, 1); - - row++; - - video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0); - - video_color_space_ = new QComboBox(); - OCIO::ConstConfigRcPtr config = stream->footage()->project()->color_manager()->GetConfig(); - int number_of_colorspaces = config->getNumColorSpaces(); - - video_color_space_->addItem(tr("Default (%1)").arg(stream->footage()->project()->color_manager()->GetDefaultInputColorSpace())); - - for (int i=0;igetColorSpaceNameByIndex(i); - - video_color_space_->addItem(colorspace); - } - - video_color_space_->setCurrentText(stream_->colorspace(false)); - - video_layout->addWidget(video_color_space_, row, 1); - - if (stream->channel_count() == VideoParams::kRGBAChannelCount) { - row++; - - video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha")); - video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha()); - video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2); - } - - row++; - - if (stream->video_type() == VideoStream::kVideoTypeImageSequence) { - QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence")); - QGridLayout* imgseq_layout = new QGridLayout(imgseq_group); - - int imgseq_row = 0; - - VideoStream* video_stream = static_cast(stream); - - imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0); - - imgseq_start_time_ = new IntegerSlider(); - imgseq_start_time_->SetMinimum(0); - imgseq_start_time_->SetValue(video_stream->start_time()); - imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1); - - imgseq_row++; - - imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0); - - imgseq_end_time_ = new IntegerSlider(); - imgseq_end_time_->SetMinimum(0); - imgseq_end_time_->SetValue(video_stream->start_time() + video_stream->duration() - 1); - imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1); - - imgseq_row++; - - imgseq_layout->addWidget(new QLabel(tr("Frame Rate:")), imgseq_row, 0); - - imgseq_frame_rate_ = new FrameRateComboBox(); - imgseq_frame_rate_->SetFrameRate(video_stream->frame_rate()); - imgseq_layout->addWidget(imgseq_frame_rate_, imgseq_row, 1); - - video_layout->addWidget(imgseq_group, row, 0, 1, 2); - } -} - -void VideoStreamProperties::Accept(MultiUndoCommand *parent) -{ - QString set_colorspace; - - if (video_color_space_->currentIndex() > 0) { - set_colorspace = video_color_space_->currentText(); - } - - if ((video_premultiply_alpha_ && video_premultiply_alpha_->isChecked() != stream_->premultiplied_alpha()) - || set_colorspace != stream_->colorspace(false) - || static_cast(video_interlace_combo_->currentIndex()) != stream_->interlacing() - || pixel_aspect_combo_->GetPixelAspectRatio() != stream_->pixel_aspect_ratio()) { - - parent->add_child(new VideoStreamChangeCommand(stream_, - video_premultiply_alpha_ ? video_premultiply_alpha_->isChecked() : stream_->premultiplied_alpha(), - set_colorspace, - static_cast(video_interlace_combo_->currentIndex()), - pixel_aspect_combo_->GetPixelAspectRatio())); - } - - if (stream_->video_type() == VideoStream::kVideoTypeImageSequence) { - VideoStream* video_stream = static_cast(stream_); - - int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue() + 1; - - if (video_stream->start_time() != imgseq_start_time_->GetValue() - || video_stream->duration() != new_dur - || video_stream->frame_rate() != imgseq_frame_rate_->GetFrameRate()) { - parent->add_child(new ImageSequenceChangeCommand(video_stream, - imgseq_start_time_->GetValue(), - new_dur, - imgseq_frame_rate_->GetFrameRate())); - } - } -} - -bool VideoStreamProperties::SanityCheck() -{ - if (stream_->video_type() == VideoStream::kVideoTypeImageSequence) { - if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) { - QMessageBox::critical(this, - tr("Invalid Configuration"), - tr("Image sequence end index must be a value higher than the start index."), - QMessageBox::Ok); - return false; - } - } - - return true; -} - -VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(VideoStream *stream, - bool premultiplied, - QString colorspace, - VideoParams::Interlacing interlacing, - const rational &pixel_ar) : - stream_(stream), - new_premultiplied_(premultiplied), - new_colorspace_(colorspace), - new_interlacing_(interlacing), - new_pixel_ar_(pixel_ar) -{ -} - -Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const -{ - return stream_->footage()->project(); -} - -void VideoStreamProperties::VideoStreamChangeCommand::redo() -{ - old_premultiplied_ = stream_->premultiplied_alpha(); - old_colorspace_ = stream_->colorspace(false); - old_interlacing_ = stream_->interlacing(); - old_pixel_ar_ = stream_->pixel_aspect_ratio(); - - stream_->set_premultiplied_alpha(new_premultiplied_); - stream_->set_colorspace(new_colorspace_); - stream_->set_interlacing(new_interlacing_); - stream_->set_pixel_aspect_ratio(new_pixel_ar_); -} - -void VideoStreamProperties::VideoStreamChangeCommand::undo() -{ - stream_->set_premultiplied_alpha(old_premultiplied_); - stream_->set_colorspace(old_colorspace_); - stream_->set_interlacing(old_interlacing_); - stream_->set_pixel_aspect_ratio(old_pixel_ar_); -} - -VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(VideoStream *video_stream, int64_t start_index, int64_t duration, const rational &frame_rate) : - video_stream_(video_stream), - new_start_index_(start_index), - new_duration_(duration), - new_frame_rate_(frame_rate) -{ -} - -Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const -{ - return video_stream_->footage()->project(); -} - -void VideoStreamProperties::ImageSequenceChangeCommand::redo() -{ - old_start_index_ = video_stream_->start_time(); - video_stream_->set_start_time(new_start_index_); - - old_duration_ = video_stream_->duration(); - video_stream_->set_duration(new_duration_); - - old_frame_rate_ = video_stream_->frame_rate(); - video_stream_->set_frame_rate(new_frame_rate_); - video_stream_->set_timebase(new_frame_rate_.flipped()); -} - -void VideoStreamProperties::ImageSequenceChangeCommand::undo() -{ - video_stream_->set_start_time(old_start_index_); - video_stream_->set_duration(old_duration_); - video_stream_->set_frame_rate(old_frame_rate_); - video_stream_->set_timebase(old_frame_rate_.flipped()); -} - -} diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h deleted file mode 100644 index 7ae9412bc..000000000 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.h +++ /dev/null @@ -1,143 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef VIDEOSTREAMPROPERTIES_H -#define VIDEOSTREAMPROPERTIES_H - -#include -#include - -#include "project/item/footage/videostream.h" -#include "streamproperties.h" -#include "widget/slider/integerslider.h" -#include "widget/standardcombos/standardcombos.h" - -namespace olive { - -class VideoStreamProperties : public StreamProperties -{ - Q_OBJECT -public: - VideoStreamProperties(VideoStream* stream); - - virtual void Accept(MultiUndoCommand *parent) override; - - virtual bool SanityCheck() override; - -private: - /** - * @brief Attached video stream - */ - VideoStream* stream_; - - /** - * @brief Setting for associated/premultiplied alpha - */ - QCheckBox* video_premultiply_alpha_; - - /** - * @brief Setting for this media's color space - */ - QComboBox* video_color_space_; - - /** - * @brief Setting for video interlacing - */ - InterlacedComboBox* video_interlace_combo_; - - /** - * @brief Sets the start index for image sequences - */ - IntegerSlider* imgseq_start_time_; - - /** - * @brief Sets the end index for image sequences - */ - IntegerSlider* imgseq_end_time_; - - /** - * @brief Sets the frame rate for image sequences - */ - FrameRateComboBox* imgseq_frame_rate_; - - /** - * @brief Sets the pixel aspect ratio of the stream - */ - PixelAspectRatioComboBox* pixel_aspect_combo_; - - class VideoStreamChangeCommand : public UndoCommand { - public: - VideoStreamChangeCommand(VideoStream* stream, - bool premultiplied, - QString colorspace, - VideoParams::Interlacing interlacing, - const rational& pixel_ar); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - virtual void undo() override; - - private: - VideoStream* stream_; - - bool new_premultiplied_; - QString new_colorspace_; - VideoParams::Interlacing new_interlacing_; - rational new_pixel_ar_; - - bool old_premultiplied_; - QString old_colorspace_; - VideoParams::Interlacing old_interlacing_; - rational old_pixel_ar_; - - }; - - class ImageSequenceChangeCommand : public UndoCommand { - public: - ImageSequenceChangeCommand(VideoStream* video_stream, - int64_t start_index, - int64_t duration, - const rational& frame_rate); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - virtual void undo() override; - - private: - VideoStream* video_stream_; - - int64_t new_start_index_; - int64_t old_start_index_; - - int64_t new_duration_; - int64_t old_duration_; - - rational new_frame_rate_; - rational old_frame_rate_; - - }; - -}; - -} - -#endif // VIDEOSTREAMPROPERTIES_H diff --git a/app/dialog/footagerelink/footagerelinkdialog.cpp b/app/dialog/footagerelink/footagerelinkdialog.cpp index 1a840b6aa..7c3cf9c4e 100644 --- a/app/dialog/footagerelink/footagerelinkdialog.cpp +++ b/app/dialog/footagerelink/footagerelinkdialog.cpp @@ -65,7 +65,7 @@ FootageRelinkDialog::FootageRelinkDialog(const QVector &footage, QWid item_actions_layout->addWidget(item_browse_btn); item->setIcon(0, f->icon()); - item->setText(0, f->name()); + item->setText(0, f->GetLabel()); item->setText(1, f->filename()); table_->addTopLevelItem(item); @@ -101,7 +101,7 @@ void FootageRelinkDialog::BrowseForFootage() QFileInfo info(f->filename()); QString new_fn = QFileDialog::getOpenFileName(this, - tr("Relink \"%1\"").arg(f->name()), + tr("Relink \"%1\"").arg(f->GetLabel()), info.absolutePath(), QStringLiteral("%1;;%2 (**)").arg(info.fileName(), tr("All Files"))); diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index c3c34131f..b9bda2442 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -36,66 +36,16 @@ namespace olive { PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) : - QDialog(parent) + ConfigDialogBase(parent) { setWindowTitle(tr("Preferences")); - QVBoxLayout* layout = new QVBoxLayout(this); - - QSplitter* splitter = new QSplitter(); - splitter->setChildrenCollapsible(false); - layout->addWidget(splitter); - - list_widget_ = new QListWidget(); - - preference_pane_stack_ = new QStackedWidget(this); - AddTab(new PreferencesGeneralTab(), tr("General")); AddTab(new PreferencesAppearanceTab(), tr("Appearance")); AddTab(new PreferencesBehaviorTab(), tr("Behavior")); AddTab(new PreferencesDiskTab(), tr("Disk")); AddTab(new PreferencesAudioTab(), tr("Audio")); AddTab(new PreferencesKeyboardTab(main_menu_bar), tr("Keyboard")); - - splitter->addWidget(list_widget_); - splitter->addWidget(preference_pane_stack_); - - QDialogButtonBox* button_box = new QDialogButtonBox(this); - button_box->setOrientation(Qt::Horizontal); - button_box->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - - layout->addWidget(button_box); - - connect(button_box, &QDialogButtonBox::accepted, this, &PreferencesDialog::accept); - connect(button_box, &QDialogButtonBox::rejected, this, &PreferencesDialog::reject); - - connect(list_widget_, - &QListWidget::currentRowChanged, - preference_pane_stack_, - &QStackedWidget::setCurrentIndex); -} - -void PreferencesDialog::accept() -{ - foreach (PreferencesTab* tab, tabs_) { - if (!tab->Validate()) { - return; - } - } - - foreach (PreferencesTab* tab, tabs_) { - tab->Accept(); - } - - QDialog::accept(); -} - -void PreferencesDialog::AddTab(PreferencesTab *tab, const QString &title) -{ - list_widget_->addItem(title); - preference_pane_stack_->addWidget(tab); - - tabs_.append(tab); } } diff --git a/app/dialog/preferences/preferences.h b/app/dialog/preferences/preferences.h index e337c3eeb..b738db6b7 100644 --- a/app/dialog/preferences/preferences.h +++ b/app/dialog/preferences/preferences.h @@ -28,7 +28,7 @@ #include #include -#include "tabs/preferencestab.h" +#include "dialog/configbase/configdialogbase.h" namespace olive { @@ -38,7 +38,7 @@ namespace olive { * A dialog for the global application settings. Mostly an interface for Config. Can be loaded from any part of the * application. */ -class PreferencesDialog : public QDialog +class PreferencesDialog : public ConfigDialogBase { Q_OBJECT @@ -50,22 +50,7 @@ public: * * QWidget parent. Usually MainWindow. */ - explicit PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar); - -private slots: - /** - * @brief Override of accept to save preferences to Config. - */ - virtual void accept() override; - -private: - void AddTab(PreferencesTab* tab, const QString& title); - - QListWidget* list_widget_; - - QStackedWidget* preference_pane_stack_; - - QList tabs_; + PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar); }; diff --git a/app/dialog/preferences/tabs/CMakeLists.txt b/app/dialog/preferences/tabs/CMakeLists.txt index 37bfe7a5a..3241a1a09 100644 --- a/app/dialog/preferences/tabs/CMakeLists.txt +++ b/app/dialog/preferences/tabs/CMakeLists.txt @@ -28,7 +28,5 @@ set(OLIVE_SOURCES dialog/preferences/tabs/preferencesaudiotab.cpp dialog/preferences/tabs/preferenceskeyboardtab.h dialog/preferences/tabs/preferenceskeyboardtab.cpp - dialog/preferences/tabs/preferencestab.h - dialog/preferences/tabs/preferencestab.cpp PARENT_SCOPE ) diff --git a/app/dialog/preferences/tabs/preferencesappearancetab.cpp b/app/dialog/preferences/tabs/preferencesappearancetab.cpp index 2b3ee7cb6..6bc54e753 100644 --- a/app/dialog/preferences/tabs/preferencesappearancetab.cpp +++ b/app/dialog/preferences/tabs/preferencesappearancetab.cpp @@ -86,13 +86,15 @@ PreferencesAppearanceTab::PreferencesAppearanceTab() layout->addStretch(); } -void PreferencesAppearanceTab::Accept() +void PreferencesAppearanceTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + QString style_path = style_combobox_->currentData().toString(); if (style_path != StyleManager::GetStyle()) { StyleManager::SetStyle(style_path); - Config::Current()["Style"] = style_path; + Config::Current()[QStringLiteral("Style")] = style_path; } for (int i=0; i #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" #include "ui/style/style.h" #include "widget/colorlabelmenu/colorcodingcombobox.h" namespace olive { -class PreferencesAppearanceTab : public PreferencesTab +class PreferencesAppearanceTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesAppearanceTab(); - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private: /** diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.cpp b/app/dialog/preferences/tabs/preferencesaudiotab.cpp index 66c34a3d9..7ee42c0fa 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.cpp +++ b/app/dialog/preferences/tabs/preferencesaudiotab.cpp @@ -91,8 +91,10 @@ PreferencesAudioTab::PreferencesAudioTab() connect(AudioManager::instance(), &AudioManager::InputListReady, this, &PreferencesAudioTab::RetrieveInputList); } -void PreferencesAudioTab::Accept() +void PreferencesAudioTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + // FIXME: Qt documentation states that QAudioDeviceInfo::deviceName() is a "unique identifiers", which would make them // ideal for saving in preferences, but in practice they don't actually appear to be unique. // See: https://bugreports.qt.io/browse/QTBUG-16841 diff --git a/app/dialog/preferences/tabs/preferencesaudiotab.h b/app/dialog/preferences/tabs/preferencesaudiotab.h index e0cb6fa7b..ba0eed3eb 100644 --- a/app/dialog/preferences/tabs/preferencesaudiotab.h +++ b/app/dialog/preferences/tabs/preferencesaudiotab.h @@ -25,17 +25,17 @@ #include #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" namespace olive { -class PreferencesAudioTab : public PreferencesTab +class PreferencesAudioTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesAudioTab(); - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private: /** diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 79b6d217c..b27dca71c 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -106,8 +106,10 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() node_group); } -void PreferencesBehaviorTab::Accept() +void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + QMap::const_iterator iterator; for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index 997aee0cc..e7640e123 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -23,17 +23,17 @@ #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" namespace olive { -class PreferencesBehaviorTab : public PreferencesTab +class PreferencesBehaviorTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesBehaviorTab(); - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private: QTreeWidgetItem *AddParent(const QString& text, const QString &tooltip, QTreeWidgetItem *parent = nullptr); diff --git a/app/dialog/preferences/tabs/preferencesdisktab.cpp b/app/dialog/preferences/tabs/preferencesdisktab.cpp index ec1eeefb1..83e2250d6 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.cpp +++ b/app/dialog/preferences/tabs/preferencesdisktab.cpp @@ -107,8 +107,10 @@ bool PreferencesDiskTab::Validate() return true; } -void PreferencesDiskTab::Accept() +void PreferencesDiskTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + if (disk_cache_location_->text() != default_disk_cache_folder_->GetPath()) { default_disk_cache_folder_->SetPath(disk_cache_location_->text()); } diff --git a/app/dialog/preferences/tabs/preferencesdisktab.h b/app/dialog/preferences/tabs/preferencesdisktab.h index 6f4e45771..1eecb9eb9 100644 --- a/app/dialog/preferences/tabs/preferencesdisktab.h +++ b/app/dialog/preferences/tabs/preferencesdisktab.h @@ -25,14 +25,14 @@ #include #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" #include "render/diskmanager.h" #include "widget/slider/floatslider.h" #include "widget/path/pathwidget.h" namespace olive { -class PreferencesDiskTab : public PreferencesTab +class PreferencesDiskTab : public ConfigDialogBaseTab { Q_OBJECT public: @@ -40,7 +40,7 @@ public: virtual bool Validate() override; - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private: PathWidget* disk_cache_location_; diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp index aafe93d70..c26fa94bb 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp @@ -100,8 +100,10 @@ PreferencesGeneralTab::PreferencesGeneralTab() layout->addStretch(); } -void PreferencesGeneralTab::Accept() +void PreferencesGeneralTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + Config::Current()[QStringLiteral("RectifiedWaveforms")] = rectified_waveforms_->isChecked(); Config::Current()[QStringLiteral("Autoscroll")] = autoscroll_method_->currentData(); diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.h b/app/dialog/preferences/tabs/preferencesgeneraltab.h index e48123fc6..5b18cc2ad 100644 --- a/app/dialog/preferences/tabs/preferencesgeneraltab.h +++ b/app/dialog/preferences/tabs/preferencesgeneraltab.h @@ -25,19 +25,19 @@ #include #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" #include "project/item/sequence/sequence.h" #include "widget/slider/floatslider.h" namespace olive { -class PreferencesGeneralTab : public PreferencesTab +class PreferencesGeneralTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesGeneralTab(); - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private: void AddLanguage(const QString& locale_name); diff --git a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp index 22ab4f652..1a4354999 100644 --- a/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp +++ b/app/dialog/preferences/tabs/preferenceskeyboardtab.cpp @@ -71,8 +71,10 @@ PreferencesKeyboardTab::PreferencesKeyboardTab(QMenuBar *menubar) setup_kbd_shortcuts(menubar); } -void PreferencesKeyboardTab::Accept() +void PreferencesKeyboardTab::Accept(MultiUndoCommand *command) { + Q_UNUSED(command) + // Save keyboard shortcuts for (int i=0;iset_action_shortcut(); diff --git a/app/dialog/preferences/tabs/preferenceskeyboardtab.h b/app/dialog/preferences/tabs/preferenceskeyboardtab.h index 723d95e44..ef50aefb4 100644 --- a/app/dialog/preferences/tabs/preferenceskeyboardtab.h +++ b/app/dialog/preferences/tabs/preferenceskeyboardtab.h @@ -24,18 +24,18 @@ #include #include -#include "preferencestab.h" +#include "dialog/configbase/configdialogbase.h" #include "../keysequenceeditor.h" namespace olive { -class PreferencesKeyboardTab : public PreferencesTab +class PreferencesKeyboardTab : public ConfigDialogBaseTab { Q_OBJECT public: PreferencesKeyboardTab(QMenuBar* menubar); - virtual void Accept() override; + virtual void Accept(MultiUndoCommand* command) override; private slots: /** diff --git a/app/dialog/sequence/presetmanager.h b/app/dialog/sequence/presetmanager.h index 1ac467a9e..3ef752f2c 100644 --- a/app/dialog/sequence/presetmanager.h +++ b/app/dialog/sequence/presetmanager.h @@ -21,6 +21,7 @@ #ifndef PRESETMANAGER_H #define PRESETMANAGER_H +#include #include #include #include @@ -62,8 +63,6 @@ private: }; -using PresetPtr = std::shared_ptr; - template class PresetManager { @@ -81,7 +80,7 @@ public: if (reader.name() == QStringLiteral("presets")) { while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("preset")) { - PresetPtr p = std::make_shared(); + Preset* p = new T(); p->Load(&reader); @@ -111,7 +110,7 @@ public: writer.writeStartElement(QStringLiteral("presets")); - foreach (PresetPtr p, custom_preset_data_) { + foreach (Preset* p, custom_preset_data_) { writer.writeStartElement(QStringLiteral("preset")); p->Save(&writer); @@ -158,7 +157,7 @@ public: return start; } - bool SavePreset(PresetPtr preset) + bool SavePreset(Preset* preset) { QString preset_name; int existing_preset; @@ -205,7 +204,7 @@ public: return QDir(FileFunctions::GetConfigurationLocation()).filePath(preset_name_); } - PresetPtr GetPreset(int index) + Preset* GetPreset(int index) { return custom_preset_data_.at(index); } @@ -220,13 +219,13 @@ public: return custom_preset_data_.size(); } - const QVector& GetPresetData() const + const QVector& GetPresetData() const { return custom_preset_data_; } private: - QVector custom_preset_data_; + QVector custom_preset_data_; QString preset_name_; diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index ed1e823af..08705e7b9 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -78,11 +78,11 @@ SequenceDialog::SequenceDialog(Sequence* s, Type t, QWidget* parent) : setWindowTitle(tr("New Sequence")); break; case kExisting: - setWindowTitle(tr("Editing \"%1\"").arg(sequence_->name())); + setWindowTitle(tr("Editing \"%1\"").arg(sequence_->GetLabel())); break; } - name_field_->setText(sequence_->name()); + name_field_->setText(sequence_->GetLabel()); } void SequenceDialog::SetUndoable(bool u) @@ -130,7 +130,7 @@ void SequenceDialog::accept() // Set sequence values directly with no undo command sequence_->set_video_params(video_params); sequence_->set_audio_params(audio_params); - sequence_->set_name(name_field_->text()); + sequence_->SetLabel(name_field_->text()); } QDialog::accept(); @@ -146,7 +146,7 @@ SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, new_name_(name), old_video_params_(s->video_params()), old_audio_params_(s->audio_params()), - old_name_(s->name()) + old_name_(s->GetLabel()) { } @@ -159,14 +159,14 @@ void SequenceDialog::SequenceParamCommand::redo() { sequence_->set_video_params(new_video_params_); sequence_->set_audio_params(new_audio_params_); - sequence_->set_name(new_name_); + sequence_->SetLabel(new_name_); } void SequenceDialog::SequenceParamCommand::undo() { sequence_->set_video_params(old_video_params_); sequence_->set_audio_params(old_audio_params_); - sequence_->set_name(old_name_); + sequence_->SetLabel(old_name_); } } diff --git a/app/dialog/sequence/sequencedialogpresettab.cpp b/app/dialog/sequence/sequencedialogpresettab.cpp index a75e35479..69671bb10 100644 --- a/app/dialog/sequence/sequencedialogpresettab.cpp +++ b/app/dialog/sequence/sequencedialogpresettab.cpp @@ -79,12 +79,19 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) : } } +SequenceDialogPresetTab::~SequenceDialogPresetTab() +{ + qDeleteAll(child_presets_); +} + void SequenceDialogPresetTab::SaveParametersAsPreset(SequencePreset preset) { - PresetPtr preset_ptr = std::make_shared(preset); + Preset* preset_ptr = new SequencePreset(preset); if (SavePreset(preset_ptr)) { AddCustomItem(my_presets_folder_, preset_ptr, GetNumberOfPresets() - 1); + } else { + delete preset_ptr; } } @@ -205,19 +212,19 @@ QTreeWidgetItem *SequenceDialogPresetTab::GetSelectedCustomPreset() return nullptr; } -void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, PresetPtr preset, const QString& description) +void SequenceDialogPresetTab::AddStandardItem(QTreeWidgetItem *folder, Preset* preset, const QString& description) { int index = default_preset_data_.size(); default_preset_data_.append(preset); AddItemInternal(folder, preset, false, index, description); } -void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, PresetPtr preset, int index, const QString &description) +void SequenceDialogPresetTab::AddCustomItem(QTreeWidgetItem *folder, Preset* preset, int index, const QString &description) { AddItemInternal(folder, preset, true, index, description); } -void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, PresetPtr preset, bool is_custom, int index, const QString &description) +void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, Preset* preset, bool is_custom, int index, const QString &description) { QTreeWidgetItem* item = new QTreeWidgetItem(); @@ -228,6 +235,8 @@ void SequenceDialogPresetTab::AddItemInternal(QTreeWidgetItem *folder, PresetPtr item->setData(0, kDataPresetIsCustomRole, is_custom); item->setData(0, kDataPresetDataRole, index); + child_presets_.append(preset); + folder->addChild(item); } @@ -238,11 +247,11 @@ void SequenceDialogPresetTab::SelectedItemChanged(QTreeWidgetItem* current, QTre if (current->data(0, kDataIsPreset).toBool()) { int preset_index = current->data(0, kDataPresetDataRole).toInt(); - PresetPtr preset_data = (current->data(0, kDataPresetIsCustomRole).toBool()) + Preset* preset_data = (current->data(0, kDataPresetIsCustomRole).toBool()) ? GetPreset(preset_index) : default_preset_data_.at(preset_index); - emit PresetChanged(*static_cast(preset_data.get())); + emit PresetChanged(*static_cast(preset_data)); } } diff --git a/app/dialog/sequence/sequencedialogpresettab.h b/app/dialog/sequence/sequencedialogpresettab.h index c6296673f..557b05b25 100644 --- a/app/dialog/sequence/sequencedialogpresettab.h +++ b/app/dialog/sequence/sequencedialogpresettab.h @@ -36,6 +36,8 @@ class SequenceDialogPresetTab : public QWidget, public PresetManager default_preset_data_; + QVector default_preset_data_; + + QVector child_presets_; private slots: void SelectedItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); diff --git a/app/dialog/sequence/sequencepreset.h b/app/dialog/sequence/sequencepreset.h index f6fc052b9..7b4a22356 100644 --- a/app/dialog/sequence/sequencepreset.h +++ b/app/dialog/sequence/sequencepreset.h @@ -57,20 +57,20 @@ public: SetName(name); } - static PresetPtr Create(const QString& name, - int width, - int height, - const rational& frame_rate, - const rational& pixel_aspect, - VideoParams::Interlacing interlacing, - int sample_rate, - uint64_t channel_layout, - int preview_divider, - VideoParams::Format preview_format) + static Preset* Create(const QString& name, + int width, + int height, + const rational& frame_rate, + const rational& pixel_aspect, + VideoParams::Interlacing interlacing, + int sample_rate, + uint64_t channel_layout, + int preview_divider, + VideoParams::Format preview_format) { - return std::make_shared(name, width, height, frame_rate, pixel_aspect, - interlacing, sample_rate, channel_layout, - preview_divider, preview_format); + return new SequencePreset(name, width, height, frame_rate, pixel_aspect, + interlacing, sample_rate, channel_layout, + preview_divider, preview_format); } virtual void Load(QXmlStreamReader* reader) override diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp index c07e5ebba..4ce6ec91b 100644 --- a/app/node/block/block.cpp +++ b/app/node/block/block.cpp @@ -209,7 +209,7 @@ void Block::Retranslate() SetInputName(kSpeedInput, tr("Speed")); } -void Block::Hash(QCryptographicHash &, const rational &) const +void Block::Hash(const QString &, QCryptographicHash &, const rational &) const { // A block does nothing by default, so we hash nothing } diff --git a/app/node/block/block.h b/app/node/block/block.h index 8c5e883b9..cb1890156 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -149,7 +149,7 @@ public: return block_links_; } - virtual void Hash(QCryptographicHash &hash, const rational &time) const override; + virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override; static const QString kLengthInput; static const QString kMediaInInput; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index efb598988..7244e929e 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -114,12 +114,12 @@ void ClipBlock::Retranslate() SetInputName(kBufferIn, tr("Buffer")); } -void ClipBlock::Hash(QCryptographicHash &hash, const rational &time) const +void ClipBlock::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const { if (IsInputConnected(kBufferIn)) { rational t = InputTimeAdjustment(kBufferIn, -1, TimeRange(time, time)).in(); - GetConnectedNode(kBufferIn)->Hash(hash, t); + GetConnectedNode(kBufferIn)->Hash(output, hash, t); } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 16b022c64..827c19b16 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -52,7 +52,7 @@ public: virtual void Retranslate() override; - virtual void Hash(QCryptographicHash &hash, const rational &time) const override; + virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override; static const QString kBufferIn; diff --git a/app/node/block/transition/transition.cpp b/app/node/block/transition/transition.cpp index 822b84192..be678c6ed 100644 --- a/app/node/block/transition/transition.cpp +++ b/app/node/block/transition/transition.cpp @@ -121,9 +121,9 @@ double TransitionBlock::GetInProgress(const double &time) const return clamp((GetInternalTransitionTime(time) - out_offset().toDouble()) / in_offset().toDouble(), 0.0, 1.0); } -void TransitionBlock::Hash(QCryptographicHash &hash, const rational &time) const +void TransitionBlock::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const { - Node::Hash(hash, time); + Node::Hash(output, hash, time); double time_dbl = time.toDouble(); double all_prog = GetTotalProgress(time_dbl); diff --git a/app/node/block/transition/transition.h b/app/node/block/transition/transition.h index a02b3d7c1..ae41378cc 100644 --- a/app/node/block/transition/transition.h +++ b/app/node/block/transition/transition.h @@ -45,7 +45,7 @@ public: double GetOutProgress(const double &time) const; double GetInProgress(const double &time) const; - virtual void Hash(QCryptographicHash& hash, const rational &time) const override; + virtual void Hash(const QString& output, QCryptographicHash& hash, const rational &time) const override; virtual NodeValueTable Value(const QString& output, NodeValueDatabase &value) const override; diff --git a/app/node/factory.cpp b/app/node/factory.cpp index f6c1f8443..c99e1f209 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -20,6 +20,8 @@ #include "factory.h" +#include + #include "audio/pan/pan.h" #include "audio/volume/volume.h" #include "block/clip/clip.h" @@ -35,13 +37,15 @@ #include "filter/blur/blur.h" #include "filter/mosaic/mosaicfilternode.h" #include "filter/stroke/stroke.h" -#include "input/media/media.h" #include "input/time/timeinput.h" #include "math/math/math.h" #include "math/merge/merge.h" #include "math/trigonometry/trigonometry.h" #include "output/track/track.h" #include "output/viewer/viewer.h" +#include "project/item/folder/folder.h" +#include "project/item/footage/footage.h" +#include "project/item/sequence/sequence.h" namespace olive { QList NodeFactory::library_; @@ -192,8 +196,6 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new MatrixGenerator(); case kTransformDistort: return new TransformDistortNode(); - case kFootageInput: - return new MediaInput(); case kTrackOutput: return new Track(); case kViewerOutput: @@ -226,6 +228,12 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new MosaicFilterNode(); case kCropDistort: return new CropDistortNode(); + case kProjectFootage: + return new Footage(); + case kProjectFolder: + return new Folder(); + case kProjectSequence: + return new Sequence(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 4ab3c43d6..78ef3bfbe 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -38,7 +38,6 @@ public: kPolygonGenerator, kMatrixGenerator, kTransformDistort, - kFootageInput, kTrackOutput, kAudioVolume, kAudioPanning, @@ -54,6 +53,9 @@ public: kDipToColorTransition, kMosaicFilter, kCropDistort, + kProjectFootage, + kProjectFolder, + kProjectSequence, // Count value kInternalNodeCount diff --git a/app/node/graph.cpp b/app/node/graph.cpp index 9036a2d70..52823d69d 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -20,8 +20,12 @@ #include "graph.h" +#include + namespace olive { +#define super QObject + NodeGraph::NodeGraph() { } @@ -35,7 +39,7 @@ void NodeGraph::Clear() void NodeGraph::childEvent(QChildEvent *event) { - Item::childEvent(event); + super::childEvent(event); Node* node = dynamic_cast(event->child()); diff --git a/app/node/graph.h b/app/node/graph.h index 016461b0d..64fb9b995 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -32,7 +32,7 @@ namespace olive { * This doesn't technically need to be a derivative of Item, but since both Item and NodeGraph need * to be QObject derivatives, this simplifies Sequence. */ -class NodeGraph : public Item +class NodeGraph : public QObject { Q_OBJECT public: diff --git a/app/node/input/CMakeLists.txt b/app/node/input/CMakeLists.txt index 95e07c839..5c95bb00d 100644 --- a/app/node/input/CMakeLists.txt +++ b/app/node/input/CMakeLists.txt @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -add_subdirectory(media) add_subdirectory(time) set(OLIVE_SOURCES diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp deleted file mode 100644 index 4c2805c28..000000000 --- a/app/node/input/media/media.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "media.h" - -#include "common/timecodefunctions.h" -#include "common/tohex.h" - -namespace olive { - -const QString MediaInput::kFootageInput = QStringLiteral("footage_in"); - -MediaInput::MediaInput() : - connected_footage_(nullptr) -{ - AddInput(kFootageInput, NodeValue::kFootage, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); -} - -QVector MediaInput::Category() const -{ - return {kCategoryInput}; -} - -Stream *MediaInput::stream() const -{ - return Node::ValueToPtr(GetStandardValue(kFootageInput)); -} - -void MediaInput::SetStream(Stream* s) -{ - SetStandardValue(kFootageInput, Node::PtrToValue(s)); -} - -void MediaInput::Retranslate() -{ - SetInputName(kFootageInput, tr("Media")); -} - -NodeValueTable MediaInput::Value(const QString &output, NodeValueDatabase &value) const -{ - Q_UNUSED(output) - - NodeValueTable table = value.Merge(); - - if (connected_footage_) { - rational media_duration = Timecode::timestamp_to_time(connected_footage_->duration(), - connected_footage_->timebase()); - - table.Push(NodeValue::kRational, QVariant::fromValue(media_duration), this, false, QStringLiteral("length")); - } - - return table; -} - -void MediaInput::InputValueChangedEvent(const QString &input, int element) -{ - Q_UNUSED(element) - - if (input == kFootageInput) { - Stream* new_footage = stream(); - - if (new_footage == connected_footage_) { - return; - } - - if (connected_footage_) { - disconnect(connected_footage_, &Stream::ParametersChanged, this, &MediaInput::FootageParametersChanged); - } - - connected_footage_ = new_footage; - - if (connected_footage_) { - connect(connected_footage_, &Stream::ParametersChanged, this, &MediaInput::FootageParametersChanged); - } - } -} - -void MediaInput::FootageParametersChanged() -{ - InvalidateCache(TimeRange(0, RATIONAL_MAX), kFootageInput); -} - -} diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h deleted file mode 100644 index c3c50bf33..000000000 --- a/app/node/input/media/media.h +++ /dev/null @@ -1,82 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef MEDIAINPUT_H -#define MEDIAINPUT_H - -#include "codec/decoder.h" -#include "node/node.h" -#include "project/item/footage/stream.h" - -namespace olive { - -/** - * @brief A node that imports an image - */ -class MediaInput : public Node -{ - Q_OBJECT -public: - MediaInput(); - - virtual QString Name() const override - { - return tr("Media"); - } - - virtual QString id() const override - { - return QStringLiteral("org.olivevideoeditor.Olive.mediainput"); - } - - virtual QString Description() const override - { - return tr("Import footage into the node graph."); - } - - virtual Node* copy() const override - { - return new MediaInput(); - } - - virtual QVector Category() const override; - - Stream* stream() const; - void SetStream(Stream *s); - - virtual void Retranslate() override; - - virtual NodeValueTable Value(const QString& output, NodeValueDatabase& value) const override; - - static const QString kFootageInput; - -protected: - virtual void InputValueChangedEvent(const QString& input, int element); - - Stream* connected_footage_; - -private slots: - void FootageParametersChanged(); - -}; - -} - -#endif // MEDIAINPUT_H diff --git a/app/node/input/time/timeinput.cpp b/app/node/input/time/timeinput.cpp index 5eadf8b83..f766ec2ce 100644 --- a/app/node/input/time/timeinput.cpp +++ b/app/node/input/time/timeinput.cpp @@ -66,9 +66,9 @@ NodeValueTable TimeInput::Value(const QString &output, NodeValueDatabase &value) return table; } -void TimeInput::Hash(QCryptographicHash &hash, const rational &time) const +void TimeInput::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const { - Node::Hash(hash, time); + Node::Hash(output, hash, time); // Make sure time is hashed hash.addData(NodeValue::ValueToBytes(NodeValue::kRational, QVariant::fromValue(time))); diff --git a/app/node/input/time/timeinput.h b/app/node/input/time/timeinput.h index 85f5b3d39..f8303b5ef 100644 --- a/app/node/input/time/timeinput.h +++ b/app/node/input/time/timeinput.h @@ -40,7 +40,7 @@ public: virtual NodeValueTable Value(const QString& output, NodeValueDatabase& value) const override; - virtual void Hash(QCryptographicHash& hash, const rational& time) const override; + virtual void Hash(const QString& output, QCryptographicHash& hash, const rational& time) const override; }; diff --git a/app/node/math/merge/merge.cpp b/app/node/math/merge/merge.cpp index b6263fe53..823a101b2 100644 --- a/app/node/math/merge/merge.cpp +++ b/app/node/math/merge/merge.cpp @@ -100,7 +100,7 @@ NodeValueTable MergeNode::Value(const QString &output, NodeValueDatabase &value) return table; } -void MergeNode::Hash(QCryptographicHash &hash, const rational &time) const +void MergeNode::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const { // We do some hash optimization here. If only one of the inputs is connected, this node // functions as a passthrough so there's no alteration to the hash. The same is true if the @@ -113,7 +113,7 @@ void MergeNode::Hash(QCryptographicHash &hash, const rational &time) const bool blend_changed_hash = false; if (IsInputConnected(kBaseIn)) { - GetConnectedNode(kBaseIn)->Hash(hash, time); + GetConnectedNode(kBaseIn)->Hash(output, hash, time); QByteArray post_base_hash = hash.result(); base_changed_hash = (post_base_hash != current_result); @@ -121,7 +121,7 @@ void MergeNode::Hash(QCryptographicHash &hash, const rational &time) const } if(IsInputConnected(kBlendIn)) { - GetConnectedNode(kBlendIn)->Hash(hash, time); + GetConnectedNode(kBlendIn)->Hash(output, hash, time); blend_changed_hash = (hash.result() != current_result); } diff --git a/app/node/math/merge/merge.h b/app/node/math/merge/merge.h index d2a8933d4..f5df8d6fc 100644 --- a/app/node/math/merge/merge.h +++ b/app/node/math/merge/merge.h @@ -46,7 +46,7 @@ public: static const QString kBaseIn; static const QString kBlendIn; - virtual void Hash(QCryptographicHash &hash, const rational &time) const override; + virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override; private: NodeInput* base_in_; diff --git a/app/node/node.cpp b/app/node/node.cpp index e0b30f860..bc763d12f 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -33,8 +33,8 @@ #include "config/config.h" #include "project/project.h" #include "project/item/footage/footage.h" -#include "project/item/footage/videostream.h" #include "ui/colorcoding.h" +#include "ui/icons/icons.h" #include "widget/nodeview/nodeviewundo.h" namespace olive { @@ -76,7 +76,7 @@ NodeGraph *Node::parent() const return static_cast(QObject::parent()); } -void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled) +void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) { while (XMLReadNextStartElement(reader)) { if (cancelled && *cancelled) { @@ -114,7 +114,7 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAto } } } else if (reader->name() == QStringLiteral("custom")) { - LoadInternal(reader, xml_node_data); + LoadInternal(reader, xml_node_data, version, cancelled); } else if (reader->name() == QStringLiteral("connections")) { // Load connections while (XMLReadNextStartElement(reader)) { @@ -199,6 +199,11 @@ void Node::Save(QXmlStreamWriter *writer) const writer->writeEndElement(); // custom } +Project* Node::project() const +{ + return dynamic_cast(parent()); +} + QString Node::ShortName() const { return Name(); @@ -214,6 +219,12 @@ void Node::Retranslate() { } +QIcon Node::icon() const +{ + // Just a meaningless default icon to be used where necessary + return icon::New; +} + Color Node::color() const { int c; @@ -253,9 +264,8 @@ QBrush Node::brush(qreal top, qreal bottom) const void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input) { - // Ensure parameters exist on the nodes requested - Q_ASSERT(input.node()->HasInputWithID(input.input())); - Q_ASSERT(output.node()->HasOutputWithID(output.output())); + // Ensure graph is the same + Q_ASSERT(input.node()->parent() == output.node()->parent()); // Ensure a connection isn't getting overwritten Q_ASSERT(input.node()->input_connections().find(input) == input.node()->input_connections().end()); @@ -264,8 +274,9 @@ void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input) input.node()->input_connections_[input] = output; output.node()->output_connections_.push_back(std::pair({output, input})); - // Call internal event + // Call internal events input.node()->InputConnectedEvent(input.input(), input.element(), output); + output.node()->OutputConnectedEvent(output.output(), input); // Emit signals emit input.node()->InputConnected(output, input); @@ -279,9 +290,8 @@ void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input) void Node::DisconnectEdge(const NodeOutput &output, const NodeInput &input) { - // Ensure parameters exist on the nodes requested - Q_ASSERT(input.node()->HasInputWithID(input.input())); - Q_ASSERT(output.node()->HasOutputWithID(output.output())); + // Ensure graph is the same + Q_ASSERT(input.node()->parent() == output.node()->parent()); // Ensure connection exists Q_ASSERT(input.node()->input_connections().at(input) == output); @@ -293,8 +303,9 @@ void Node::DisconnectEdge(const NodeOutput &output, const NodeInput &input) OutputConnections& outputs = output.node()->output_connections_; outputs.erase(std::find(outputs.begin(), outputs.end(), std::pair({output, input}))); - // Call internal event + // Call internal events input.node()->InputDisconnectedEvent(input.input(), input.element(), output); + output.node()->OutputDisconnectedEvent(output.output(), input); emit input.node()->InputDisconnected(output, input); emit output.node()->OutputDisconnected(output, input); @@ -1292,7 +1303,7 @@ void Node::IgnoreHashingFrom(const QString &input_id) ignore_when_hashing_.append(input_id); } -void Node::LoadInternal(QXmlStreamReader *reader, XMLNodeData &) +void Node::LoadInternal(QXmlStreamReader *reader, XMLNodeData &, uint, const QAtomicInt*) { reader->skipCurrentElement(); } @@ -1337,10 +1348,13 @@ void Node::SetLabel(const QString &s) } } -void Node::Hash(QCryptographicHash &hash, const rational& time) const +void Node::Hash(const QString &output, QCryptographicHash &hash, const rational& time) const { - // Add this Node's ID + Q_UNUSED(output) + + // Add this Node's ID and output being used hash.addData(id().toUtf8()); + hash.addData(output.toUtf8()); foreach (const QString& input, input_ids_) { // For each input, try to hash its value @@ -1479,57 +1493,14 @@ void Node::HashInputElement(QCryptographicHash &hash, const QString& input, int if (IsInputConnected(input, element)) { // Traverse down this edge - GetConnectedNode(input, element)->Hash(hash, input_time); + NodeOutput output = GetConnectedOutput(input, element); + + output.node()->Hash(output.output(), hash, input_time); } else { // Grab the value at this time QVariant value = GetValueAtTime(input, input_time, element); hash.addData(NodeValue::ValueToBytes(GetInputDataType(input), value)); } - - // We have one exception for FOOTAGE types, since we resolve the footage into a frame in the renderer - if (GetInputDataType(input) == NodeValue::kFootage) { - Stream* stream = Node::ValueToPtr(GetStandardValue(input, element)); - - if (stream) { - // Add footage details to hash - - // Footage filename - hash.addData(stream->footage()->filename().toUtf8()); - - // Footage last modified date - hash.addData(QString::number(stream->footage()->timestamp()).toUtf8()); - - // Footage stream - hash.addData(QString::number(stream->index()).toUtf8()); - - if (stream->type() == Stream::kVideo) { - VideoStream* image_stream = static_cast(stream); - - // Current color config and space - hash.addData(image_stream->footage()->project()->color_manager()->GetConfigFilename().toUtf8()); - hash.addData(image_stream->colorspace().toUtf8()); - - // Alpha associated setting - hash.addData(QString::number(image_stream->premultiplied_alpha()).toUtf8()); - - // Pixel aspect ratio - hash.addData(reinterpret_cast(&image_stream->pixel_aspect_ratio()), sizeof(rational)); - } - - // Footage timestamp - if (stream->type() == Stream::kVideo) { - VideoStream* video_stream = static_cast(stream); - - int64_t video_ts = Timecode::time_to_timestamp(input_time, video_stream->timebase()); - - // Add timestamp in units of the video stream's timebase - hash.addData(reinterpret_cast(&video_ts), sizeof(int64_t)); - - // Add start time - used for both image sequences and video streams - hash.addData(QString::number(video_stream->start_time()).toUtf8()); - } - } - } } QVector Node::GetDependencies() const @@ -1700,6 +1671,8 @@ QString Node::GetCategoryName(const CategoryID &c) return tr("Channel"); case kCategoryTransition: return tr("Transition"); + case kCategoryProject: + return tr("Project"); case kCategoryUnknown: case kCategoryCount: break; @@ -2032,6 +2005,18 @@ void Node::InputDisconnectedEvent(const QString &input, int element, const NodeO Q_UNUSED(output) } +void Node::OutputConnectedEvent(const QString &output, const NodeInput &input) +{ + Q_UNUSED(output) + Q_UNUSED(input) +} + +void Node::OutputDisconnectedEvent(const QString &output, const NodeInput &input) +{ + Q_UNUSED(output) + Q_UNUSED(input) +} + void Node::childEvent(QChildEvent *event) { super::childEvent(event); @@ -2152,17 +2137,17 @@ void Node::InvalidateFromKeyframeTypeChanged() Project *Node::ArrayInsertCommand::GetRelevantProject() const { - return node_->parent()->project(); + return node_->project(); } Project *Node::ArrayRemoveCommand::GetRelevantProject() const { - return node_->parent()->project(); + return node_->project(); } Project *Node::ArrayResizeCommand::GetRelevantProject() const { - return node_->parent()->project(); + return node_->project(); } } diff --git a/app/node/node.h b/app/node/node.h index 1393cfa82..40d179421 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -79,6 +79,7 @@ public: kCategoryChannels, kCategoryTransition, kCategoryDistort, + kCategoryProject, kCategoryCount }; @@ -100,10 +101,12 @@ public: */ NodeGraph* parent() const; + Project* project() const; + /** * @brief Clear current node variables and replace them with */ - void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); + void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled); /** * @brief Save this node into a text/XML format @@ -157,6 +160,8 @@ public: */ virtual void Retranslate(); + virtual QIcon icon() const; + const QVector& inputs() const { return input_ids_; @@ -685,7 +690,7 @@ public: const QString& GetLabel() const; void SetLabel(const QString& s); - virtual void Hash(QCryptographicHash& hash, const rational &time) const; + virtual void Hash(const QString& output, QCryptographicHash& hash, const rational &time) const; void InvalidateAll(const QString& input, int element = -1); @@ -768,7 +773,7 @@ protected: void IgnoreHashingFrom(const QString& input_id); - virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data); + virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled); virtual void SaveInternal(QXmlStreamWriter* writer) const; @@ -798,6 +803,10 @@ protected: virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput& output); + virtual void OutputConnectedEvent(const QString& output, const NodeInput& input); + + virtual void OutputDisconnectedEvent(const QString& output, const NodeInput& input); + virtual void childEvent(QChildEvent *event) override; signals: diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index f9f000eb0..e0456cc03 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -36,7 +36,7 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, vo QXmlStreamWriter writer(©_str); writer.setAutoFormatting(true); - writer.writeStartDocument(); + writer.writeStartDocument(QString::number(Core::kProjectVersion)); writer.writeStartElement(QStringLiteral("olive")); foreach (Node* n, nodes) { @@ -65,12 +65,15 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } QXmlStreamReader reader(clipboard); + uint data_version = reader.documentVersion().toUInt(); QVector pasted_nodes; XMLNodeData xml_node_data; while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { + // Default to current version - this may not be desirable? + while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("node")) { Node* node = nullptr; @@ -83,7 +86,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } if (node) { - node->Load(&reader, xml_node_data, nullptr); + node->Load(&reader, xml_node_data, data_version, nullptr); pasted_nodes.append(node); } diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 9726c69f8..a5562b976 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -138,7 +138,7 @@ void Track::SetTrackHeight(const double &height) emit TrackHeightChangedInPixels(GetTrackHeightInPixels()); } -void Track::LoadInternal(QXmlStreamReader *reader, XMLNodeData &) +void Track::LoadInternal(QXmlStreamReader *reader, XMLNodeData &, uint , const QAtomicInt* ) { while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("height")) { @@ -567,13 +567,13 @@ bool Track::IsLocked() const return locked_; } -void Track::Hash(QCryptographicHash &hash, const rational &time) const +void Track::Hash(const QString &output, QCryptographicHash &hash, const rational &time) const { Block* b = BlockAtTime(time); // Defer to block at this time, don't add any of our own information to the hash if (b) { - b->Hash(hash, TransformTimeForBlock(b, time)); + b->Hash(output, hash, TransformTimeForBlock(b, time)); } } diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index 3beb78ba1..2a303a732 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -274,7 +274,7 @@ public: bool IsLocked() const; - virtual void Hash(QCryptographicHash& hash, const rational &time) const override; + virtual void Hash(const QString& output, QCryptographicHash& hash, const rational &time) const override; AudioVisualWaveform& waveform() { @@ -335,7 +335,7 @@ signals: void BlocksRefreshed(); protected: - virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data) override; + virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; virtual void SaveInternal(QXmlStreamWriter* writer) const override; diff --git a/app/node/output/track/tracklist.cpp b/app/node/output/track/tracklist.cpp index 06ce79869..65b766ac8 100644 --- a/app/node/output/track/tracklist.cpp +++ b/app/node/output/track/tracklist.cpp @@ -27,7 +27,7 @@ namespace olive { -TrackList::TrackList(ViewerOutput *parent, const Track::Type &type, const QString &track_input) : +TrackList::TrackList(Sequence *parent, const Track::Type &type, const QString &track_input) : QObject(parent), track_input_(track_input), type_(type) @@ -136,7 +136,7 @@ void TrackList::UpdateTrackIndexesFrom(int index) NodeGraph *TrackList::GetParentGraph() const { - return static_cast(parent()->parent()); + return parent()->parent(); } const QString& TrackList::track_input() const @@ -149,9 +149,9 @@ NodeInput TrackList::track_input(int element) const return NodeInput(parent(), track_input(), element); } -ViewerOutput *TrackList::parent() const +Sequence *TrackList::parent() const { - return static_cast(QObject::parent()); + return static_cast(QObject::parent()); } int TrackList::ArraySize() const diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h index 042b93558..dcb0b8dbe 100644 --- a/app/node/output/track/tracklist.h +++ b/app/node/output/track/tracklist.h @@ -29,13 +29,13 @@ namespace olive { -class ViewerOutput; +class Sequence; class TrackList : public QObject { Q_OBJECT public: - TrackList(ViewerOutput *parent, const Track::Type& type, const QString& track_input); + TrackList(Sequence *parent, const Track::Type& type, const QString& track_input); const Track::Type& type() const { @@ -64,7 +64,7 @@ public: const QString &track_input() const; NodeInput track_input(int element) const; - ViewerOutput* parent() const; + Sequence* parent() const; int ArraySize() const; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 52b9c55df..bf253714c 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -20,49 +20,13 @@ #include "viewer.h" -#include "node/traverser.h" - namespace olive { -const QString ViewerOutput::kTextureInput = QStringLiteral("tex_in"); -const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); -const QString ViewerOutput::kTrackInputFormat = QStringLiteral("track_in_%1"); +#define super Sequence ViewerOutput::ViewerOutput() : - video_frame_cache_(this), - audio_playback_cache_(this), - operation_stack_(0) + Sequence(true) { - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - - AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); - - // Create TrackList instances - track_lists_.resize(Track::kCount); - - for (int i=0;i(i), track_input_id); - track_lists_.replace(i, list); - connect(list, &TrackList::TrackListChanged, this, &ViewerOutput::UpdateTrackCache); - connect(list, &TrackList::LengthChanged, this, &ViewerOutput::VerifyLength); - connect(list, &TrackList::TrackAdded, this, &ViewerOutput::TrackAdded); - connect(list, &TrackList::TrackRemoved, this, &ViewerOutput::TrackRemoved); - } - - // Create UUID for this node - uuid_ = QUuid::createUuid(); -} - -ViewerOutput::~ViewerOutput() -{ - DisconnectAll(); } Node *ViewerOutput::copy() const @@ -90,237 +54,4 @@ QString ViewerOutput::Description() const return tr("Interface between a Viewer panel and the node system."); } -void ViewerOutput::ShiftVideoCache(const rational &from, const rational &to) -{ - video_frame_cache_.Shift(from, to); -} - -void ViewerOutput::ShiftAudioCache(const rational &from, const rational &to) -{ - audio_playback_cache_.Shift(from, to); - - foreach (Track* track, track_lists_.at(Track::kAudio)->GetTracks()) { - track->waveform().Shift(from, to); - } -} - -void ViewerOutput::ShiftCache(const rational &from, const rational &to) -{ - ShiftVideoCache(from, to); - ShiftAudioCache(from, to); -} - -void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, int element) -{ - Q_UNUSED(element) - - if (operation_stack_ == 0) { - if (from == kTextureInput || from == kSamplesInput) { - TimeRange invalidated_range(qMax(rational(), range.in()), - qMin(GetLength(), range.out())); - - if (invalidated_range.in() != invalidated_range.out()) { - if (from == kTextureInput) { - video_frame_cache_.Invalidate(invalidated_range); - } else { - audio_playback_cache_.Invalidate(invalidated_range); - } - } - } - - VerifyLength(); - } - - Node::InvalidateCache(range, from); -} - -void ViewerOutput::set_video_params(const VideoParams &video) -{ - bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height(); - bool timebase_changed = video_params_.time_base() != video.time_base(); - bool pixel_aspect_changed = video_params_.pixel_aspect_ratio() != video.pixel_aspect_ratio(); - bool interlacing_changed = video_params_.interlacing() != video.interlacing(); - - video_params_ = video; - - if (size_changed) { - emit SizeChanged(video_params_.width(), video_params_.height()); - } - - if (pixel_aspect_changed) { - emit PixelAspectChanged(video_params_.pixel_aspect_ratio()); - } - - if (interlacing_changed) { - emit InterlacingChanged(video_params_.interlacing()); - } - - if (timebase_changed) { - video_frame_cache_.SetTimebase(video_params_.time_base()); - emit TimebaseChanged(video_params_.time_base()); - } - - emit VideoParamsChanged(); - - video_frame_cache_.InvalidateAll(); -} - -void ViewerOutput::set_audio_params(const AudioParams &audio) -{ - audio_params_ = audio; - - emit AudioParamsChanged(); - - // This will automatically InvalidateAll - audio_playback_cache_.SetParameters(audio_params()); -} - -rational ViewerOutput::GetLength() -{ - return last_length_; -} - -QVector ViewerOutput::GetUnlockedTracks() const -{ - QVector tracks = GetTracks(); - - for (int i=0;iIsLocked()) { - tracks.removeAt(i); - i--; - } - } - - return tracks; -} - -void ViewerOutput::UpdateTrackCache() -{ - track_cache_.clear(); - - foreach (TrackList* list, track_lists_) { - foreach (Track* track, list->GetTracks()) { - track_cache_.append(track); - } - } -} - -void ViewerOutput::VerifyLength() -{ - if (operation_stack_ != 0) { - return; - } - - NodeTraverser traverser; - - rational video_length, audio_length, subtitle_length; - - { - video_length = track_lists_.at(Track::kVideo)->GetTotalLength(); - - if (video_length.isNull() && IsInputConnected(kTextureInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); - video_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); - } - - video_frame_cache_.SetLength(video_length); - } - - { - audio_length = track_lists_.at(Track::kAudio)->GetTotalLength(); - - if (audio_length.isNull() && IsInputConnected(kSamplesInput)) { - NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); - audio_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); - } - - audio_playback_cache_.SetLength(audio_length); - } - - { - subtitle_length = track_lists_.at(Track::kSubtitle)->GetTotalLength(); - } - - rational real_length = qMax(subtitle_length, qMax(video_length, audio_length)); - - if (real_length != last_length_) { - last_length_ = real_length; - emit LengthChanged(last_length_); - } -} - -void ViewerOutput::Retranslate() -{ - Node::Retranslate(); - - SetInputName(kTextureInput, tr("Texture")); - - SetInputName(kSamplesInput, tr("Samples")); - - for (int i=0;i(i)) { - case Track::kVideo: - input_name = tr("Video Tracks"); - break; - case Track::kAudio: - input_name = tr("Audio Tracks"); - break; - case Track::kSubtitle: - input_name = tr("Subtitle Tracks"); - break; - case Track::kNone: - case Track::kCount: - break; - } - - if (!input_name.isEmpty()) { - SetInputName(kTrackInputFormat.arg(i), input_name); - } - } -} - -void ViewerOutput::BeginOperation() -{ - operation_stack_++; - - Node::BeginOperation(); -} - -void ViewerOutput::EndOperation() -{ - operation_stack_--; - - Node::EndOperation(); -} - -void ViewerOutput::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) -{ - if (input == kTextureInput) { - emit TextureInputChanged(); - } else { - foreach (TrackList* list, track_lists_) { - if (list->track_input() == input) { - list->TrackConnected(output.node(), element); - break; - } - } - } -} - -void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) -{ - if (input == kTextureInput) { - emit TextureInputChanged(); - } else { - foreach (TrackList* list, track_lists_) { - if (list->track_input() == input) { - list->TrackDisconnected(output.node(), element); - break; - } - } - } -} - } diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 80a0a944b..e330c6537 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -21,17 +21,7 @@ #ifndef VIEWER_H #define VIEWER_H -#include - -#include "node/block/block.h" -#include "node/output/track/track.h" -#include "node/output/track/tracklist.h" -#include "node/node.h" -#include "render/audioparams.h" -#include "render/audioplaybackcache.h" -#include "render/framehashcache.h" -#include "render/videoparams.h" -#include "timeline/timelinecommon.h" +#include "project/item/sequence/sequence.h" namespace olive { @@ -40,14 +30,12 @@ namespace olive { * * Receives update/time change signals from ViewerPanels and responds by sending them a texture of that frame */ -class ViewerOutput : public Node +class ViewerOutput : public Sequence { Q_OBJECT public: ViewerOutput(); - virtual ~ViewerOutput() override; - virtual Node* copy() const override; virtual QString Name() const override; @@ -55,120 +43,6 @@ public: virtual QVector Category() const override; virtual QString Description() const override; - void ShiftVideoCache(const rational& from, const rational& to); - void ShiftAudioCache(const rational& from, const rational& to); - void ShiftCache(const rational& from, const rational& to); - - virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1) override; - - const VideoParams& video_params() const - { - return video_params_; - } - - const AudioParams& audio_params() const - { - return audio_params_; - } - - void set_video_params(const VideoParams &video); - void set_audio_params(const AudioParams &audio); - - rational GetLength(); - - const QUuid& uuid() const - { - return uuid_; - } - - const QVector &GetTracks() const - { - return track_cache_; - } - - Track* GetTrackFromReference(const Track::Reference& track_ref) const - { - return track_lists_.at(track_ref.type())->GetTrackAt(track_ref.index()); - } - - /** - * @brief Same as GetTracks() but omits tracks that are locked. - */ - QVector GetUnlockedTracks() const; - - TrackList* track_list(Track::Type type) const - { - return track_lists_.at(type); - } - - virtual void Retranslate() override; - - FrameHashCache* video_frame_cache() - { - return &video_frame_cache_; - } - - AudioPlaybackCache* audio_playback_cache() - { - return &audio_playback_cache_; - } - - virtual void BeginOperation() override; - - virtual void EndOperation() override; - - static const QString kTextureInput; - static const QString kSamplesInput; - static const QString kTrackInputFormat; - -signals: - void TimebaseChanged(const rational&); - - void LengthChanged(const rational& length); - - void SizeChanged(int width, int height); - - void PixelAspectChanged(const rational& pixel_aspect); - - void InterlacingChanged(VideoParams::Interlacing mode); - - void VideoParamsChanged(); - void AudioParamsChanged(); - - void TrackAdded(Track* track); - void TrackRemoved(Track* track); - - void TextureInputChanged(); - -protected: - void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override; - - void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; - -private: - QUuid uuid_; - - VideoParams video_params_; - - AudioParams audio_params_; - - QVector track_lists_; - - QVector track_cache_; - - rational last_length_; - - FrameHashCache video_frame_cache_; - - AudioPlaybackCache audio_playback_cache_; - - int operation_stack_; - -private slots: - void UpdateTrackCache(); - - void VerifyLength(); - }; } diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp index 4724d55f9..c3503babd 100644 --- a/app/node/traverser.cpp +++ b/app/node/traverser.cpp @@ -21,6 +21,7 @@ #include "traverser.h" #include "node.h" +#include "render/job/footagejob.h" namespace olive { @@ -127,7 +128,7 @@ NodeValueTable NodeTraverser::GenerateBlockTable(const Track *track, const TimeR return table; } -QVariant NodeTraverser::ProcessVideoFootage(VideoStream *stream, const rational &input_time) +QVariant NodeTraverser::ProcessVideoFootage(const Footage::StreamReference& stream, const rational &input_time) { Q_UNUSED(stream) Q_UNUSED(input_time) @@ -135,7 +136,7 @@ QVariant NodeTraverser::ProcessVideoFootage(VideoStream *stream, const rational return QVariant(); } -QVariant NodeTraverser::ProcessAudioFootage(AudioStream *stream, const TimeRange &input_time) +QVariant NodeTraverser::ProcessAudioFootage(const Footage::StreamReference& stream, const TimeRange &input_time) { Q_UNUSED(stream) Q_UNUSED(input_time) @@ -202,8 +203,7 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N } // Strip out any jobs or footage - QList video_footage_to_retrieve; - QList audio_footage_to_retrieve; + QList footage_jobs_to_run; QList shader_jobs_to_run; QList sample_jobs_to_run; QList generate_jobs_to_run; @@ -212,16 +212,8 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N const NodeValue& v = output_params.at(i); QList* take_this_value_list = nullptr; - if (v.type() == NodeValue::kFootage) { - Stream* s = Node::ValueToPtr(v.data()); - - if (s) { - if (s->type() == Stream::kVideo) { - take_this_value_list = &video_footage_to_retrieve; - } else if (s->type() == Stream::kAudio) { - take_this_value_list = &audio_footage_to_retrieve; - } - } + if (v.type() == NodeValue::kFootageJob) { + take_this_value_list = &footage_jobs_to_run; } else if (v.type() == NodeValue::kShaderJob) { take_this_value_list = &shader_jobs_to_run; } else if (v.type() == NodeValue::kSampleJob) { @@ -238,12 +230,12 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N if (!got_cached_frame) { // Retrieve video frames - foreach (const NodeValue& v, video_footage_to_retrieve) { + foreach (const NodeValue& v, footage_jobs_to_run) { // Assume this is a VideoStream, we did a type check earlier in the function - VideoStream* stream = Node::ValueToPtr(v.data()); + Footage::StreamReference job = v.data().value(); - if (stream->footage()->IsValid()) { - QVariant value = ProcessVideoFootage(stream, range.in()); + if (job.IsValid() && job.type() == Stream::kVideo && job.footage()->IsValid()) { + QVariant value = ProcessVideoFootage(job, range.in()); if (!value.isNull()) { output_params.Push(NodeValue::kTexture, value, node); @@ -271,12 +263,12 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N } // Retrieve audio samples - foreach (const NodeValue& v, audio_footage_to_retrieve) { + foreach (const NodeValue& v, footage_jobs_to_run) { // Assume this is an AudioStream, we did a type check earlier in the function - AudioStream* stream = Node::ValueToPtr(v.data()); + Footage::StreamReference job = v.data().value(); - if (stream->footage()->IsValid()) { - QVariant value = ProcessAudioFootage(stream, range); + if (job.IsValid() && job.type() == Stream::kAudio && job.footage()->IsValid()) { + QVariant value = ProcessAudioFootage(job, range); if (!value.isNull()) { output_params.Push(NodeValue::kSamples, value, node); diff --git a/app/node/traverser.h b/app/node/traverser.h index eebe6f823..e597aac4a 100644 --- a/app/node/traverser.h +++ b/app/node/traverser.h @@ -26,7 +26,6 @@ #include "codec/decoder.h" #include "common/cancelableobject.h" #include "node/output/track/track.h" -#include "project/item/footage/stream.h" #include "value.h" namespace olive { @@ -49,9 +48,9 @@ protected: virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange& range); - virtual QVariant ProcessVideoFootage(VideoStream* stream, const rational &input_time); + virtual QVariant ProcessVideoFootage(const Footage::StreamReference &stream, const rational &input_time); - virtual QVariant ProcessAudioFootage(AudioStream* stream, const TimeRange &input_time); + virtual QVariant ProcessAudioFootage(const Footage::StreamReference &stream, const TimeRange &input_time); virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job); diff --git a/app/node/value.cpp b/app/node/value.cpp index 7f25b522b..1755f9227 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -27,6 +27,7 @@ #include #include "common/tohex.h" +#include "project/item/footage/stream.h" #include "render/color.h" namespace olive { @@ -64,8 +65,6 @@ QString NodeValue::ValueToString(Type data_type, const QVariant &value, bool val QString::number(c.alpha())); } else if (data_type == kRational) { return value.value().toString(); - } else if (data_type == kFootage) { - return QString::number(value.value()); } else if (data_type == kTexture || data_type == kSamples) { // These data types need no XML representation @@ -116,9 +115,14 @@ QByteArray NodeValue::ValueToBytes(NodeValue::Type type, const QVariant &value) case kVec4: return ValueToBytesInternal(value); case kCombo: return ValueToBytesInternal(value); + case kVideoStreamProperties: + case kAudioStreamProperties: + return value.value().toBytes(); + + // These types have no persistent input case kNone: - case kFootage: + case kFootageJob: case kTexture: case kSamples: case kShaderJob: @@ -177,6 +181,10 @@ QVector NodeValue::split_normal_value_into_track_values(Type type, con QVariant NodeValue::combine_track_values_into_normal_value(Type type, const QVector &split) { + if (split.isEmpty()) { + return QVariant(); + } + switch (type) { case kVec2: { @@ -293,15 +301,18 @@ QString NodeValue::GetPrettyDataTypeName(Type type) return QCoreApplication::translate("NodeValue", "Texture"); case kSamples: return QCoreApplication::translate("NodeValue", "Samples"); - case kFootage: - return QCoreApplication::translate("NodeValue", "Footage"); case kVec2: return QCoreApplication::translate("NodeValue", "Vector 2D"); case kVec3: return QCoreApplication::translate("NodeValue", "Vector 3D"); case kVec4: return QCoreApplication::translate("NodeValue", "Vector 4D"); + case kVideoStreamProperties: + return QCoreApplication::translate("NodeValue", "Video Stream Properties"); + case kAudioStreamProperties: + return QCoreApplication::translate("NodeValue", "Audio Stream Properties"); + case kFootageJob: case kShaderJob: case kSampleJob: case kGenerateJob: diff --git a/app/node/value.h b/app/node/value.h index 4300aaee7..c1c2ff7c1 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -121,13 +121,6 @@ public: */ kSamples, - /** - * Footage stream identifier type - * - * Resolves to `StreamPtr`. - */ - kFootage, - /** * Two-dimensional vector (XY) type * @@ -156,6 +149,29 @@ public: */ kCombo, + /** + * Properties pertaining to the video stream of a footage file + * + * Resolves to a `Stream` object. + */ + kVideoStreamProperties, + + /** + * Properties pertaining to the audio stream of a footage file + * + * Resolves to a `Stream` object. + */ + kAudioStreamProperties, + + /** + * Job type + * + * An internal type used to indicate to the renderer that a footage job needs to + * run. This value will usually be taken from a table and a kTexture or kSamples value will be + * pushed to take its place. + */ + kFootageJob, + /** * Job type * diff --git a/app/panel/footageviewer/footageviewer.cpp b/app/panel/footageviewer/footageviewer.cpp index 486962353..0030ff89a 100644 --- a/app/panel/footageviewer/footageviewer.cpp +++ b/app/panel/footageviewer/footageviewer.cpp @@ -60,7 +60,7 @@ void FootageViewerPanel::SetFootage(Footage *f) if (f) { // SetSubtitle() will call Retranslate(), so we don't need to call it here - SetSubtitle(f->name()); + SetSubtitle(f->GetLabel()); // Pop this panel up so the user doesn't think nothing's happening if it's behind another tab this->show(); diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index 275e6f394..3df3b2a45 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -108,7 +108,7 @@ QModelIndex ProjectPanel::get_root_index() const return explorer_->get_root_index(); } -void ProjectPanel::set_root(Item *item) +void ProjectPanel::set_root(Folder *item) { explorer_->set_root(item); @@ -184,10 +184,10 @@ void ProjectPanel::ItemDoubleClickSlot(Item *item) if (item == nullptr) { // If the user double clicks on empty space, show the import dialog Core::instance()->DialogImportShow(); - } else if (item->type() == Item::kFootage) { + } else if (dynamic_cast(item)) { // Open this footage in a FootageViewer PanelManager::instance()->MostRecentlyFocused()->SetFootage(static_cast(item)); - } else if (item->type() == Item::kSequence) { + } else if (dynamic_cast(item)) { // Open this sequence in the Timeline Core::instance()->main_window()->OpenSequence(static_cast(item)); } @@ -210,10 +210,10 @@ void ProjectPanel::UpdateSubtitle() if (explorer_->get_root_index().isValid()) { QString folder_path; - Item* item = static_cast(explorer_->get_root_index().internalPointer()); + Folder* item = static_cast(explorer_->get_root_index().internalPointer()); do { - folder_path.prepend(QStringLiteral("/%1").arg(item->name())); + folder_path.prepend(QStringLiteral("/%1").arg(item->GetLabel())); item = item->item_parent(); } while (item != project()->root()); @@ -238,7 +238,7 @@ QVector ProjectPanel::GetSelectedFootage() const QVector footage; foreach (Item* i, items) { - if (i->type() == Item::kFootage) { + if (dynamic_cast(i)) { footage.append(static_cast(i)); } } diff --git a/app/panel/project/project.h b/app/panel/project/project.h index 1da6cf335..b3c2e3fa5 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -42,7 +42,7 @@ public: QModelIndex get_root_index() const; - void set_root(Item* item); + void set_root(Folder* item); QVector SelectedItems() const; diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index 8b3ea15f6..3e59f02f0 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -113,7 +113,7 @@ TimeBasedWidget *TimeBasedPanel::GetTimeBasedWidget() const return widget_; } -ViewerOutput *TimeBasedPanel::GetConnectedViewer() const +Sequence *TimeBasedPanel::GetConnectedViewer() const { return widget_->GetConnectedNode(); } @@ -123,7 +123,7 @@ TimeRuler *TimeBasedPanel::ruler() const return widget_->ruler(); } -void TimeBasedPanel::ConnectViewerNode(ViewerOutput *node) +void TimeBasedPanel::ConnectViewerNode(Sequence *node) { if (widget_->GetConnectedNode() == node) { return; diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 2484a3183..b3c54975f 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -32,13 +32,13 @@ class TimeBasedPanel : public PanelWidget public: TimeBasedPanel(const QString& object_name, QWidget *parent = nullptr); - void ConnectViewerNode(ViewerOutput* node); + void ConnectViewerNode(Sequence *node); void DisconnectViewerNode(); rational GetTime(); - ViewerOutput* GetConnectedViewer() const; + Sequence *GetConnectedViewer() const; TimeRuler* ruler() const; diff --git a/app/project/item/folder/folder.cpp b/app/project/item/folder/folder.cpp index 0618e4f3b..9ce15ca9b 100644 --- a/app/project/item/folder/folder.cpp +++ b/app/project/item/folder/folder.cpp @@ -27,80 +27,65 @@ namespace olive { -Item::Type Folder::type() const +Folder::Folder() { - return kFolder; } -bool Folder::CanHaveChildren() const -{ - return true; -} - -QIcon Folder::icon() +QIcon Folder::icon() const { return icon::Folder; } -void Folder::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt *cancelled) +bool ChildExistsWithNameInternal(const Folder* n, const QString& s) { - XMLAttributeLoop(reader, attr) { - if (cancelled && *cancelled) { - return; - } + foreach (const Node::OutputConnection& c, n->output_connections()) { + Node* connected = c.second.node(); - if (attr.name() == QStringLiteral("name")) { - set_name(attr.value().toString()); - } else if (attr.name() == QStringLiteral("ptr")) { - xml_node_data.item_ptrs.insert(attr.value().toULongLong(), this); - } - } - - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - Item* child; - - if (reader->name() == QStringLiteral("folder")) { - child = new Folder(); - } else if (reader->name() == QStringLiteral("footage")) { - child = new Footage(); - } else if (reader->name() == QStringLiteral("sequence")) { - child = new Sequence(); + if (connected->GetLabel() == s) { + return true; } else { - reader->skipCurrentElement(); - continue; - } + Folder* subfolder = dynamic_cast(connected); - child->setParent(this); - child->Load(reader, xml_node_data, version, cancelled); + if (subfolder && ChildExistsWithNameInternal(subfolder, s)) { + return true; + } + } } + + return false; } -void Folder::Save(QXmlStreamWriter *writer) const +bool Folder::ChildExistsWithName(const QString &s) const { - writer->writeAttribute(QStringLiteral("name"), name()); + return ChildExistsWithNameInternal(this, s); +} - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); +void Folder::OutputConnectedEvent(const QString &output, const NodeInput &input) +{ + Q_UNUSED(output) - foreach (Item* child, children()) { - switch (child->type()) { - case Item::kFootage: - writer->writeStartElement(QStringLiteral("footage")); - break; - case Item::kSequence: - writer->writeStartElement(QStringLiteral("sequence")); - break; - case Item::kFolder: - writer->writeStartElement(QStringLiteral("folder")); - break; - } + Item* item = dynamic_cast(input.node()); - child->Save(writer); + if (item) { + // The insert index is always our "count" because we only support appending in our internal + // model. For sorting/organizing, a QSortFilterProxyModel is used instead. + emit BeginInsertItem(item, item_child_count()); + item_children_.append(item); + emit EndInsertItem(); + } +} - writer->writeEndElement(); // footage/folder/sequence +void Folder::OutputDisconnectedEvent(const QString &output, const NodeInput &input) +{ + Q_UNUSED(output) + + Item* item = dynamic_cast(input.node()); + + if (item) { + int child_index = item_children_.indexOf(item); + emit BeginRemoveItem(item, child_index); + item_children_.removeAt(child_index); + emit EndRemoveItem(); } } diff --git a/app/project/item/folder/folder.h b/app/project/item/folder/folder.h index c2505bcf5..899b300a8 100644 --- a/app/project/item/folder/folder.h +++ b/app/project/item/folder/folder.h @@ -21,6 +21,7 @@ #ifndef FOLDER_H #define FOLDER_H +#include "node/node.h" #include "project/item/item.h" namespace olive { @@ -33,20 +34,113 @@ namespace olive { */ class Folder : public Item { + Q_OBJECT public: - Folder() = default; + Folder(); - virtual Type type() const override; + virtual Node* copy() const override + { + return new Folder(); + } - virtual bool CanHaveChildren() const override; + virtual QString Name() const override + { + return tr("Folder"); + } - virtual QIcon icon() override; + virtual QString id() const override + { + return QStringLiteral("org.olivevideoeditor.Olive.folder"); + } - virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override; + virtual QVector Category() const override + { + return {kCategoryProject}; + } - virtual void Save(QXmlStreamWriter* writer) const override; + virtual QString Description() const override + { + return tr("Organize several items into a single collection."); + } + + virtual QIcon icon() const override; + + bool ChildExistsWithName(const QString& s) const; + + int item_child_count() const + { + return item_children_.size(); + } + + Item* item_child(int i) const + { + return item_children_.at(i); + } + + const QVector& children() const + { + return item_children_; + } + + /** + * @brief Returns a list of nodes that are of a certain type that this node outputs to + */ + template + QVector ListOutputsOfType(bool recursive = true) const + { + QVector list; + + ListOutputsOfTypeInternal(this, list, recursive); + + return list; + } + + int index_of_child(Item* item) const + { + return item_children_.indexOf(item); + } + +signals: + void BeginInsertItem(Item* n, int index); + + void EndInsertItem(); + + void BeginRemoveItem(Item* n, int index); + + void EndRemoveItem(); + +protected: + virtual void OutputConnectedEvent(const QString& output, const NodeInput& input) override; + + virtual void OutputDisconnectedEvent(const QString& output, const NodeInput& input) override; private: + template + static void ListOutputsOfTypeInternal(const Folder* n, QVector& list, bool recursive) + { + foreach (const Node::OutputConnection& c, n->output_connections()) { + Node* connected = c.second.node(); + + T* cast_test = dynamic_cast(connected); + + if (cast_test) { + // Avoid duplicates + if (!list.contains(cast_test)) { + list.append(cast_test); + } + } + + if (recursive) { + Folder* subfolder = dynamic_cast(connected); + + if (subfolder) { + ListOutputsOfTypeInternal(subfolder, list, recursive); + } + } + } + } + + QVector item_children_; }; diff --git a/app/project/item/footage/CMakeLists.txt b/app/project/item/footage/CMakeLists.txt index 1a3d79e45..d868e0c15 100644 --- a/app/project/item/footage/CMakeLists.txt +++ b/app/project/item/footage/CMakeLists.txt @@ -17,13 +17,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - project/item/footage/audiostream.h - project/item/footage/audiostream.cpp - project/item/footage/footage.h project/item/footage/footage.cpp - project/item/footage/stream.h + project/item/footage/footage.h project/item/footage/stream.cpp - project/item/footage/videostream.h - project/item/footage/videostream.cpp + project/item/footage/stream.h PARENT_SCOPE ) diff --git a/app/project/item/footage/audiostream.cpp b/app/project/item/footage/audiostream.cpp deleted file mode 100644 index 7b1ff0d15..000000000 --- a/app/project/item/footage/audiostream.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "audiostream.h" - -#include "common/xmlutils.h" - -namespace olive { - -AudioStream::AudioStream() -{ - set_type(kAudio); -} - -QString AudioStream::description() const -{ - return QCoreApplication::translate("Stream", "%1: Audio - %2 Channel(s), %3Hz") - .arg(QString::number(index()), - QString::number(channels()), - QString::number(sample_rate())); -} - -const int &AudioStream::channels() const -{ - return channels_; -} - -void AudioStream::set_channels(const int &channels) -{ - channels_ = channels; -} - -const uint64_t &AudioStream::channel_layout() const -{ - return layout_; -} - -void AudioStream::set_channel_layout(const uint64_t &layout) -{ - layout_ = layout; -} - -const int &AudioStream::sample_rate() const -{ - return sample_rate_; -} - -void AudioStream::set_sample_rate(const int &sample_rate) -{ - sample_rate_ = sample_rate; -} - -QIcon AudioStream::icon() const -{ - return icon::Audio; -} - -void AudioStream::LoadCustomParameters(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("channels")) { - set_channels(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("layout")) { - set_channel_layout(reader->readElementText().toULongLong()); - } else if (reader->name() == QStringLiteral("rate")) { - set_sample_rate(reader->readElementText().toInt()); - } else { - reader->skipCurrentElement(); - } - } -} - -void AudioStream::SaveCustomParameters(QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("channels"), QString::number(channels_)); - writer->writeTextElement(QStringLiteral("layout"), QString::number(layout_)); - writer->writeTextElement(QStringLiteral("rate"), QString::number(sample_rate_)); -} - -} diff --git a/app/project/item/footage/audiostream.h b/app/project/item/footage/audiostream.h deleted file mode 100644 index b0f53ee25..000000000 --- a/app/project/item/footage/audiostream.h +++ /dev/null @@ -1,68 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef AUDIOSTREAM_H -#define AUDIOSTREAM_H - -#include - -#include "common/rational.h" -#include "render/audioparams.h" -#include "stream.h" - -namespace olive { - -/** - * @brief A Stream derivative containing audio-specific information - */ -class AudioStream : public Stream -{ - Q_OBJECT -public: - AudioStream(); - - virtual QString description() const override; - - const int& channels() const; - void set_channels(const int& channels); - - const uint64_t& channel_layout() const; - void set_channel_layout(const uint64_t& channel_layout); - - const int& sample_rate() const; - void set_sample_rate(const int& sample_rate); - - virtual QIcon icon() const override; - -protected: - virtual void LoadCustomParameters(QXmlStreamReader *reader) override; - - virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override; - -private: - int channels_; - uint64_t layout_; - int sample_rate_; - -}; - -} - -#endif // AUDIOSTREAM_H diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index 5f9501a58..ad6fb51c9 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -22,28 +22,51 @@ #include #include +#include #include "codec/decoder.h" #include "common/filefunctions.h" #include "common/xmlutils.h" #include "config/config.h" #include "core.h" +#include "render/job/footagejob.h" #include "ui/icons/icons.h" namespace olive { -Footage::Footage() +const QString Footage::kFilenameInput = QStringLiteral("file_in"); +const QString Footage::kStreamPropertiesFormat = QStringLiteral("stream_properties:%1"); + +#define super Item + +Footage::Footage(const QString &filename) : + super(true, false), + stream_count_(0), + cancelled_(nullptr) { + AddInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + Clear(); + + set_filename(filename); } -Footage::~Footage() +void Footage::Retranslate() { - ClearStreams(); + super::Retranslate(); + + SetInputName(kFilenameInput, tr("Filename")); + + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + StreamReference ref = GetReferenceFromRealIndex(it.key()); + + SetInputName(it.value(), QStringLiteral("%1 %2").arg(GetStreamTypeName(ref.type()), QString::number(ref.index()))); + } } -void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) +void Footage::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) { + Q_UNUSED(xml_node_data) Q_UNUSED(version) while (XMLReadNextStartElement(reader)) { @@ -51,16 +74,8 @@ void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint ve return; } - if (reader->name() == QStringLiteral("name")) { - set_name(reader->readElementText()); - } else if (reader->name() == QStringLiteral("filename")) { - set_filename(reader->readElementText()); - } else if (reader->name() == QStringLiteral("stream")) { - add_stream(Stream::Load(reader, xml_node_data, cancelled)); - } else if (reader->name() == QStringLiteral("timestamp")) { + if (reader->name() == QStringLiteral("timestamp")) { set_timestamp(reader->readElementText().toLongLong()); - } else if (reader->name() == QStringLiteral("decoder")) { - set_decoder(reader->readElementText()); } else if (reader->name() == QStringLiteral("points")) { TimelinePoints::Load(reader); } else { @@ -69,28 +84,182 @@ void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint ve } } -void Footage::Save(QXmlStreamWriter *writer) const +void Footage::SaveInternal(QXmlStreamWriter *writer) const { - writer->writeTextElement(QStringLiteral("name"), name()); - writer->writeTextElement(QStringLiteral("filename"), filename()); writer->writeTextElement(QStringLiteral("timestamp"), QString::number(timestamp_)); - writer->writeTextElement(QStringLiteral("decoder"), decoder_); writer->writeStartElement(QStringLiteral("points")); - TimelinePoints::Save(writer); + TimelinePoints::Save(writer); writer->writeEndElement(); // points +} - foreach (Stream* stream, streams_) { - writer->writeStartElement(QStringLiteral("stream")); - stream->Save(writer); - writer->writeEndElement(); // stream +void Footage::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element) + + if (input == kFilenameInput) { + // Reset internal stream cache + Clear(); + + // Determine if file still exists + QFileInfo info(filename()); + + if (info.exists()) { + // Grab timestamp + set_timestamp(info.lastModified().toMSecsSinceEpoch()); + + // Determine if we've already cached the metadata of this file + QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename())); + + MetadataCache footage_info; + + if (QFileInfo::exists(meta_cache_file)) { + + // Load meta cache file + footage_info = LoadStreamCache(meta_cache_file); + + } else { + + // Probe and create cache + QVector decoder_list = Decoder::ReceiveListOfAllDecoders(); + + foreach (DecoderPtr decoder, decoder_list) { + footage_info.streams = decoder->Probe(filename(), cancelled_); + + if (!footage_info.streams.isEmpty()) { + footage_info.decoder = decoder->id(); + SetValid(); + break; + } + } + + if (!SaveStreamCache(meta_cache_file, footage_info)) { + qWarning() << "Failed to save stream cache, footage will have to be re-probed"; + } + + } + + stream_count_ = footage_info.streams.size(); + + if (!footage_info.streams.isEmpty()) { + set_decoder(footage_info.decoder); + + for (int i=0; isetParent(this); + Stream s = GetStreamAt(index); - // Add a copy of this stream to the list - streams_.append(s); -} - -void Footage::add_streams(const QVector &streams) -{ - foreach (Stream* s, streams) { - s->setParent(this); + if (!s.IsValid()) { + return AV_NOPTS_VALUE; } - streams_.append(streams); + return Timecode::time_to_timestamp(time, s.timebase()) + s.start_time(); } -Stream* Footage::stream(int index) const +int Footage::GetRealStreamIndex(Stream::Type type, int index) const { - return streams_.at(index); + int lookup_index = 0; + + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + Stream stream = GetStandardValue(it.value()).value(); + + if (stream.type() == type) { + if (lookup_index == index) { + return it.key(); + } else { + lookup_index++; + } + } + } + + return -1; } -int Footage::stream_count() const +QString Footage::GetStringFromReference(Stream::Type type, int index) { - return streams_.size(); + QString type_string; + + if (type == Stream::kVideo) { + type_string = QStringLiteral("v"); + } else if (type == Stream::kAudio) { + type_string = QStringLiteral("a"); + } else { + return QString(); + } + + return QStringLiteral("%1:%2").arg(type_string, QString::number(index)); } -Item::Type Footage::type() const +Footage::StreamReference Footage::GetReferenceFromRealIndex(int real_index) const { - return kFootage; + Stream s = GetStreamAt(real_index); + + if (!s.IsValid()) { + // Return invalid/null reference + return StreamReference(); + } + + int index_in_type = 0; + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + if (it.key() == real_index) { + break; + } else { + Stream temp = GetStreamAt(it.key()); + + if (temp.type() == s.type()) { + index_in_type++; + } + } + } + + return StreamReference(this, s.type(), index_in_type); +} + +Stream::Type Footage::GetTypeFromOutput(const QString &s) const +{ + if (s.at(1) == ':') { + if (s.at(0) == 'v') { + // Video stream + return Stream::kVideo; + } else if (s.at(0) == 'a') { + // Audio stream + return Stream::kAudio; + } + } + + return Stream::kUnknown; +} + +Stream Footage::GetFirstEnabledStreamOfType(Stream::Type type) const +{ + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + Stream stream = GetStandardValue(it.value()).value(); + + if (stream.enabled() && stream.type() == type) { + return stream; + } + } + + return Stream(); +} + +QVector Footage::GetStreamIndexesOfType(Stream::Type type) const +{ + QVector indexes; + + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + Stream stream = GetStandardValue(it.value()).value(); + + if (stream.enabled() && stream.type() == type) { + indexes.append(it.key()); + } + } + + return indexes; } const QString &Footage::decoder() const @@ -164,17 +413,17 @@ void Footage::set_decoder(const QString &id) decoder_ = id; } -QIcon Footage::icon() +QIcon Footage::icon() const { - if (valid_ && !streams_.isEmpty()) { + if (valid_ && !inputs_for_stream_properties_.isEmpty()) { // Prioritize video > audio > image - Stream* s = get_first_enabled_stream_of_type(Stream::kVideo); + Stream s = GetFirstEnabledStreamOfType(Stream::kVideo); - if (s && static_cast(s)->video_type() != VideoStream::kVideoTypeStill) { + if (s.IsValid() && s.video_type() != Stream::kVideoTypeStill) { return icon::Video; } else if (HasEnabledStreamsOfType(Stream::kAudio)) { return icon::Audio; - } else if (s && static_cast(s)->video_type() == VideoStream::kVideoTypeStill) { + } else if (s.IsValid() && s.video_type() == Stream::kVideoTypeStill) { return icon::Image; } } @@ -185,32 +434,31 @@ QIcon Footage::icon() QString Footage::duration() { // Find longest stream duration - Stream* longest_stream = nullptr; + Stream longest_stream; rational longest; - foreach (Stream* stream, streams_) { - if (stream->enabled() && (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio)) { - rational this_stream_dur = Timecode::timestamp_to_time(stream->duration(), - stream->timebase()); + for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) { + Stream s = GetStandardValue(it.value()).value(); + + if (s.enabled() && (s.type() == Stream::kVideo || s.type() == Stream::kAudio)) { + rational this_stream_dur = Timecode::timestamp_to_time(s.duration(), s.timebase()); if (this_stream_dur > longest) { - longest_stream = stream; + longest_stream = s; longest = this_stream_dur; } } } - if (longest_stream) { - if (longest_stream->type() == Stream::kVideo) { - VideoStream* video_stream = static_cast(longest_stream); + if (longest_stream.IsValid()) { + if (longest_stream.type() == Stream::kVideo) { + if (longest_stream.video_type() != Stream::kVideoTypeStill) { + int64_t duration = longest_stream.duration(); + rational frame_rate_timebase = longest_stream.frame_rate().flipped(); - if (video_stream->video_type() != VideoStream::kVideoTypeStill) { - int64_t duration = video_stream->duration(); - rational frame_rate_timebase = video_stream->frame_rate().flipped(); - - if (video_stream->timebase() != frame_rate_timebase) { + if (longest_stream.timebase() != frame_rate_timebase) { // Convert from timebase to frame rate - rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase()); + rational duration_time = Timecode::timestamp_to_time(duration, longest_stream.timebase()); duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase); } @@ -218,7 +466,7 @@ QString Footage::duration() frame_rate_timebase, Core::instance()->GetTimecodeDisplay()); } - } else if (longest_stream->type() == Stream::kAudio) { + } else if (longest_stream.type() == Stream::kAudio) { // If we're showing in a timecode, we prefer showing audio in seconds instead Timecode::Display display = Core::instance()->GetTimecodeDisplay(); if (display == Timecode::kTimecodeDropFrame @@ -226,8 +474,8 @@ QString Footage::duration() display = Timecode::kTimecodeSeconds; } - return Timecode::timestamp_to_timecode(longest_stream->duration(), - longest_stream->timebase(), + return Timecode::timestamp_to_timecode(longest_stream.duration(), + longest_stream.timebase(), display); } } @@ -237,21 +485,21 @@ QString Footage::duration() QString Footage::rate() { - if (streams_.isEmpty()) { + if (inputs_for_stream_properties_.isEmpty()) { return QString(); } if (HasEnabledStreamsOfType(Stream::kVideo)) { // This is a video editor, prioritize video streams - VideoStream* video_stream = static_cast(get_first_enabled_stream_of_type(Stream::kVideo)); + Stream video_stream = GetFirstEnabledStreamOfType(Stream::kVideo); - if (video_stream->video_type() != VideoStream::kVideoTypeStill) { - return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream->frame_rate().toDouble()); + if (video_stream.video_type() != Stream::kVideoTypeStill) { + return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream.frame_rate().toDouble()); } } else if (HasEnabledStreamsOfType(Stream::kAudio)) { // No video streams, return audio - AudioStream* audio_stream = static_cast(streams_.first()); - return QCoreApplication::translate("Footage", "%1 Hz").arg(audio_stream->sample_rate()); + Stream audio_stream = GetStreamAt(0); + return QCoreApplication::translate("Footage", "%1 Hz").arg(audio_stream.sample_rate()); } return QString(); @@ -260,30 +508,23 @@ QString Footage::rate() quint64 Footage::get_enabled_stream_flags() const { quint64 enabled_streams = 0; - quint64 stream_enabler = 1; - foreach (Stream* s, streams_) { - if (s->enabled()) { - enabled_streams |= stream_enabler; + for (int i=0; ienabled() && stream->type() == type) { + for (int i=0; ienabled() && stream->type() == type) { - return stream; - } - } - - return nullptr; -} - bool Footage::CompareFootageToFile(Footage *footage, const QString &filename) { // Heuristic to determine if file has changed QFileInfo info(filename); if (info.exists()) { - if (info.lastModified().toMSecsSinceEpoch() == footage->timestamp()) { + /*if (info.lastModified().toMSecsSinceEpoch() == footage->timestamp()) { // Footage has not been modified and is where we expect return true; } else { // Footage may have changed and we'll have to re-probe it. It also may not have, in which // case nothing needs to change. - std::unique_ptr item(Decoder::Probe(footage->project(), filename, nullptr)); + DecoderPtr decoder = Decoder::CreateFromID(footage->decoder()); - if (item) { - // Item is the same type, that's a good sign. Let's look for any differences. - // FIXME: Implement this + Streams probed_streams = decoder->Probe(filename, nullptr); + + if (probed_streams == footage->streams_) { return true; } - } + }*/ + + // Simplified, since our footage node is much more tolerant, we'll try this + return true; } // Footage file couldn't be found or resolved to something we didn't expect @@ -333,16 +566,106 @@ bool Footage::CompareFootageToItsFilename(Footage *footage) return CompareFootageToFile(footage, footage->filename()); } +void Footage::Hash(const QString& output, QCryptographicHash &hash, const rational &time) const +{ + super::Hash(output, hash, time); + + // Translate output ID to stream + StreamReference ref = GetReferenceFromOutput(output); + + QString fn = filename(); + + if (!fn.isEmpty()) { + Stream stream = GetStreamAt(GetReferenceFromOutput(output)); + + if (stream.IsValid()) { + // Add footage details to hash + + // Footage filename + hash.addData(filename().toUtf8()); + + // Footage last modified date + hash.addData(QString::number(timestamp()).toUtf8()); + + // Footage stream + hash.addData(QString::number(ref.index()).toUtf8()); + + if (ref.type() == Stream::kVideo) { + // Current color config and space + hash.addData(project()->color_manager()->GetConfigFilename().toUtf8()); + hash.addData(stream.colorspace().toUtf8()); + + // Alpha associated setting + hash.addData(QString::number(stream.premultiplied_alpha()).toUtf8()); + + // Pixel aspect ratio + hash.addData(reinterpret_cast(&stream.pixel_aspect_ratio()), sizeof(stream.pixel_aspect_ratio())); + + // Footage timestamp + if (stream.video_type() != Stream::kVideoTypeStill) { + int64_t video_ts = Timecode::time_to_timestamp(time, stream.timebase()); + + // Add timestamp in units of the video stream's timebase + hash.addData(reinterpret_cast(&video_ts), sizeof(int64_t)); + + // Add start time - used for both image sequences and video streams + hash.addData(QString::number(stream.start_time()).toUtf8()); + } + } + } + } +} + +NodeValueTable Footage::Value(const QString &output, NodeValueDatabase &value) const +{ + StreamReference ref = GetReferenceFromOutput(output); + + // Pop filename from table + QString file = value[kFilenameInput].Take(NodeValue::kFile).toString(); + + // Merge table + NodeValueTable table = value.Merge(); + + // If the file exists and the reference is valid, push a footage job to the renderer + if (QFileInfo(file).exists() && ref.IsValid()) { + table.Push(NodeValue::kFootageJob, QVariant::fromValue(ref), this); + } + + return table; +} + +QString Footage::GetStreamTypeName(Stream::Type type) +{ + switch (type) { + case Stream::kVideo: + return tr("Video"); + case Stream::kAudio: + return tr("Audio"); + case Stream::kSubtitle: + return tr("Subtitle"); + case Stream::kData: + return tr("Data"); + case Stream::kAttachment: + return tr("Attachment"); + case Stream::kUnknown: + break; + } + + return tr("Unknown"); +} + void Footage::UpdateTooltip() { if (valid_) { QString tip = QCoreApplication::translate("Footage", "Filename: %1").arg(filename()); - if (!streams_.isEmpty()) { - foreach (Stream* s, streams_) { - if (s->enabled()) { + if (!inputs_for_stream_properties_.isEmpty()) { + for (int i=0; idescription()); + tip.append(DescribeStream(i)); } } } @@ -353,4 +676,116 @@ void Footage::UpdateTooltip() } } +Footage::MetadataCache Footage::LoadStreamCache(const QString &filename) +{ + MetadataCache cache; + QFile file(filename); + + if (file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&file); + + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("streamcache")) { + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("decoder")) { + cache.decoder = reader.readElementText(); + } else if (reader.name() == QStringLiteral("streams")) { + while (XMLReadNextStartElement(&reader)) { + if (reader.name() == QStringLiteral("stream")) { + Stream s; + s.Load(&reader); + cache.streams.append(s); + } else { + reader.skipCurrentElement(); + } + } + } else { + reader.skipCurrentElement(); + } + } + } else { + reader.skipCurrentElement(); + } + } + + file.close(); + } + + return cache; +} + +bool Footage::SaveStreamCache(const QString &filename, const Footage::MetadataCache &data) +{ + QFile file(filename); + + if (!file.open(QFile::WriteOnly)) { + return false; + } + + QXmlStreamWriter writer(&file); + + writer.writeStartDocument(); + + writer.writeStartElement(QStringLiteral("streamcache")); + + writer.writeTextElement(QStringLiteral("decoder"), data.decoder); + + writer.writeStartElement(QStringLiteral("streams")); + + foreach (const Stream& s, data.streams) { + writer.writeStartElement(QStringLiteral("stream")); + s.Save(&writer); + writer.writeEndElement(); // stream + } + + writer.writeEndElement(); // streams + + writer.writeEndElement(); // streamcache + + writer.writeEndDocument(); + + file.close(); + + return true; +} + +void Footage::CheckFootage() +{ + QString fn = filename(); + + if (!fn.isEmpty()) { + QFileInfo info(fn); + + qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch(); + + if (current_file_timestamp != timestamp()) { + // File has changed! + set_timestamp(current_file_timestamp); + InvalidateAll(kFilenameInput); + } + } +} + +QString Footage::StreamReference::video_colorspace(bool default_if_empty) const +{ + if (IsValid()) { + Stream stream = footage_->GetStreamAt(type_, index_); + + if (stream.IsValid()) { + if (stream.colorspace().isEmpty() && default_if_empty) { + return footage_->project()->color_manager()->GetDefaultInputColorSpace(); + } else { + return stream.colorspace(); + } + } + } + + return QString(); +} + +uint qHash(const Footage::StreamReference &ref, uint seed) +{ + return qHash(ref.footage(), seed) ^ qHash(ref.type(), seed) ^ qHash(ref.index(), seed); +} + } diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h index 71ec994ea..1e08463e9 100644 --- a/app/project/item/footage/footage.h +++ b/app/project/item/footage/footage.h @@ -25,9 +25,9 @@ #include #include "common/rational.h" +#include "node/node.h" #include "project/item/item.h" -#include "project/item/footage/audiostream.h" -#include "project/item/footage/videostream.h" +#include "stream.h" #include "timeline/timelinepoints.h" namespace olive { @@ -46,24 +46,34 @@ public: /** * @brief Footage Constructor */ - Footage(); + Footage(const QString& filename = QString()); - /** - * @brief Footage Destructor - * - * Makes sure Stream objects are cleared properly - */ - virtual ~Footage() override; + virtual Node* copy() const override + { + return new Footage(); + } - /** - * @brief Load function - */ - virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override; + virtual QString Name() const override + { + return tr("Footage"); + } - /** - * @brief Save function - */ - virtual void Save(QXmlStreamWriter *writer) const override; + virtual QString id() const override + { + return QStringLiteral("org.olivevideoeditor.Olive.footage"); + } + + virtual QVector Category() const override + { + return {kCategoryProject}; + } + + virtual QString Description() const override + { + return tr("Import video, audio, or still image files into the composition."); + } + + virtual void Retranslate() override; /** * @brief Reset Footage state ready for running through Probe() again @@ -90,7 +100,7 @@ public: /** * @brief Return the current filename of this Footage object */ - const QString& filename() const; + QString filename() const; /** * @brief Set the filename @@ -123,54 +133,189 @@ public: */ void set_timestamp(const qint64 &t); - /** - * @brief Add a stream metadata object to this footage - * - * Usually done during a Decoder::Probe() function for retrieving metadata about the video/audio/other streams - * inside a container. Streams can have non-video/audio types so that they can be equivalent to the file's actual - * stream list, though the only streams officially supported are video and audio streams. - * - * @param s - * - * A pointer to a stream object. The Footage takes ownership of this object and will free it when it's deleted. - */ - void add_stream(Stream *s); - - void add_streams(const QVector& streams); - - /** - * @brief Retrieve a stream at the given index. - * - * @param index - * - * The index will be equivalent to the stream's index in the file (or in FFmpeg - * terms AVStream->file_index). Must be < stream_count(). - * - * @return - * - * The stream at the index provided - */ - Stream *stream(int index) const; - - /** - * @brief Returns a list of the streams in this Footage - */ - const QVector& streams() const + void SetCancelPointer(const QAtomicInt* c) { - return streams_; + cancelled_ = c; } - /** - * @brief Retrieve total number of streams in this Footage file - */ - int stream_count() const; + class StreamReference + { + public: + StreamReference() + { + footage_ = nullptr; + type_ = Stream::kUnknown; + index_ = -1; + } - /** - * @brief Item::Type() override - * - * @return kFootage - */ - virtual Type type() const override; + StreamReference(const Footage* footage, Stream::Type type, int index) + { + footage_ = footage; + type_ = type; + index_ = index; + } + + bool operator==(const StreamReference& rhs) const + { + return footage_ == rhs.footage_ && type_ == rhs.type_ && index_ == rhs.index_; + } + + bool IsValid() const + { + return footage_ && index_ >= 0; + } + + void Reset() + { + *this = StreamReference(); + } + + const Footage* footage() const + { + return footage_; + } + + Stream::Type type() const + { + return type_; + } + + int index() const + { + return index_; + } + + Stream GetStream() const + { + if (IsValid()) { + return footage_->GetStreamAt(*this); + } else { + return Stream(); + } + } + + int64_t GetTimeInTimebaseUnits(const rational& timecode) const + { + if (IsValid()) { + return footage_->GetTimeInTimebaseUnits(type_, index_, timecode); + } else { + return -1; + } + } + + int GetRealStreamIndex() const + { + if (IsValid()) { + return footage_->GetRealStreamIndex(*this); + } else { + return -1; + } + } + + QString filename() const + { + if (footage_) { + return footage_->filename(); + } else { + return QString(); + } + } + + VideoParams video_params() const + { + return GetStream().video_params(); + } + + AudioParams audio_params() const + { + return GetStream().audio_params(); + } + + int64_t duration() const + { + return GetStream().duration(); + } + + QString video_colorspace(bool default_if_empty = true) const; + + private: + const Footage* footage_; + Stream::Type type_; + int index_; + + }; + + Stream GetStreamAt(int index) const + { + return GetStandardValue(GetInputIDOfIndex(index)).value(); + } + + Stream GetStreamAt(Stream::Type type, int index_within_type) const + { + return GetStreamAt(GetRealStreamIndex(type, index_within_type)); + } + + Stream GetStreamAt(const StreamReference& ref) const + { + return GetStreamAt(ref.type(), ref.index()); + } + + void SetStreamAt(int index, const Stream& stream) + { + SetStandardValue(GetInputIDOfIndex(index), QVariant::fromValue(stream)); + } + + void SetStreamAt(Stream::Type type, int index_within_type, const Stream& stream) + { + SetStreamAt(GetRealStreamIndex(type, index_within_type), stream); + } + + void SetStreamAt(const StreamReference& ref, const Stream& stream) + { + SetStreamAt(ref.type(), ref.index(), stream); + } + + int64_t GetTimeInTimebaseUnits(int index, const rational& time) const; + int64_t GetTimeInTimebaseUnits(Stream::Type type, int index_within_type, const rational& time) const + { + return GetTimeInTimebaseUnits(GetRealStreamIndex(type, index_within_type), time); + } + + int GetRealStreamIndex(Stream::Type type, int index_within_type) const; + int GetRealStreamIndex(const StreamReference& ref) const + { + return GetRealStreamIndex(ref.type(), ref.index()); + } + + static QString GetStringFromReference(Stream::Type type, int index); + static QString GetStringFromReference(const StreamReference& ref) + { + return GetStringFromReference(ref.type(), ref.index()); + } + + StreamReference GetReferenceFromRealIndex(int real_index) const; + + Stream::Type GetTypeFromOutput(const QString& output) const; + + StreamReference GetReferenceFromOutput(const QString& s) const; + + int GetStreamCount() const + { + return stream_count_; + } + + int GetStreamTypeCount(Stream::Type type) const; + + bool IsStreamEnabled(int index) const + { + return GetStreamAt(index).enabled(); + } + + Stream GetFirstEnabledStreamOfType(Stream::Type type) const; + + QVector GetStreamIndexesOfType(Stream::Type type) const; + + Stream::Type GetStreamType(int index); /** * @brief Get the Decoder ID set when this Footage was probed @@ -186,7 +331,7 @@ public: */ void set_decoder(const QString& id); - virtual QIcon icon() override; + virtual QIcon icon() const override; virtual QString duration() override; @@ -203,16 +348,38 @@ public: */ bool HasEnabledStreamsOfType(const Stream::Type& type) const; - Stream* get_first_enabled_stream_of_type(const Stream::Type& type) const; - static bool CompareFootageToFile(Footage* footage, const QString& filename); static bool CompareFootageToItsFilename(Footage* footage); -private: + virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override; + + virtual NodeValueTable Value(const QString &output, NodeValueDatabase& value) const override; + + static QString GetStreamTypeName(Stream::Type type); + + static const QString kFilenameInput; + static const QString kStreamPropertiesFormat; + +protected: /** - * @brief Internal function to delete all Stream children and empty the array + * @brief Load function */ - void ClearStreams(); + virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override; + + /** + * @brief Save function + */ + virtual void SaveInternal(QXmlStreamWriter *writer) const override; + + virtual void InputValueChangedEvent(const QString &input, int element) override; + +private: + QString DescribeStream(int index) const; + + struct MetadataCache { + QString decoder; + Streams streams; + }; /** * @brief Update the icon based on the Footage status @@ -230,30 +397,50 @@ private: */ void UpdateTooltip(); + MetadataCache LoadStreamCache(const QString& filename); + + bool SaveStreamCache(const QString& filename, const MetadataCache& data); + + static QString GetInputIDOfIndex(int index) + { + return kStreamPropertiesFormat.arg(index); + } + /** - * @brief Internal filename string + * @brief List of dynamic inputs added for stream properties */ - QString filename_; + QMap inputs_for_stream_properties_; + + /** + * @brief List of dynamic outputs added for streams + */ + QMap outputs_for_streams_; /** * @brief Internal timestamp object */ qint64 timestamp_; - /** - * @brief Internal streams array - */ - QVector streams_; - /** * @brief Internal attached decoder ID */ QString decoder_; + int stream_count_; + bool valid_; + const QAtomicInt* cancelled_; + +private slots: + void CheckFootage(); + }; +uint qHash(const Footage::StreamReference& ref, uint seed = 0); + } +Q_DECLARE_METATYPE(olive::Footage::StreamReference) + #endif // FOOTAGE_H diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp index 87843eb52..f3173aec1 100644 --- a/app/project/item/footage/stream.cpp +++ b/app/project/item/footage/stream.cpp @@ -20,167 +20,112 @@ #include "stream.h" -#include "footage.h" -#include "ui/icons/icons.h" +#include "common/xmlutils.h" namespace olive { -Stream::Stream() : - type_(kUnknown), - enabled_(true) +void Stream::Load(QXmlStreamReader *reader) { - -} - -Stream::~Stream() -{ -} - -Stream *Stream::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled) -{ - Stream* stream = nullptr; - XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("type")) { - Stream::Type type = static_cast(attr.value().toInt()); - switch (type) { - case Stream::kVideo: - stream = new VideoStream(); - break; - case Stream::kAudio: - stream = new AudioStream(); - break; - default: - stream = new Stream(); - stream->set_type(type); - break; - } - - // This is the only attribute we need + *this = Stream(static_cast(attr.value().toInt())); break; } } - if (!stream) { - return nullptr; - } - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("ptr")) { - //xml_node_data.footage_ptrs.insert(reader->readElementText().toULongLong(), stream); - } else if (reader->name() == QStringLiteral("index")) { - stream->set_index(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("timebase")) { - stream->set_timebase(rational::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("duration")) { - stream->set_duration(reader->readElementText().toLongLong()); - } else if (reader->name() == QStringLiteral("enabled")) { - stream->set_enabled(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("custom")) { - stream->LoadCustomParameters(reader); + if (reader->name() == QStringLiteral("global")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("timebase")) { + timebase_ = rational::fromString(reader->readElementText()); + } else if (reader->name() == QStringLiteral("duration")) { + duration_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("channelcount")) { + channel_count_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("enabled")) { + enabled_ = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("video")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("width")) { + width_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("height")) { + height_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("pixelaspectratio")) { + pixel_aspect_ratio_ = rational::fromString(reader->readElementText()); + } else if (reader->name() == QStringLiteral("videotype")) { + video_type_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("interlacing")) { + interlacing_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("pixelformat")) { + pixel_format_ = static_cast(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("framerate")) { + frame_rate_ = rational::fromString(reader->readElementText()); + } else if (reader->name() == QStringLiteral("starttime")) { + start_time_ = reader->readElementText().toLongLong(); + } else if (reader->name() == QStringLiteral("premultipliedalpha")) { + premultiplied_alpha_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("colorspace")) { + colorspace_ = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("audio")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("samplerate")) { + sample_rate_ = reader->readElementText().toInt(); + } else if (reader->name() == QStringLiteral("channellayout")) { + channel_layout_ = reader->readElementText().toULongLong(); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } } - - return stream; } void Stream::Save(QXmlStreamWriter *writer) const { writer->writeAttribute(QStringLiteral("type"), QString::number(type_)); - writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + writer->writeStartElement(QStringLiteral("global")); + writer->writeTextElement(QStringLiteral("timebase"), timebase_.toString()); + writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_)); + writer->writeTextElement(QStringLiteral("channelcount"), QString::number(channel_count_)); + writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_)); + writer->writeEndElement(); // global - writer->writeTextElement(QStringLiteral("index"), QString::number(index_)); + writer->writeStartElement(QStringLiteral("video")); + writer->writeTextElement(QStringLiteral("width"), QString::number(width_)); + writer->writeTextElement(QStringLiteral("height"), QString::number(height_)); + writer->writeTextElement(QStringLiteral("pixelaspectratio"), pixel_aspect_ratio_.toString()); + writer->writeTextElement(QStringLiteral("videotype"), QString::number(video_type_)); + writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_)); + writer->writeTextElement(QStringLiteral("pixelformat"), QString::number(pixel_format_)); + writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString()); + writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_)); + writer->writeTextElement(QStringLiteral("premultipliedalpha"), QString::number(premultiplied_alpha_)); + writer->writeTextElement(QStringLiteral("colorspace"), colorspace_); + writer->writeEndElement(); // video - writer->writeTextElement(QStringLiteral("timebase"), timebase_.toString()); - - writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_)); - - writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_)); - - writer->writeStartElement(QStringLiteral("custom")); - - SaveCustomParameters(writer); - - writer->writeEndElement(); -} - -QString Stream::description() const -{ - return QCoreApplication::translate("Stream", "%1: Unknown").arg(index()); -} - -const Stream::Type &Stream::type() const -{ - return type_; -} - -void Stream::set_type(const Stream::Type &type) -{ - type_ = type; -} - -Footage *Stream::footage() const -{ - return dynamic_cast(parent()); -} - -const rational &Stream::timebase() const -{ - return timebase_; -} - -void Stream::set_timebase(const rational &timebase) -{ - timebase_ = timebase; -} - -const int &Stream::index() const -{ - return index_; -} - -void Stream::set_index(const int &index) -{ - index_ = index; -} - -const int64_t &Stream::duration() const -{ - return duration_; -} - -void Stream::set_duration(const int64_t &duration) -{ - duration_ = duration; - - emit ParametersChanged(); -} - -bool Stream::enabled() const -{ - return enabled_; -} - -void Stream::set_enabled(bool e) -{ - enabled_ = e; -} - -QIcon Stream::icon() const -{ - return QIcon(); -} - -void Stream::LoadCustomParameters(QXmlStreamReader* reader) -{ - reader->skipCurrentElement(); -} - -void Stream::SaveCustomParameters(QXmlStreamWriter*) const -{ + writer->writeStartElement(QStringLiteral("audio")); + writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_)); + writer->writeTextElement(QStringLiteral("channellayout"), QString::number(channel_layout_)); + writer->writeEndElement(); // audio } } diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h index 9e5d055fc..3dc323144 100644 --- a/app/project/item/footage/stream.h +++ b/app/project/item/footage/stream.h @@ -21,35 +21,20 @@ #ifndef STREAM_H #define STREAM_H -#include -#include -#include -#include +#include +#include #include #include "common/rational.h" -#include "ui/icons/icons.h" +#include "render/audioparams.h" +#include "render/videoparams.h" namespace olive { -class Footage; -struct XMLNodeData; - -/** - * @brief A base class for keeping metadata about a media stream. - * - * A Stream can contain video data, audio data, subtitle data, - * etc. and a Stream object stores metadata about it. - * - * The Stream class is fairly simple and is intended to be subclassed for data that pertains specifically to one - * Stream::Type. \see VideoStream and \see AudioStream. - */ -class Stream : public QObject -{ - Q_OBJECT +class Stream { public: enum Type { - kUnknown, + kUnknown = -1, kVideo, kAudio, kData, @@ -57,69 +42,283 @@ public: kAttachment }; - /** - * @brief Stream constructor - */ - Stream(); + enum VideoType { + kVideoTypeVideo, + kVideoTypeStill, + kVideoTypeImageSequence + }; - /** - * @brief Required virtual destructor, serves no purpose - */ - virtual ~Stream() override; - - static Stream* Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled); - - void Save(QXmlStreamWriter *writer) const; - - virtual QString description() const; - - const Type& type() const; - void set_type(const Type& type); - - Footage* footage() const; - - const rational& timebase() const; - void set_timebase(const rational& timebase); - - const int& index() const; - void set_index(const int& index); - - const int64_t& duration() const; - void set_duration(const int64_t& duration); - - bool enabled() const; - void set_enabled(bool e); - - virtual QIcon icon() const; - - QMutex* mutex() + Stream(Type type = kUnknown) : + type_(type) { - return &mutex_; + Init(); } -protected: - virtual void LoadCustomParameters(QXmlStreamReader *reader); + bool IsValid() const + { + return type_ != kUnknown; + } - virtual void SaveCustomParameters(QXmlStreamWriter* writer) const; + Type type() const + { + return type_; + } -signals: - void ParametersChanged(); + const rational& timebase() const + { + return timebase_; + } + + void set_timebase(const rational& timebase) + { + timebase_ = timebase; + } + + int64_t duration() const + { + return duration_; + } + + void set_duration(int64_t duration) + { + duration_ = duration; + } + + int channel_count() const + { + return channel_count_; + } + + void set_channel_count(int c) + { + channel_count_ = c; + } + + bool enabled() const + { + return enabled_; + } + + void set_enabled(bool e) + { + enabled_ = e; + } + + int width() const + { + return width_; + } + + void set_width(int w) + { + width_ = w; + } + + int height() const + { + return height_; + } + + void set_height(int h) + { + height_ = h; + } + + const rational& pixel_aspect_ratio() const + { + return pixel_aspect_ratio_; + } + + void set_pixel_aspect_ratio(const rational& pixel_aspect_ratio) + { + pixel_aspect_ratio_ = pixel_aspect_ratio; + } + + VideoType video_type() const + { + return video_type_; + } + + void set_video_type(VideoType t) + { + video_type_ = t; + } + + VideoParams::Interlacing interlacing() const + { + return interlacing_; + } + + void set_interlacing(VideoParams::Interlacing interlacing) + { + interlacing_ = interlacing; + } + + VideoParams::Format pixel_format() const + { + return pixel_format_; + } + + void set_pixel_format(VideoParams::Format pixel_format) + { + pixel_format_ = pixel_format; + } + + const rational& frame_rate() const + { + return frame_rate_; + } + + void set_frame_rate(const rational& frame_rate) + { + frame_rate_ = frame_rate; + } + + int64_t start_time() const + { + return start_time_; + } + + void set_start_time(int64_t start_time) + { + start_time_ = start_time; + } + + bool premultiplied_alpha() const + { + return premultiplied_alpha_; + } + + void set_premultiplied_alpha(bool premultiplied_alpha) + { + premultiplied_alpha_ = premultiplied_alpha; + } + + const QString& colorspace() const + { + return colorspace_; + } + + void set_colorspace(const QString& c) + { + colorspace_ = c; + } + + int sample_rate() const + { + return sample_rate_; + } + + void set_sample_rate(int sample_rate) + { + sample_rate_ = sample_rate; + } + + uint64_t channel_layout() const + { + return channel_layout_; + } + + void set_channel_layout(uint64_t channel_layout) + { + channel_layout_ = channel_layout; + } + + VideoParams video_params() const + { + if (type_ == kVideo) { + return VideoParams(width_, height_, timebase_, + pixel_format_, channel_count_, pixel_aspect_ratio_, + interlacing_); + } else { + return VideoParams(); + } + } + + AudioParams audio_params() const + { + if (type_ == kAudio) { + return AudioParams(sample_rate_, channel_layout_, AudioParams::kInternalFormat); + } else { + return AudioParams(); + } + } + + void Load(QXmlStreamReader* reader); + + void Save(QXmlStreamWriter* writer) const; + + QByteArray toBytes() const + { + QByteArray arr; + + arr.append(reinterpret_cast(&type_), sizeof(type_)); + arr.append(reinterpret_cast(&timebase_), sizeof(timebase_)); + arr.append(reinterpret_cast(&duration_), sizeof(duration_)); + arr.append(reinterpret_cast(&channel_count_), sizeof(channel_count_)); + arr.append(reinterpret_cast(&enabled_), sizeof(enabled_)); + arr.append(reinterpret_cast(&width_), sizeof(width_)); + arr.append(reinterpret_cast(&height_), sizeof(height_)); + arr.append(reinterpret_cast(&pixel_aspect_ratio_), sizeof(pixel_aspect_ratio_)); + arr.append(reinterpret_cast(&video_type_), sizeof(video_type_)); + arr.append(reinterpret_cast(&interlacing_), sizeof(interlacing_)); + arr.append(reinterpret_cast(&pixel_format_), sizeof(pixel_format_)); + arr.append(reinterpret_cast(&frame_rate_), sizeof(frame_rate_)); + arr.append(reinterpret_cast(&start_time_), sizeof(start_time_)); + arr.append(reinterpret_cast(&premultiplied_alpha_), sizeof(premultiplied_alpha_)); + arr.append(colorspace_.toUtf8()); + arr.append(reinterpret_cast(&sample_rate_), sizeof(sample_rate_)); + arr.append(reinterpret_cast(&channel_layout_), sizeof(channel_layout_)); + + return arr; + } private: - rational timebase_; - - int64_t duration_; - - int index_; + void Init() + { + duration_ = AV_NOPTS_VALUE; + channel_count_ = 0; + enabled_ = true; + width_ = 0; + height_ = 0; + video_type_ = VideoType::kVideoTypeVideo; + interlacing_ = VideoParams::kInterlaceNone; + pixel_format_ = VideoParams::kFormatInvalid; + start_time_ = 0; + premultiplied_alpha_ = false; + sample_rate_ = 0; + channel_layout_ = 0; + } + // Global members Type type_; - + rational timebase_; + int64_t duration_; + int channel_count_; bool enabled_; - QMutex mutex_; + // Video members + int width_; + int height_; + rational pixel_aspect_ratio_; + VideoType video_type_; + VideoParams::Interlacing interlacing_; + VideoParams::Format pixel_format_; + rational frame_rate_; + int64_t start_time_; + bool premultiplied_alpha_; + QString colorspace_; + + // Audio members + int sample_rate_; + uint64_t channel_layout_; }; +using Streams = QVector; + } +Q_DECLARE_METATYPE(olive::Stream) + #endif // STREAM_H diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp deleted file mode 100644 index 0bef7bc42..000000000 --- a/app/project/item/footage/videostream.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "videostream.h" - -#include - -#include "common/timecodefunctions.h" -#include "common/xmlutils.h" -#include "footage.h" -#include "project/project.h" -#include "render/colormanager.h" - -namespace olive { - -VideoStream::VideoStream() : - premultiplied_alpha_(false), - interlacing_(VideoParams::kInterlaceNone), - video_type_(VideoStream::kVideoTypeVideo), - pixel_aspect_ratio_(1), - start_time_(0) -{ - set_type(Stream::kVideo); -} - -QString VideoStream::description() const -{ - if (video_type_ == VideoStream::kVideoTypeStill) { - return QCoreApplication::translate("Stream", "%1: Image - %2x%3").arg(QString::number(index()), - QString::number(width()), - QString::number(height())); - } else { - return QCoreApplication::translate("Stream", "%1: Video - %2x%3").arg(QString::number(index()), - QString::number(width()), - QString::number(height())); - } -} - -const rational &VideoStream::frame_rate() const -{ - return frame_rate_; -} - -void VideoStream::set_frame_rate(const rational &frame_rate) -{ - frame_rate_ = frame_rate; -} - -const int64_t &VideoStream::start_time() const -{ - return start_time_; -} - -void VideoStream::set_start_time(const int64_t &start_time) -{ - start_time_ = start_time; - emit ParametersChanged(); -} - -int64_t VideoStream::get_time_in_timebase_units(const rational &time) const -{ - return Timecode::time_to_timestamp(time, timebase()) + start_time(); -} - -QIcon VideoStream::icon() const -{ - if (video_type_ == kVideoTypeStill) { - return icon::Image; - } else { - return icon::Video; - } -} - -void VideoStream::LoadCustomParameters(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("width")) { - set_width(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("height")) { - set_height(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("premultiplied")) { - set_premultiplied_alpha(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("colorspace")) { - set_colorspace(reader->readElementText()); - } else if (reader->name() == QStringLiteral("interlacing")) { - set_interlacing(static_cast(reader->readElementText().toInt())); - } else if (reader->name() == QStringLiteral("type")) { - set_video_type(static_cast(reader->readElementText().toInt())); - } else if (reader->name() == QStringLiteral("format")) { - set_format(static_cast(reader->readElementText().toInt())); - } else if (reader->name() == QStringLiteral("channels")) { - set_channel_count(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("pixelaspect")) { - set_pixel_aspect_ratio(rational::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("framerate")) { - set_frame_rate(rational::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("starttime")) { - set_start_time(reader->readElementText().toLongLong()); - } else { - reader->skipCurrentElement(); - } - } -} - -void VideoStream::SaveCustomParameters(QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("width"), QString::number(width_)); - writer->writeTextElement(QStringLiteral("height"), QString::number(height_)); - writer->writeTextElement(QStringLiteral("premultiplied"), QString::number(premultiplied_alpha_)); - writer->writeTextElement(QStringLiteral("colorspace"), colorspace_); - writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_)); - writer->writeTextElement(QStringLiteral("type"), QString::number(video_type_)); - writer->writeTextElement(QStringLiteral("format"), QString::number(format_)); - writer->writeTextElement(QStringLiteral("channels"), QString::number(channel_count_)); - writer->writeTextElement(QStringLiteral("pixelaspect"), pixel_aspect_ratio_.toString()); - writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString()); - writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_)); -} - -bool VideoStream::premultiplied_alpha() -{ - QMutexLocker locker(mutex()); - - return premultiplied_alpha_; -} - -void VideoStream::set_premultiplied_alpha(bool e) -{ - mutex()->lock(); - premultiplied_alpha_ = e; - mutex()->unlock(); - - emit ParametersChanged(); -} - -const QString &VideoStream::colorspace(bool default_if_empty) -{ - QMutexLocker locker(mutex()); - - if (colorspace_.isEmpty() && default_if_empty) { - return footage()->project()->color_manager()->GetDefaultInputColorSpace(); - } else { - return colorspace_; - } -} - -void VideoStream::set_colorspace(const QString &color) -{ - mutex()->lock(); - colorspace_ = color; - mutex()->unlock(); - - emit ParametersChanged(); -} - -void VideoStream::ColorConfigChanged() -{ - ColorManager* color_manager = footage()->project()->color_manager(); - - // Check if this colorspace is in the new config - if (!colorspace_.isEmpty()) { - QStringList colorspaces = color_manager->ListAvailableColorspaces(); - if (!colorspaces.contains(colorspace_)) { - // Set to empty if not - colorspace_.clear(); - } - } - - // Either way, the color calculation has likely changed so we signal here - emit ParametersChanged(); -} - -void VideoStream::DefaultColorSpaceChanged() -{ - // If no colorspace is set, this stream uses the default color space and it's just changed - if (colorspace_.isEmpty()) { - emit ParametersChanged(); - } -} - -} diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h deleted file mode 100644 index 9c5569385..000000000 --- a/app/project/item/footage/videostream.h +++ /dev/null @@ -1,179 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef VIDEOSTREAM_H -#define VIDEOSTREAM_H - -#include "render/videoparams.h" -#include "stream.h" - -namespace olive { - -/** - * @brief A Stream derivative containing video-specific information - */ -class VideoStream : public Stream -{ - Q_OBJECT -public: - VideoStream(); - - enum VideoType { - kVideoTypeVideo, - kVideoTypeStill, - kVideoTypeImageSequence - }; - - virtual QString description() const override; - - VideoType video_type() const - { - return video_type_; - } - - void set_video_type(VideoType t) - { - video_type_ = t; - } - - const int& width() const - { - return width_; - } - - void set_width(const int& width) - { - width_ = width; - } - - const int& height() const - { - return height_; - } - - void set_height(const int& height) - { - height_ = height; - } - - const VideoParams::Format& format() const - { - return format_; - } - - void set_format(const VideoParams::Format& format) - { - format_ = format; - } - - int channel_count() const - { - return channel_count_; - } - - void set_channel_count(int c) - { - channel_count_ = c; - } - - bool premultiplied_alpha(); - void set_premultiplied_alpha(bool e); - - const QString& colorspace(bool default_if_empty = true); - void set_colorspace(const QString& color); - - VideoParams::Interlacing interlacing() const - { - return interlacing_; - } - - void set_interlacing(VideoParams::Interlacing i) - { - interlacing_ = i; - - emit ParametersChanged(); - } - - const rational& pixel_aspect_ratio() const - { - return pixel_aspect_ratio_; - } - - void set_pixel_aspect_ratio(const rational& r) - { - // Auto-correct null aspect ratio to 1:1 - if (r.isNull()) { - pixel_aspect_ratio_ = 1; - } else { - pixel_aspect_ratio_ = r; - } - - emit ParametersChanged(); - } - - /** - * @brief Get this video stream's frame rate - * - * Used purely for metadata, rendering uses the timebase instead. - */ - const rational& frame_rate() const; - void set_frame_rate(const rational& frame_rate); - - const int64_t& start_time() const; - void set_start_time(const int64_t& start_time); - - int64_t get_time_in_timebase_units(const rational& time) const; - - virtual QIcon icon() const override; - -public slots: - void ColorConfigChanged(); - - void DefaultColorSpaceChanged(); - -protected: - virtual void LoadCustomParameters(QXmlStreamReader *reader) override; - - virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override; - -private: - int width_; - int height_; - bool premultiplied_alpha_; - QString colorspace_; - VideoParams::Interlacing interlacing_; - - VideoType video_type_; - - VideoParams::Format format_; - - int channel_count_; - - rational pixel_aspect_ratio_; - - rational frame_rate_; - - int64_t start_time_; - -}; - -} - -#endif // VIDEOSTREAM_H diff --git a/app/project/item/item.cpp b/app/project/item/item.cpp index 8d4b437ef..c2925f822 100644 --- a/app/project/item/item.cpp +++ b/app/project/item/item.cpp @@ -20,31 +20,21 @@ #include "item.h" +#include "folder/folder.h" + namespace olive { #define super QObject -Item::Item() : - item_parent_(nullptr), - project_(nullptr) -{ -} +const QString Item::kParentInput = QStringLiteral("parent_in"); -Item::~Item() +Item::Item(bool create_folder_input, bool create_default_output) : + Node(create_default_output) { - setParent(nullptr); -} - -const QString &Item::name() const -{ - return name_; -} - -void Item::set_name(const QString &n) -{ - name_ = n; - - NameChangedEvent(n); + if (create_folder_input) { + // Hierarchy input for items + AddInput(kParentInput, NodeValue::kNone); + } } const QString &Item::tooltip() const @@ -67,91 +57,14 @@ QString Item::rate() return QString(); } -Project *Item::project() const +Folder *Item::item_parent() const { - return project_; + return dynamic_cast(GetConnectedNode(kParentInput)); } -void Item::set_project(Project *project) +void Item::Retranslate() { - project_ = project; - - foreach (Item* i, item_children_) { - i->set_project(project_); - } -} - -QVector Item::get_children_of_type(Type type, bool recursive) const -{ - QVector list; - - foreach (Item* item, item_children_) { - if (item->type() == type) { - list.append(item); - } - - if (recursive && item->CanHaveChildren()) { - list.append(item->get_children_of_type(type, recursive)); - } - } - - return list; -} - -bool Item::CanHaveChildren() const -{ - return false; -} - -bool Item::ChildExistsWithName(const QString &name) -{ - return ChildExistsWithNameInternal(name, this); -} - -void Item::NameChangedEvent(const QString &) -{ -} - -void Item::childEvent(QChildEvent *event) -{ - super::childEvent(event); - - Item* cast_test = dynamic_cast(event->child()); - - if (cast_test) { - if (event->type() == QEvent::ChildAdded) { - - item_children_.append(cast_test); - cast_test->item_parent_ = this; - cast_test->set_project(project_); - - } else if (event->type() == QEvent::ChildRemoved) { - - item_children_.removeOne(cast_test); - cast_test->item_parent_ = nullptr; - cast_test->set_project(nullptr); - - } - } -} - -bool Item::ChildExistsWithNameInternal(const QString &name, Item *folder) -{ - // Loop through all children - foreach (Item* child, folder->item_children_) { - // If this child has the same name, return true - if (child->name() == name) { - return true; - } else if (child->CanHaveChildren()) { - // If the child has children, run function recursively on this item - if (ChildExistsWithNameInternal(name, child)) { - // If it returns true, we've found a child so we can return now - return true; - } - } - } - - return false; + SetInputName(kParentInput, tr("Folder")); } } diff --git a/app/project/item/item.h b/app/project/item/item.h index a410ff302..b0a5f4a03 100644 --- a/app/project/item/item.h +++ b/app/project/item/item.h @@ -30,10 +30,11 @@ #include "common/threadedobject.h" #include "common/xmlutils.h" -#include "project/item/footage/stream.h" +#include "node/node.h" namespace olive { +class Folder; class Project; /** @@ -42,90 +43,29 @@ class Project; * Project objects implement a parent-child hierarchy of Items that can be used throughout the Project. The Item class * itself is abstract and will need to be subclassed to be used in a Project. */ -class Item : public QObject +class Item : public Node { Q_OBJECT public: - enum Type { - kFolder, - kFootage, - kSequence - }; - /** * @brief Item constructor */ - Item(); - - /** - * @brief Required virtual Item destructor - */ - virtual ~Item() override; - - virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) = 0; - - virtual void Save(QXmlStreamWriter* writer) const = 0; - - virtual Type type() const = 0; - - int item_child_count() const - { - return item_children_.size(); - } - - Item* item_child(int i) const - { - return item_children_.at(i); - } - - const QVector& children() const - { - return item_children_; - } - - const QString& name() const; - void set_name(const QString& n); + Item(bool create_folder_input = true, bool create_default_output = true); const QString& tooltip() const; void set_tooltip(const QString& t); - virtual QIcon icon() = 0; - virtual QString duration(); virtual QString rate(); - Item *item_parent() const - { - return item_parent_; - } + Folder *item_parent() const; - Project* project() const; + static const QString kParentInput; - void set_project(Project* project); - - QVector get_children_of_type(Type type, bool recursive) const; - - virtual bool CanHaveChildren() const; - - bool ChildExistsWithName(const QString& name); - -protected: - virtual void NameChangedEvent(const QString& name); - - virtual void childEvent(QChildEvent *event) override; + virtual void Retranslate() override; private: - static bool ChildExistsWithNameInternal(const QString& name, Item* folder); - - QVector item_children_; - - Item* item_parent_; - - Project* project_; - - QString name_; - QString tooltip_; }; diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index da5e657a1..b70d912f1 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -20,7 +20,6 @@ #include "sequence.h" -#include #include #include "config/config.h" @@ -38,192 +37,67 @@ namespace olive { -Sequence::Sequence() -{ - viewer_output_ = new ViewerOutput(); - viewer_output_->SetCanBeDeleted(false); - viewer_output_->setParent(this); - connect(viewer_output_, &ViewerOutput::LabelChanged, this, &Sequence::set_name); -} +const QString Sequence::kTextureInput = QStringLiteral("tex_in"); +const QString Sequence::kSamplesInput = QStringLiteral("samples_in"); +const QString Sequence::kTrackInputFormat = QStringLiteral("track_in_%1"); -void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt *cancelled) -{ - { - XMLAttributeLoop(reader, attr) { - if (cancelled && *cancelled) { - return; - } +#define super Item - if (attr.name() == QStringLiteral("name")) { - set_name(attr.value().toString()); - } else if (attr.name() == QStringLiteral("ptr")) { - xml_node_data.item_ptrs.insert(attr.value().toULongLong(), this); - } +Sequence::Sequence(bool viewer_only_mode) : + Item(!viewer_only_mode, true), + video_frame_cache_(this), + audio_playback_cache_(this), + operation_stack_(0) +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); + + if (!viewer_only_mode) { + // Create TrackList instances + track_lists_.resize(Track::kCount); + + for (int i=0;i(i), track_input_id); + track_lists_.replace(i, list); + connect(list, &TrackList::TrackListChanged, this, &Sequence::UpdateTrackCache); + connect(list, &TrackList::LengthChanged, this, &Sequence::VerifyLength); + connect(list, &TrackList::TrackAdded, this, &Sequence::TrackAdded); + connect(list, &TrackList::TrackRemoved, this, &Sequence::TrackRemoved); } } - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("video")) { - int video_width = 0, video_height = 0, preview_div = 1; - rational video_timebase, video_pixel_aspect; - VideoParams::Interlacing video_interlacing = VideoParams::kInterlaceNone; - VideoParams::Format preview_format = VideoParams::kFormatInvalid; - - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("width")) { - video_width = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("height")) { - video_height = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("timebase")) { - video_timebase = rational::fromString(reader->readElementText()); - } else if (reader->name() == QStringLiteral("divider")) { - preview_div = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("format")) { - preview_format = static_cast(reader->readElementText().toInt()); - } else if (reader->name() == QStringLiteral("pixelaspect")) { - video_pixel_aspect = rational::fromString(reader->readElementText()); - } else if (reader->name() == QStringLiteral("interlacing")) { - video_interlacing = static_cast(reader->readElementText().toInt()); - } else { - reader->skipCurrentElement(); - } - } - - set_video_params(VideoParams(video_width, video_height, video_timebase, preview_format, - VideoParams::kInternalChannelCount, video_pixel_aspect, - video_interlacing, preview_div)); - } else if (reader->name() == QStringLiteral("audio")) { - int rate = 0; - uint64_t layout = 0; - AudioParams::Format format = AudioParams::kFormatInvalid; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("rate")) { - rate = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("layout")) { - layout = reader->readElementText().toULongLong(); - } else if (reader->name() == QStringLiteral("format")) { - format = static_cast(reader->readElementText().toInt()); - } else { - reader->skipCurrentElement(); - } - } - - set_audio_params(AudioParams(rate, layout, format)); - } else if (reader->name() == QStringLiteral("points")) { - - TimelinePoints::Load(reader); - - } else if (reader->name() == QStringLiteral("node") || reader->name() == QStringLiteral("viewer")) { - Node* node; - - if (reader->name() == QStringLiteral("node")) { - node = nullptr; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - QString id = attr.value().toString(); - - node = NodeFactory::CreateFromID(id); - break; - } - } - } - } else { - node = viewer_output_; - } - - if (node) { - node->Load(reader, xml_node_data, cancelled); - node->setParent(this); - } - } else { - reader->skipCurrentElement(); - } - } - - // Make connections - XMLConnectNodes(xml_node_data); - - // Link blocks - XMLLinkBlocks(xml_node_data); + // Create UUID for this node + uuid_ = QUuid::createUuid(); } -void Sequence::Save(QXmlStreamWriter *writer) const +Sequence::~Sequence() { - writer->writeAttribute(QStringLiteral("name"), name()); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); - - writer->writeStartElement(QStringLiteral("video")); - - writer->writeTextElement(QStringLiteral("width"), QString::number(video_params().width())); - writer->writeTextElement(QStringLiteral("height"), QString::number(video_params().height())); - writer->writeTextElement(QStringLiteral("timebase"), video_params().time_base().toString()); - writer->writeTextElement(QStringLiteral("pixelaspect"), video_params().pixel_aspect_ratio().toString()); - writer->writeTextElement(QStringLiteral("interlacing"), QString::number(video_params().interlacing())); - writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params().divider())); - writer->writeTextElement(QStringLiteral("format"), QString::number(video_params().format())); - - writer->writeEndElement(); // video - - writer->writeStartElement(QStringLiteral("audio")); - - writer->writeTextElement(QStringLiteral("rate"), QString::number(audio_params().sample_rate())); - writer->writeTextElement(QStringLiteral("layout"), QString::number(audio_params().channel_layout())); - writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params().format())); - - writer->writeEndElement(); // audio - - // Write TimelinePoints - writer->writeStartElement(QStringLiteral("points")); - TimelinePoints::Save(writer); - writer->writeEndElement(); // points - - foreach (Node* node, nodes()) { - if (node != viewer_output_) { - writer->writeStartElement(QStringLiteral("node")); - writer->writeAttribute(QStringLiteral("id"), node->id()); - node->Save(writer); - writer->writeEndElement(); // node; - } - } - - writer->writeStartElement(QStringLiteral("viewer")); - writer->writeAttribute(QStringLiteral("id"), viewer_output_->id()); - viewer_output_->Save(writer); - writer->writeEndElement(); // viewer; + DisconnectAll(); } -void Sequence::add_default_nodes() +void Sequence::add_default_nodes(MultiUndoCommand* command) { // Create tracks and connect them to the viewer - TimelineAddTrackCommand(viewer_output_->track_list(Track::kVideo)).redo(); - TimelineAddTrackCommand(viewer_output_->track_list(Track::kAudio)).redo(); + command->add_child(new TimelineAddTrackCommand(track_list(Track::kVideo))); + command->add_child(new TimelineAddTrackCommand(track_list(Track::kAudio))); } -Item::Type Sequence::type() const -{ - return kSequence; -} - -QIcon Sequence::icon() +QIcon Sequence::icon() const { return icon::Sequence; } QString Sequence::duration() { - rational timeline_length = viewer_output_->GetLength(); + rational timeline_length = GetLength(); int64_t timestamp = Timecode::time_to_timestamp(timeline_length, video_params().time_base()); @@ -232,27 +106,7 @@ QString Sequence::duration() QString Sequence::rate() { - return QCoreApplication::translate("Sequence", "%1 FPS").arg(video_params().time_base().flipped().toDouble()); -} - -const VideoParams &Sequence::video_params() const -{ - return viewer_output_->video_params(); -} - -void Sequence::set_video_params(const VideoParams &vparam) -{ - viewer_output_->set_video_params(vparam); -} - -const AudioParams &Sequence::audio_params() const -{ - return viewer_output_->audio_params(); -} - -void Sequence::set_audio_params(const AudioParams ¶ms) -{ - viewer_output_->set_audio_params(params); + return tr("%1 FPS").arg(video_params().time_base().flipped().toDouble()); } void Sequence::set_default_parameters() @@ -279,45 +133,48 @@ void Sequence::set_parameters_from_footage(const QVector footage) bool found_audio_params = false; foreach (Footage* f, footage) { - foreach (Stream* s, f->streams()) { - if (!s->enabled()) { + for (int i=0; iGetStreamCount(); i++) { + if (!f->IsStreamEnabled(i)) { continue; } - switch (s->type()) { + Stream s = f->GetStreamAt(i); + + if (!s.IsValid()) { + continue; + } + + switch (s.type()) { case Stream::kVideo: { - VideoStream* vs = static_cast(s); - // If this is a video stream, use these parameters if (!found_video_params) { rational using_timebase; - if (vs->video_type() == VideoStream::kVideoTypeStill) { + if (s.video_type() == Stream::kVideoTypeStill) { // If this is a still image, we'll use it's resolution but won't set // `found_video_params` in case something with a frame rate comes along which we'll // prioritize using_timebase = video_params().time_base(); } else { - using_timebase = vs->frame_rate().flipped(); + using_timebase = s.frame_rate().flipped(); found_video_params = true; } - set_video_params(VideoParams(vs->width(), - vs->height(), + set_video_params(VideoParams(s.width(), + s.height(), using_timebase, - static_cast(Config::Current()["OfflinePixelFormat"].toInt()), + static_cast(Config::Current()[QStringLiteral("OfflinePixelFormat")].toInt()), VideoParams::kInternalChannelCount, - vs->pixel_aspect_ratio(), - vs->interlacing(), - VideoParams::generate_auto_divider(vs->width(), vs->height()))); + s.pixel_aspect_ratio(), + s.interlacing(), + VideoParams::generate_auto_divider(s.width(), s.height()))); } break; } case Stream::kAudio: if (!found_audio_params) { - AudioStream* as = static_cast(s); - set_audio_params(AudioParams(as->sample_rate(), as->channel_layout(), AudioParams::kInternalFormat)); + set_audio_params(AudioParams(s.sample_rate(), s.channel_layout(), AudioParams::kInternalFormat)); found_audio_params = true; } break; @@ -336,14 +193,296 @@ void Sequence::set_parameters_from_footage(const QVector footage) } } -ViewerOutput *Sequence::viewer_output() const +QVector Sequence::GetUnlockedTracks() const { - return viewer_output_; + QVector tracks = GetTracks(); + + for (int i=0;iIsLocked()) { + tracks.removeAt(i); + i--; + } + } + + return tracks; } -void Sequence::NameChangedEvent(const QString &name) +void Sequence::Retranslate() { - viewer_output_->SetLabel(name); + super::Retranslate(); + + SetInputName(kTextureInput, tr("Texture")); + + SetInputName(kSamplesInput, tr("Samples")); + + for (int i=0;i(i)) { + case Track::kVideo: + input_name = tr("Video Tracks"); + break; + case Track::kAudio: + input_name = tr("Audio Tracks"); + break; + case Track::kSubtitle: + input_name = tr("Subtitle Tracks"); + break; + case Track::kNone: + case Track::kCount: + break; + } + + if (!input_name.isEmpty()) { + SetInputName(kTrackInputFormat.arg(i), input_name); + } + } +} + +rational Sequence::GetCustomLength(Track::Type type) const +{ + switch (type) { + case Track::kVideo: + return track_lists_.at(Track::kVideo)->GetTotalLength(); + case Track::kAudio: + return track_lists_.at(Track::kAudio)->GetTotalLength(); + case Track::kSubtitle: + return track_lists_.at(Track::kSubtitle)->GetTotalLength(); + case Track::kNone: + case Track::kCount: + break; + } + + return rational(); +} + +void Sequence::InputConnectedEvent(const QString &input, int element, const NodeOutput &output) +{ + if (input == kTextureInput) { + emit TextureInputChanged(); + } else { + foreach (TrackList* list, track_lists_) { + if (list->track_input() == input) { + // Return because we found our input + list->TrackConnected(output.node(), element); + return; + } + } + } + + super::InputConnectedEvent(input, element, output); +} + +void Sequence::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) +{ + if (input == kTextureInput) { + emit TextureInputChanged(); + } else { + foreach (TrackList* list, track_lists_) { + if (list->track_input() == input) { + // Return because we found our input + list->TrackDisconnected(output.node(), element); + return; + } + } + } + + super::InputDisconnectedEvent(input, element, output); +} + +void Sequence::ShiftAudioEvent(const rational &from, const rational &to) +{ + foreach (Track* track, track_lists_.at(Track::kAudio)->GetTracks()) { + track->waveform().Shift(from, to); + } +} + +void Sequence::UpdateTrackCache() +{ + track_cache_.clear(); + + foreach (TrackList* list, track_lists_) { + foreach (Track* track, list->GetTracks()) { + track_cache_.append(track); + } + } +} + +void Sequence::ShiftVideoCache(const rational &from, const rational &to) +{ + video_frame_cache_.Shift(from, to); + + ShiftVideoEvent(from, to); +} + +void Sequence::ShiftAudioCache(const rational &from, const rational &to) +{ + audio_playback_cache_.Shift(from, to); + + ShiftAudioEvent(from, to); +} + +void Sequence::ShiftCache(const rational &from, const rational &to) +{ + ShiftVideoCache(from, to); + ShiftAudioCache(from, to); +} + +void Sequence::InvalidateCache(const TimeRange& range, const QString& from, int element) +{ + Q_UNUSED(element) + + if (operation_stack_ == 0) { + if (from == kTextureInput || from == kSamplesInput) { + TimeRange invalidated_range(qMax(rational(), range.in()), + qMin(GetLength(), range.out())); + + if (invalidated_range.in() != invalidated_range.out()) { + if (from == kTextureInput) { + video_frame_cache_.Invalidate(invalidated_range); + } else { + audio_playback_cache_.Invalidate(invalidated_range); + } + } + } + + VerifyLength(); + } + + super::InvalidateCache(range, from); +} + +void Sequence::set_video_params(const VideoParams &video) +{ + bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height(); + bool timebase_changed = video_params_.time_base() != video.time_base(); + bool pixel_aspect_changed = video_params_.pixel_aspect_ratio() != video.pixel_aspect_ratio(); + bool interlacing_changed = video_params_.interlacing() != video.interlacing(); + + video_params_ = video; + + if (size_changed) { + emit SizeChanged(video_params_.width(), video_params_.height()); + } + + if (pixel_aspect_changed) { + emit PixelAspectChanged(video_params_.pixel_aspect_ratio()); + } + + if (interlacing_changed) { + emit InterlacingChanged(video_params_.interlacing()); + } + + if (timebase_changed) { + video_frame_cache_.SetTimebase(video_params_.time_base()); + emit TimebaseChanged(video_params_.time_base()); + } + + emit VideoParamsChanged(); + + video_frame_cache_.InvalidateAll(); +} + +void Sequence::set_audio_params(const AudioParams &audio) +{ + audio_params_ = audio; + + emit AudioParamsChanged(); + + // This will automatically InvalidateAll + audio_playback_cache_.SetParameters(audio_params()); +} + +rational Sequence::GetLength() +{ + return last_length_; +} + +void Sequence::VerifyLength() +{ + if (operation_stack_ != 0) { + return; + } + + NodeTraverser traverser; + + rational video_length, audio_length, subtitle_length; + + { + video_length = GetCustomLength(Track::kVideo); + + if (video_length.isNull() && IsInputConnected(kTextureInput)) { + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); + video_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + } + + video_frame_cache_.SetLength(video_length); + } + + { + audio_length = GetCustomLength(Track::kAudio); + + if (audio_length.isNull() && IsInputConnected(kSamplesInput)) { + NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); + audio_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value(); + } + + audio_playback_cache_.SetLength(audio_length); + } + + { + subtitle_length = GetCustomLength(Track::kSubtitle); + } + + rational real_length = qMax(subtitle_length, qMax(video_length, audio_length)); + + if (real_length != last_length_) { + last_length_ = real_length; + emit LengthChanged(last_length_); + } +} + +void Sequence::BeginOperation() +{ + operation_stack_++; + + super::BeginOperation(); +} + +void Sequence::EndOperation() +{ + operation_stack_--; + + super::EndOperation(); +} + +void Sequence::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) +{ + Q_UNUSED(xml_node_data) + Q_UNUSED(version) + Q_UNUSED(cancelled) + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("points")) { + timeline_points_.Load(reader); + } else { + reader->skipCurrentElement(); + } + } +} + +void Sequence::SaveInternal(QXmlStreamWriter *writer) const +{ + // Write TimelinePoints + writer->writeStartElement(QStringLiteral("points")); + timeline_points_.Save(writer); + writer->writeEndElement(); // points +} + +void Sequence::ShiftVideoEvent(const rational &from, const rational &to) +{ + Q_UNUSED(from) + Q_UNUSED(to) } } diff --git a/app/project/item/sequence/sequence.h b/app/project/item/sequence/sequence.h index 956e7ed7f..83600998e 100644 --- a/app/project/item/sequence/sequence.h +++ b/app/project/item/sequence/sequence.h @@ -21,12 +21,24 @@ #ifndef SEQUENCE_H #define SEQUENCE_H +#include + #include "common/rational.h" +#include "node/block/block.h" #include "node/graph.h" -#include "node/output/viewer/viewer.h" -#include "render/videoparams.h" -#include "project/item/footage/stream.h" +#include "node/node.h" +#include "node/output/track/track.h" +#include "node/output/track/tracklist.h" +#include "node/traverser.h" +#include "project/item/footage/footage.h" #include "project/item/item.h" +#include "render/audioparams.h" +#include "render/audioplaybackcache.h" +#include "render/framehashcache.h" +#include "render/videoparams.h" +#include "render/videoparams.h" +#include "timeline/timelinecommon.h" +#include "timeline/timelinepoints.h" #include "timeline/timelinepoints.h" namespace olive { @@ -34,51 +46,180 @@ namespace olive { /** * @brief The main timeline object, an graph of edited clips that forms a complete edit */ -class Sequence : public NodeGraph, public TimelinePoints +class Sequence : public Item, public TimelinePoints { Q_OBJECT public: - Sequence(); + Sequence(bool viewer_only_mode = false); - /** - * @brief Load function - */ - virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) override; + virtual ~Sequence() override; - /** - * @brief Save function - */ - virtual void Save(QXmlStreamWriter *writer) const override; + virtual Node* copy() const override + { + return new Sequence(); + } - void add_default_nodes(); + virtual QString Name() const override + { + return tr("Sequence"); + } - /** - * @brief Item::Type() override - */ - virtual Type type() const override; + virtual QString id() const override + { + return QStringLiteral("org.olivevideoeditor.Olive.sequence"); + } - virtual QIcon icon() override; + virtual QVector Category() const override + { + return {kCategoryProject}; + } + + virtual QString Description() const override + { + return tr("A series of cuts that result in an edited video. Also called a timeline."); + } + + void add_default_nodes(MultiUndoCommand *command); + + virtual QIcon icon() const override; virtual QString duration() override; virtual QString rate() override; - const VideoParams &video_params() const; - void set_video_params(const VideoParams &vparam); - - const AudioParams& audio_params() const; - void set_audio_params(const AudioParams& params); - void set_default_parameters(); void set_parameters_from_footage(const QVector footage); - ViewerOutput* viewer_output() const; + const QVector &GetTracks() const + { + return track_cache_; + } + + Track* GetTrackFromReference(const Track::Reference& track_ref) const + { + return track_lists_.at(track_ref.type())->GetTrackAt(track_ref.index()); + } + + /** + * @brief Same as GetTracks() but omits tracks that are locked. + */ + QVector GetUnlockedTracks() const; + + TrackList* track_list(Track::Type type) const + { + return track_lists_.at(type); + } + + void ShiftVideoCache(const rational& from, const rational& to); + void ShiftAudioCache(const rational& from, const rational& to); + void ShiftCache(const rational& from, const rational& to); + + virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1) override; + + const VideoParams& video_params() const + { + return video_params_; + } + + const AudioParams& audio_params() const + { + return audio_params_; + } + + void set_video_params(const VideoParams &video); + void set_audio_params(const AudioParams &audio); + + rational GetLength(); + + virtual void Retranslate() override; + + FrameHashCache* video_frame_cache() + { + return &video_frame_cache_; + } + + AudioPlaybackCache* audio_playback_cache() + { + return &audio_playback_cache_; + } + + virtual void BeginOperation() override; + + virtual void EndOperation() override; + + static const QString kTextureInput; + static const QString kSamplesInput; + static const QString kTrackInputFormat; + + TimelinePoints* timeline_points() + { + return &timeline_points_; + } + + const QUuid& uuid() const + { + return uuid_; + } + +signals: + void TimebaseChanged(const rational&); + + void LengthChanged(const rational& length); + + void SizeChanged(int width, int height); + + void PixelAspectChanged(const rational& pixel_aspect); + + void InterlacingChanged(VideoParams::Interlacing mode); + + void VideoParamsChanged(); + void AudioParamsChanged(); + + void TrackAdded(Track* track); + void TrackRemoved(Track* track); + + void TextureInputChanged(); + +public slots: + void VerifyLength(); protected: - virtual void NameChangedEvent(const QString& name) override; + virtual void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override; + + virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override; + + virtual rational GetCustomLength(Track::Type type) const; + + virtual void ShiftVideoEvent(const rational &from, const rational &to); + + virtual void ShiftAudioEvent(const rational &from, const rational &to); + + virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) override; + + virtual void SaveInternal(QXmlStreamWriter *writer) const override; private: - ViewerOutput* viewer_output_; + QVector track_lists_; + + QVector track_cache_; + + QUuid uuid_; + + rational last_length_; + + FrameHashCache video_frame_cache_; + + AudioPlaybackCache audio_playback_cache_; + + int operation_stack_; + + VideoParams video_params_; + AudioParams audio_params_; + + TimelinePoints timeline_points_; + +private slots: + void UpdateTrackCache(); }; diff --git a/app/project/project.cpp b/app/project/project.cpp index eca7d6604..4847e11f1 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -26,6 +26,7 @@ #include "common/xmlutils.h" #include "core.h" #include "dialog/progress/progress.h" +#include "node/factory.h" #include "render/diskmanager.h" #include "window/mainwindow/mainwindow.h" @@ -36,7 +37,6 @@ Project::Project() : autorecovery_saved_(true) { root_.setParent(this); - root_.set_project(this); connect(&color_manager_, &ColorManager::ConfigChanged, this, &Project::ColorConfigChanged); @@ -79,6 +79,38 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint *layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data); + } else if (reader->name() == QStringLiteral("nodes")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + QString id; + + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + break; + } + } + } + + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + } else { + Node* node = NodeFactory::CreateFromID(id); + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + } else { + node->Load(reader, xml_node_data, version, cancelled); + node->setParent(this); + } + } + } else { + reader->skipCurrentElement(); + } + } + } else { // Skip this @@ -86,6 +118,12 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint } } + + // Make connections + XMLConnectNodes(xml_node_data); + + // Link blocks + XMLLinkBlocks(xml_node_data); } void Project::Save(QXmlStreamWriter *writer) const @@ -156,11 +194,6 @@ ColorManager *Project::color_manager() return &color_manager_; } -QVector Project::get_items_of_type(Item::Type type) const -{ - return root_.get_children_of_type(type, true); -} - bool Project::is_modified() const { return is_modified_; @@ -199,27 +232,21 @@ const QString &Project::cache_path(bool default_if_empty) const void Project::ColorConfigChanged() { - QVector footage = this->get_items_of_type(Item::kFootage); + QVector footage = root()->ListOutputsOfType(); - foreach (Item* item, footage) { - foreach (Stream* s, static_cast(item)->streams()) { - if (s->type() == Stream::kVideo) { - static_cast(s)->ColorConfigChanged(); - } - } + foreach (Footage* item, footage) { + item->InvalidateAll(QString()); + //static_cast(s)->ColorConfigChanged(); } } void Project::DefaultColorSpaceChanged() { - QVector footage = this->get_items_of_type(Item::kFootage); + QVector footage = root_.ListOutputsOfType(); - foreach (Item* item, footage) { - foreach (Stream* s, static_cast(item)->streams()) { - if (s->type() == Stream::kVideo) { - static_cast(s)->DefaultColorSpaceChanged(); - } - } + foreach (Footage* item, footage) { + item->InvalidateAll(QString()); + //static_cast(s)->DefaultColorSpaceChanged(); } } diff --git a/app/project/project.h b/app/project/project.h index 59501cd97..594f34e17 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -41,7 +41,7 @@ namespace olive { * * Project Settings * * Window Layout */ -class Project : public QObject +class Project : public NodeGraph { Q_OBJECT public: @@ -61,8 +61,6 @@ public: ColorManager* color_manager(); - QVector get_items_of_type(Item::Type type) const; - bool is_modified() const; void set_modified(bool e); diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index b22f71352..94c7e2ac7 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -25,7 +25,7 @@ #include #include "core.h" -#include "node/input/media/media.h" +#include "widget/nodeview/nodeviewundo.h" namespace olive { @@ -48,8 +48,16 @@ void ProjectViewModel::set_project(Project *p) { beginResetModel(); + if (project_) { + DisconnectItem(project_->root()); + } + project_ = p; + if (project_) { + ConnectItem(project_->root()); + } + endResetModel(); } @@ -60,8 +68,8 @@ QModelIndex ProjectViewModel::index(int row, int column, const QModelIndex &pare return QModelIndex(); } - // Get the parent object (project root if the index is invalid) - Item* item_parent = GetItemObjectFromIndex(parent); + // Get the parent object, we assume it's a folder since only folders can have children + Folder* item_parent = static_cast(GetItemObjectFromIndex(parent)); // Return an index to this object return createIndex(row, column, item_parent->item_child(row)); @@ -103,7 +111,7 @@ int ProjectViewModel::rowCount(const QModelIndex &parent) const } // Otherwise, the index must contain a valid pointer, so we just return its child count - return GetItemObjectFromIndex(parent)->item_child_count(); + return static_cast(GetItemObjectFromIndex(parent))->item_child_count(); } int ProjectViewModel::columnCount(const QModelIndex &parent) const @@ -131,7 +139,7 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const switch (column_type) { case kName: - return internal_item->name(); + return internal_item->GetLabel(); case kDuration: return internal_item->duration(); case kRate: @@ -175,20 +183,11 @@ QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation, bool ProjectViewModel::hasChildren(const QModelIndex &parent) const { - // Check if this is a valid index - if (parent.isValid()) { - Item* item = GetItemObjectFromIndex(parent); + // If it's a folder, we always return TRUE in order to always show the "expand triangle" icon, + // even when there are no "physical" children + Item* item = GetItemObjectFromIndex(parent); - // Check if this item is a kFolder type - // If it's a folder, we always return TRUE in order to always show the "expand triangle" icon, - // even when there are no "physical" children - if (item->CanHaveChildren()) { - return true; - } - } - - // Otherwise, return default behavior - return QAbstractItemModel::hasChildren(parent); + return dynamic_cast(item); } bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, int role) @@ -197,9 +196,11 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, if (index.isValid() && columns_.at(index.column()) == kName && role == Qt::EditRole) { Item* item = GetItemObjectFromIndex(index); - RenameItemCommand* ric = new RenameItemCommand(this, item, value.toString()); + NodeRenameCommand* nrc = new NodeRenameCommand(); - Core::instance()->undo_stack()->push(ric); + nrc->AddNode(item, value.toString()); + + Core::instance()->undo_stack()->push(nrc); return true; } @@ -209,20 +210,8 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, bool ProjectViewModel::canFetchMore(const QModelIndex &parent) const { - // Check if this is a valid index - if (parent.isValid()) { - Item* item = GetItemObjectFromIndex(parent); - - // Check if this item is a kFolder type - // If it's a folder, we always return TRUE in order to always show the "expand triangle" icon, - // even when there are no "physical" children - if (item->CanHaveChildren()) { - return true; - } - } - - // Otherwise, return default behavior - return QAbstractItemModel::canFetchMore(parent); + // Use the same hack that always returns true with folders so the expand triangle is always visible + return hasChildren(parent); } Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const @@ -234,7 +223,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const Qt::ItemFlags f = Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index); - if (GetItemObjectFromIndex(index)->CanHaveChildren()) { + if (dynamic_cast(GetItemObjectFromIndex(index))) { f |= Qt::ItemIsDropEnabled; } @@ -277,7 +266,7 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const // If not, add it to the stream (and also keep track of it in the vector) quint64 stream_flags; - if (static_cast(index.internalPointer())->type() == Item::kFootage) { + if (dynamic_cast(static_cast(index.internalPointer()))) { stream_flags = static_cast(index.internalPointer())->get_enabled_stream_flags(); } else { stream_flags = UINT64_MAX; @@ -320,7 +309,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action Item* drop_location = GetItemObjectFromIndex(drop); // If this is not a folder, we cannot drop these items here - if (!drop_location->CanHaveChildren()) { + if (!dynamic_cast(drop_location)) { return false; } @@ -342,8 +331,11 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action // Check if Item is already the drop location or if its parent is the drop location, in which case this is a // no-op - if (item != drop_location && item->parent() != drop_location && !ItemIsParentOfChild(item, drop_location)) { - move_command->add_child(new MoveItemCommand(this, item, static_cast(drop_location))); + if (item != drop_location && item->item_parent() != drop_location && !ItemIsParentOfChild(item, drop_location)) { + NodeInput child_input(item, Item::kParentInput); + + move_command->add_child(new NodeEdgeRemoveCommand(item->item_parent(), child_input)); + move_command->add_child(new NodeEdgeAddCommand(static_cast(drop_location), child_input)); } } @@ -372,7 +364,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action Item* drop_item = GetItemObjectFromIndex(drop); // If we didn't drop onto an item, find the nearest parent folder (should eventually terminate at root either way) - while (!drop_item->CanHaveChildren()) { + while (!dynamic_cast(drop_item)) { drop_item = drop_item->item_parent(); } @@ -383,83 +375,25 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action return false; } -void ProjectViewModel::AddChild(Item *parent, Item *child) -{ - QModelIndex parent_index; - - if (parent != project_->root()) { - parent_index = CreateIndexFromItem(parent); - } - - beginInsertRows(parent_index, parent->item_child_count(), parent->item_child_count()); - - child->setParent(parent); - - endInsertRows(); -} - -void ProjectViewModel::RemoveChild(Item *parent, Item *child, QObject *new_parent) -{ - QModelIndex parent_index; - - if (parent != project_->root()) { - parent_index = CreateIndexFromItem(parent); - } - - int child_row = IndexOfChild(child); - - beginRemoveRows(parent_index, child_row, child_row); - - child->setParent(new_parent); - - endRemoveRows(); -} - -void ProjectViewModel::RenameChild(Item *item, const QString &name) -{ - item->set_name(name); - - QModelIndex index = CreateIndexFromItem(item, columns_.indexOf(kName)); - - emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); -} - int ProjectViewModel::IndexOfChild(Item *item) const { // Find parent's index within its own parent - // (FIXME: this model should handle sorting, which means it'll have to "know" the indices) + Folder* parent = item->item_parent(); - if (item == project_->root()) { - return -1; - } - - Item* parent = item->item_parent(); - - if (parent != nullptr) { - for (int i=0;iitem_child_count();i++) { - if (parent->item_child(i) == item) { - return i; - } - } + if (parent) { + return parent->index_of_child(item); } return -1; } -int ProjectViewModel::ChildCount(const QModelIndex &index) -{ - Item* item = GetItemObjectFromIndex(index); - - return item->item_child_count(); -} - Item *ProjectViewModel::GetItemObjectFromIndex(const QModelIndex &index) const { if (index.isValid()) { return static_cast(index.internalPointer()); } - return project_->root(); + return project_ ? project_->root() : nullptr; } bool ProjectViewModel::ItemIsParentOfChild(Item *parent, Item *child) const @@ -476,18 +410,95 @@ bool ProjectViewModel::ItemIsParentOfChild(Item *parent, Item *child) const return false; } -void ProjectViewModel::MoveItemInternal(Item *item, Item *destination) +void ProjectViewModel::ConnectItem(Item *n) { - QModelIndex item_index = CreateIndexFromItem(item); + connect(n, &Item::LabelChanged, this, &ProjectViewModel::ItemRenamed); - QModelIndex destination_index = CreateIndexFromItem(destination); + Folder* f = dynamic_cast(n); + if (f) { + connect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::FolderBeginInsertItem); + connect(f, &Folder::EndInsertItem, this, &ProjectViewModel::FolderEndInsertItem); + connect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem); + connect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem); - beginMoveRows(item_index.parent(), item_index.row(), item_index.row(), - destination_index, destination->item_child_count()); + foreach (const Node::OutputConnection& c, f->output_connections()) { + Item* item = dynamic_cast(c.second.node()); - item->setParent(destination); + if (item) { + ConnectItem(item); + } + } + } +} - endMoveRows(); +void ProjectViewModel::DisconnectItem(Item *n) +{ + disconnect(n, &Item::LabelChanged, this, &ProjectViewModel::ItemRenamed); + + Folder* f = dynamic_cast(n); + if (f) { + disconnect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::FolderBeginInsertItem); + disconnect(f, &Folder::EndInsertItem, this, &ProjectViewModel::FolderEndInsertItem); + disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem); + disconnect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem); + + foreach (const Node::OutputConnection& c, f->output_connections()) { + Item* item = dynamic_cast(c.second.node()); + + if (item) { + DisconnectItem(item); + } + } + } +} + +void ProjectViewModel::FolderBeginInsertItem(Item *n, int insert_index) +{ + Folder* folder = static_cast(sender()); + + ConnectItem(n); + + QModelIndex index; + + if (folder != project_->root()) { + index = CreateIndexFromItem(folder); + } + + beginInsertRows(index, insert_index, insert_index); +} + +void ProjectViewModel::FolderEndInsertItem() +{ + endInsertRows(); +} + +void ProjectViewModel::FolderBeginRemoveItem(Item *n, int child_index) +{ + Folder* folder = static_cast(sender()); + + DisconnectItem(n); + + QModelIndex index; + + if (folder != project_->root()) { + index = CreateIndexFromItem(folder); + } + + beginRemoveRows(index, child_index, child_index); +} + +void ProjectViewModel::FolderEndRemoveItem() +{ + endRemoveRows(); +} + +void ProjectViewModel::ItemRenamed() +{ + Item* item = static_cast(sender()); + + QModelIndex index = CreateIndexFromItem(item); + + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); } QModelIndex ProjectViewModel::CreateIndexFromItem(Item *item, int column) @@ -495,111 +506,4 @@ QModelIndex ProjectViewModel::CreateIndexFromItem(Item *item, int column) return createIndex(IndexOfChild(item), column, item); } -ProjectViewModel::MoveItemCommand::MoveItemCommand(ProjectViewModel *model, - Item *item, - Folder *destination) : - model_(model), - item_(item), - destination_(destination) -{ - source_ = static_cast(item->parent()); - - set_name(QCoreApplication::translate("MoveItemCommand", "Move Item")); -} - -Project *ProjectViewModel::MoveItemCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::MoveItemCommand::redo() -{ - model_->MoveItemInternal(item_, destination_); -} - -void ProjectViewModel::MoveItemCommand::undo() -{ - model_->MoveItemInternal(item_, source_); -} - -ProjectViewModel::RenameItemCommand::RenameItemCommand(ProjectViewModel* model, Item *item, const QString &name) : - model_(model), - item_(item), - new_name_(name) -{ - old_name_ = item->name(); - - set_name(QCoreApplication::translate("RenameItemCommand", "Rename Item")); -} - -Project *ProjectViewModel::RenameItemCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::RenameItemCommand::redo() -{ - model_->RenameChild(item_, new_name_); -} - -void ProjectViewModel::RenameItemCommand::undo() -{ - model_->RenameChild(item_, old_name_); -} - -ProjectViewModel::AddItemCommand::AddItemCommand(ProjectViewModel* model, Item* folder, Item* child) : - model_(model), - parent_(folder), - child_(child) -{ - // Ensure all operations are done in folder's thread - if (memory_manager_.thread() != parent_->thread()) { - memory_manager_.moveToThread(parent_->thread()); - } - - child_->setParent(&memory_manager_); -} - -Project *ProjectViewModel::AddItemCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::AddItemCommand::redo() -{ - model_->AddChild(parent_, child_); -} - -void ProjectViewModel::AddItemCommand::undo() -{ - model_->RemoveChild(parent_, child_, &memory_manager_); -} - -ProjectViewModel::RemoveItemCommand::RemoveItemCommand(ProjectViewModel *model, Item *item) : - model_(model), - item_(item) -{ - // Ensure all operations are done in folder's thread - parent_ = item_->item_parent(); - - if (memory_manager_.thread() != item_->thread()) { - memory_manager_.moveToThread(item_->thread()); - } -} - -Project *ProjectViewModel::RemoveItemCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::RemoveItemCommand::redo() -{ - model_->RemoveChild(parent_, item_, &memory_manager_); -} - -void ProjectViewModel::RemoveItemCommand::undo() -{ - model_->AddChild(parent_, item_); -} - } diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 5b072755b..5bb2fecb9 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -99,99 +99,11 @@ public: virtual QMimeData * mimeData(const QModelIndexList &indexes) const override; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - /** Other model functions */ - void AddChild(Item* parent, Item* child); - void RemoveChild(Item* parent, Item* child, QObject* new_parent); - void RenameChild(Item* item, const QString& name); - /** * @brief Convenience function for creating QModelIndexes from an Item object */ QModelIndex CreateIndexFromItem(Item* item, int column = 0); - /** - * @brief An UndoCommand for moving an item from one folder to another folder - */ - class MoveItemCommand : public UndoCommand { - public: - MoveItemCommand(ProjectViewModel* model, Item* item, Folder* destination); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - - virtual void undo() override; - - private: - ProjectViewModel* model_; - Item* item_; - Folder* source_; - Folder* destination_; - - }; - - /** - * @brief An UndoCommand for renaming an item - */ - class RenameItemCommand : public UndoCommand { - public: - RenameItemCommand(ProjectViewModel* model, Item* item, const QString& name); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - - virtual void undo() override; - - private: - ProjectViewModel* model_; - Item* item_; - QString old_name_; - QString new_name_; - }; - - /** - * @brief An UndoCommand for adding an item - */ - class AddItemCommand : public UndoCommand { - public: - AddItemCommand(ProjectViewModel* model, Item* folder, Item *child); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - - virtual void undo() override; - - private: - ProjectViewModel* model_; - Item* parent_; - Item* child_; - QObject memory_manager_; - - }; - - /** - * @brief An undo command for removing an item - */ - class RemoveItemCommand : public UndoCommand { - public: - RemoveItemCommand(ProjectViewModel* model, Item* item); - - virtual Project* GetRelevantProject() const override; - - virtual void redo() override; - - virtual void undo() override; - - private: - ProjectViewModel* model_; - Item* item_; - Item* parent_; - QObject memory_manager_; - - }; - private: /** * @brief Retrieve the index of `item` in its parent @@ -205,17 +117,6 @@ private: */ int IndexOfChild(Item* item) const; - /** - * @brief Get the child count of an index - * - * @param index - * - * @return - * - * Return number of children (immediate children only) - */ - int ChildCount(const QModelIndex& index); - /** * @brief Retrieves the Item object from a given index * @@ -230,21 +131,25 @@ private: */ bool ItemIsParentOfChild(Item* parent, Item* child) const; - /** - * @brief Moves an item to a new destination updating all views in the process - * - * This function will emit a signal indicating that rows are moving, set `destination` as the new parent of `item`, - * and then emit a signal that the row has finished moving. - * - * It's not recommended to use this function directly in most cases since it does not create an UndoCommand allowing - * the user to undo the move. Instead this function should primarily be called from UndoCommands belonging to this - * class (e.g. MoveItemCommand). - */ - void MoveItemInternal(Item* item, Item* destination); + void ConnectItem(Item* n); + + void DisconnectItem(Item* n); Project* project_; QVector columns_; + +private slots: + void FolderBeginInsertItem(Item* n, int insert_index); + + void FolderEndInsertItem(); + + void FolderBeginRemoveItem(Item* n, int child_index); + + void FolderEndRemoveItem(); + + void ItemRenamed(); + }; } diff --git a/app/render/colorprocessorcache.h b/app/render/colorprocessorcache.h index 9c9c40b6b..20eb27ecc 100644 --- a/app/render/colorprocessorcache.h +++ b/app/render/colorprocessorcache.h @@ -21,7 +21,6 @@ #ifndef COLORPROCESSORCACHE_H #define COLORPROCESSORCACHE_H -#include "project/item/footage/stream.h" #include "render/colorprocessor.h" namespace olive { diff --git a/app/render/job/CMakeLists.txt b/app/render/job/CMakeLists.txt index 100d7f5e3..75139574f 100644 --- a/app/render/job/CMakeLists.txt +++ b/app/render/job/CMakeLists.txt @@ -18,6 +18,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} render/job/acceleratedjob.cpp render/job/acceleratedjob.h + render/job/footagejob.h render/job/generatejob.h render/job/samplejob.h render/job/shaderjob.h diff --git a/app/dialog/footageproperties/streamproperties/audiostreamproperties.h b/app/render/job/footagejob.h similarity index 59% rename from app/dialog/footageproperties/streamproperties/audiostreamproperties.h rename to app/render/job/footagejob.h index cc1163786..3602251c3 100644 --- a/app/dialog/footageproperties/streamproperties/audiostreamproperties.h +++ b/app/render/job/footagejob.h @@ -18,25 +18,43 @@ ***/ -#ifndef AUDIOSTREAMPROPERTIES_H -#define AUDIOSTREAMPROPERTIES_H +#ifndef FOOTAGEJOB_H +#define FOOTAGEJOB_H -#include "project/item/footage/audiostream.h" -#include "streamproperties.h" +#include "project/item/footage/footage.h" namespace olive { -class AudioStreamProperties : public StreamProperties +class FootageJob { public: - AudioStreamProperties(AudioStream* stream); + FootageJob() = default; - virtual void Accept(MultiUndoCommand* parent) override; + FootageJob(const Footage::StreamReference& ref, const TimeRange& range) : + footage_(ref), + range_(range) + { + } + + const Footage::StreamReference& footage() const + { + return footage_; + } + + const TimeRange& range() const + { + return range_; + } private: - AudioStream* stream_; + Footage::StreamReference footage_; + + TimeRange range_; + }; } -#endif // AUDIOSTREAMPROPERTIES_H +Q_DECLARE_METATYPE(olive::FootageJob) + +#endif // FOOTAGEJOB_H diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp index af43e941f..b7220c733 100644 --- a/app/render/opengl/openglrenderer.cpp +++ b/app/render/opengl/openglrenderer.cpp @@ -437,10 +437,12 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video case NodeValue::kRational: case NodeValue::kFont: case NodeValue::kFile: + case NodeValue::kVideoStreamProperties: + case NodeValue::kAudioStreamProperties: case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: - case NodeValue::kFootage: + case NodeValue::kFootageJob: case NodeValue::kNone: break; } diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index a56c106be..4b9af966d 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -151,12 +151,7 @@ Project *PlaybackCache::GetProject() const return nullptr; } - Sequence* sequence = static_cast(viewer->parent()); - if (!sequence) { - return nullptr; - } - - return sequence->project(); + return viewer->project(); } void PlaybackCache::RemoveRangeFromJobs(const TimeRange &remove) diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index d9548d0ef..34ab1497c 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -59,7 +59,7 @@ void PreviewAutoCacher::SetPaused(bool paused) } } -void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector ×, qint64 job_time) +void PreviewAutoCacher::GenerateHashes(Sequence *viewer, FrameHashCache* cache, const QVector ×, qint64 job_time) { std::vector existing_hashes; @@ -289,6 +289,9 @@ void PreviewAutoCacher::AddNode(Node *node) // Copy node Node* copy = node->copy(); + // Add to project + copy->setParent(&copied_project_); + // Insert into map copy_map_.insert(node, copy); @@ -339,8 +342,8 @@ void PreviewAutoCacher::UpdateAudioParams() void PreviewAutoCacher::SetPlayhead(const rational &playhead) { - cache_range_ = TimeRange(playhead - Config::Current()["DiskCacheBehind"].value(), - playhead + Config::Current()["DiskCacheAhead"].value()); + cache_range_ = TimeRange(playhead - Config::Current()[QStringLiteral("DiskCacheBehind")].value(), + playhead + Config::Current()[QStringLiteral("DiskCacheAhead")].value()); has_changed_ = true; use_custom_range_ = false; @@ -588,7 +591,7 @@ void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) RequeueFrames(); } -void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) +void PreviewAutoCacher::SetViewerNode(Sequence *viewer_node) { if (viewer_node_ == viewer_node) { return; diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 1b28217d2..e4b730ea2 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -7,6 +7,7 @@ #include "node/graph.h" #include "node/node.h" #include "node/output/viewer/viewer.h" +#include "project/project.h" #include "render/colormanager.h" #include "threading/threadticketwatcher.h" @@ -28,7 +29,7 @@ public: /** * @brief Set the viewer node to auto-cache */ - void SetViewerNode(ViewerOutput *viewer_node); + void SetViewerNode(Sequence *viewer_node); /** * @brief If the mouse is held during the next cache invalidation, cache anyway @@ -91,7 +92,7 @@ public: } private: - static void GenerateHashes(ViewerOutput* viewer, FrameHashCache *cache, const QVector& times, qint64 job_time); + static void GenerateHashes(Sequence *viewer, FrameHashCache *cache, const QVector& times, qint64 job_time); void TryRender(); @@ -131,11 +132,13 @@ private: NodeOutput output; }; - ViewerOutput* viewer_node_; + Sequence* viewer_node_; + + Project copied_project_; QVector graph_update_queue_; QHash copy_map_; - ViewerOutput* copied_viewer_node_; + Sequence* copied_viewer_node_; bool paused_; diff --git a/app/render/rendercache.h b/app/render/rendercache.h index f27120e61..678781d83 100644 --- a/app/render/rendercache.h +++ b/app/render/rendercache.h @@ -22,7 +22,6 @@ #define RENDERCACHE_H #include "codec/decoder.h" -#include "project/item/footage/stream.h" namespace olive { @@ -40,7 +39,7 @@ private: }; -using DecoderCache = RenderCache; +using DecoderCache = RenderCache; using ShaderCache = RenderCache; } diff --git a/app/render/renderer.h b/app/render/renderer.h index b75a1e0bc..1c997aec7 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -21,6 +21,7 @@ #ifndef RENDERCONTEXT_H #define RENDERCONTEXT_H +#include #include #include diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index eceb9f89a..a0979c2a3 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -94,13 +94,13 @@ QByteArray RenderManager::Hash(const Node *n, const VideoParams ¶ms, const r hasher.addData(reinterpret_cast(&format), sizeof(VideoParams::Format)); if (n) { - n->Hash(hasher, time); + n->Hash(Node::kDefaultOutput, hasher, time); } return hasher.result(); } -RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, +RenderTicketPtr RenderManager::RenderFrame(Sequence* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, FrameHashCache* cache, bool prioritize) { @@ -118,7 +118,7 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* c prioritize); } -RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, +RenderTicketPtr RenderManager::RenderFrame(Sequence* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, const VideoParams &video_params, const AudioParams &audio_params, const QSize& force_size, @@ -157,12 +157,12 @@ RenderTicketPtr RenderManager::RenderFrame(ViewerOutput* viewer, ColorManager* c return ticket; } -RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize) +RenderTicketPtr RenderManager::RenderAudio(Sequence* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize) { return RenderAudio(viewer, r, viewer->audio_params(), generate_waveforms, prioritize); } -RenderTicketPtr RenderManager::RenderAudio(ViewerOutput* viewer, const TimeRange &r, const AudioParams ¶ms, bool generate_waveforms, bool prioritize) +RenderTicketPtr RenderManager::RenderAudio(Sequence* viewer, const TimeRange &r, const AudioParams ¶ms, bool generate_waveforms, bool prioritize) { // Create ticket RenderTicketPtr ticket = std::make_shared(); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index 8bc580ab8..8d9c50c28 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -80,10 +80,10 @@ public: * * This function is thread-safe. */ - RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + RenderTicketPtr RenderFrame(Sequence *viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, FrameHashCache* cache = nullptr, bool prioritize = false); - RenderTicketPtr RenderFrame(ViewerOutput* viewer, ColorManager* color_manager, + RenderTicketPtr RenderFrame(Sequence* viewer, ColorManager* color_manager, const rational& time, RenderMode::Mode mode, const VideoParams& video_params, const AudioParams& audio_params, const QSize& force_size, @@ -101,8 +101,8 @@ public: * * This function is thread-safe. */ - RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, const AudioParams& params, bool generate_waveforms, bool prioritize = false); - RenderTicketPtr RenderAudio(ViewerOutput* viewer, const TimeRange& r, bool generate_waveforms, bool prioritize = false); + RenderTicketPtr RenderAudio(Sequence* viewer, const TimeRange& r, const AudioParams& params, bool generate_waveforms, bool prioritize = false); + RenderTicketPtr RenderAudio(Sequence *viewer, const TimeRange& r, bool generate_waveforms, bool prioritize = false); RenderTicketPtr SaveFrameToCache(FrameHashCache* cache, FramePtr frame, const QByteArray& hash, bool prioritize = false); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 4079ee03a..adf90aefc 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -54,11 +54,11 @@ void RenderProcessor::Run() switch (type) { case RenderManager::kTypeVideo: { - ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); + Sequence* viewer = Node::ValueToPtr(ticket_->property("viewer")); const VideoParams& video_params = ticket_->property("vparam").value(); rational time = ticket_->property("time").value(); - NodeValueTable table = ProcessInput(viewer, ViewerOutput::kTextureInput, + NodeValueTable table = ProcessInput(viewer, Sequence::kTextureInput, TimeRange(time, time + video_params.time_base())); TexturePtr texture = table.Get(NodeValue::kTexture).value(); @@ -130,10 +130,10 @@ void RenderProcessor::Run() } case RenderManager::kTypeAudio: { - ViewerOutput* viewer = Node::ValueToPtr(ticket_->property("viewer")); + Sequence* viewer = Node::ValueToPtr(ticket_->property("viewer")); TimeRange time = ticket_->property("time").value(); - NodeValueTable table = ProcessInput(viewer, ViewerOutput::kSamplesInput, time); + NodeValueTable table = ProcessInput(viewer, Sequence::kSamplesInput, time); ticket_->Finish(table.Get(NodeValue::kSamples), IsCancelled()); break; @@ -153,9 +153,9 @@ void RenderProcessor::Run() } } -DecoderPtr RenderProcessor::ResolveDecoderFromInput(Stream *stream) +DecoderPtr RenderProcessor::ResolveDecoderFromInput(const QString& decoder_id, const Decoder::CodecStream &stream) { - if (!stream) { + if (!stream.IsValid()) { qWarning() << "Attempted to resolve the decoder of a null stream"; return nullptr; } @@ -166,13 +166,13 @@ DecoderPtr RenderProcessor::ResolveDecoderFromInput(Stream *stream) if (!decoder) { // No decoder - decoder = Decoder::CreateFromID(stream->footage()->decoder()); + decoder = Decoder::CreateFromID(decoder_id); if (decoder->Open(stream)) { decoder_cache_->insert(stream, decoder); } else { - qWarning() << "Failed to open decoder for" << stream->footage()->filename() - << "::" << stream->index(); + qWarning() << "Failed to open decoder for" << stream.filename() + << "::" << stream.stream(); return nullptr; } } @@ -262,32 +262,35 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim } } -QVariant RenderProcessor::ProcessVideoFootage(VideoStream *video_stream, const rational &input_time) +QVariant RenderProcessor::ProcessVideoFootage(const Footage::StreamReference &stream, const rational &input_time) { TexturePtr value = nullptr; // Check the still frame cache. On large frames such as high resolution still images, uploading // and color managing them for every frame is a waste of time, so we implement a small cache here // to optimize such a situation - const VideoParams& video_params = ticket_->property("vparam").value(); + const VideoParams& render_params = ticket_->property("vparam").value(); + VideoParams stream_params = stream.video_params(); ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); // See if we can make this divider larger (i.e. if the fooage is smaller) - int footage_divider = video_params.divider(); + int footage_divider = render_params.divider(); while (footage_divider > 1 - && VideoParams::GetScaledDimension(video_stream->width(), footage_divider-1) < video_params.effective_width() - && VideoParams::GetScaledDimension(video_stream->height(), footage_divider-1) < video_params.effective_height()) { + && VideoParams::GetScaledDimension(stream_params.width(), footage_divider-1) < render_params.effective_width() + && VideoParams::GetScaledDimension(stream_params.height(), footage_divider-1) < render_params.effective_height()) { footage_divider--; } + Stream stream_data = stream.GetStream(); + StillImageCache::EntryPtr want_entry = std::make_shared( nullptr, - video_stream, - ColorProcessor::GenerateID(color_manager, video_stream->colorspace(), color_manager->GetReferenceColorSpace()), - video_stream->premultiplied_alpha(), + stream, + ColorProcessor::GenerateID(color_manager, stream.video_colorspace(), color_manager->GetReferenceColorSpace()), + stream_data.premultiplied_alpha(), footage_divider, - (video_stream->video_type() == VideoStream::kVideoTypeStill) ? 0 : input_time, + (stream_data.video_type() == Stream::kVideoTypeStill) ? 0 : input_time, true); bool found_existing = false; @@ -324,11 +327,31 @@ QVariant RenderProcessor::ProcessVideoFootage(VideoStream *video_stream, const r still_image_cache_->mutex()->unlock(); - DecoderPtr decoder = ResolveDecoderFromInput(video_stream); + QString decoder_id = stream.footage()->decoder(); + + DecoderPtr decoder = nullptr; + + if (stream_data.video_type() == Stream::kVideoTypeVideo) { + decoder = ResolveDecoderFromInput(decoder_id, Decoder::GetCodecStreamFromStreamReference(stream)); + } else { + // Since image sequences involve multiple files, we don't engage the decoder cache + decoder = Decoder::CreateFromID(decoder_id); + + QString frame_filename; + + if (stream_data.video_type() == Stream::kVideoTypeImageSequence) { + int64_t frame_number = stream.GetTimeInTimebaseUnits(input_time); + frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number); + } else { + frame_filename = stream.filename(); + } + + // Decoder will close automatically since it's a stream_ptr + decoder->Open(Decoder::CodecStream(frame_filename, stream.index())); + } if (decoder) { - FramePtr frame = decoder->RetrieveVideo(input_time, - footage_divider); + FramePtr frame = decoder->RetrieveVideo((stream_data.video_type() == Stream::kVideoTypeVideo) ? input_time : Decoder::kAnyTimecode, footage_divider); if (frame) { // Return a texture from the derived class @@ -339,15 +362,17 @@ QVariant RenderProcessor::ProcessVideoFootage(VideoStream *video_stream, const r // We convert to our rendering pixel format, since that will always be float-based which // is necessary for correct color conversion VideoParams managed_params = frame->video_params(); - managed_params.set_format(video_params.format()); + managed_params.set_format(render_params.format()); + managed_params.set_pixel_aspect_ratio(stream_data.pixel_aspect_ratio()); + managed_params.set_interlacing(stream_data.interlacing()); value = render_ctx_->CreateTexture(managed_params); ColorProcessorPtr processor = ColorProcessor::Create(color_manager, - video_stream->colorspace(), + stream.video_colorspace(), color_manager->GetReferenceColorSpace()); render_ctx_->BlitColorManaged(processor, unmanaged_texture, - video_stream->premultiplied_alpha(), + stream_data.premultiplied_alpha(), value.get()); still_image_cache_->mutex()->lock(); @@ -366,16 +391,18 @@ QVariant RenderProcessor::ProcessVideoFootage(VideoStream *video_stream, const r return QVariant::fromValue(value); } -QVariant RenderProcessor::ProcessAudioFootage(AudioStream *stream, const TimeRange &input_time) +QVariant RenderProcessor::ProcessAudioFootage(const Footage::StreamReference &stream, const TimeRange &input_time) { QVariant value; - DecoderPtr decoder = ResolveDecoderFromInput(stream); + DecoderPtr decoder = ResolveDecoderFromInput(stream.footage()->decoder(), Decoder::GetCodecStreamFromStreamReference(stream)); if (decoder) { const AudioParams& audio_params = ticket_->property("aparam").value(); - SampleBufferPtr frame = decoder->RetrieveAudio(input_time, audio_params, &IsCancelled()); + SampleBufferPtr frame = decoder->RetrieveAudio(input_time, audio_params, + stream.footage()->project()->cache_path(), + &IsCancelled()); if (frame) { value = QVariant::fromValue(frame); diff --git a/app/render/renderprocessor.h b/app/render/renderprocessor.h index 68e0f10ac..ba0cab7d4 100644 --- a/app/render/renderprocessor.h +++ b/app/render/renderprocessor.h @@ -43,9 +43,9 @@ public: protected: virtual NodeValueTable GenerateBlockTable(const Track *track, const TimeRange &range) override; - virtual QVariant ProcessVideoFootage(VideoStream* video_stream, const rational &input_time) override; + virtual QVariant ProcessVideoFootage(const Footage::StreamReference &stream, const rational &input_time) override; - virtual QVariant ProcessAudioFootage(AudioStream* stream, const TimeRange &input_time) override; + virtual QVariant ProcessAudioFootage(const Footage::StreamReference &stream, const TimeRange &input_time) override; virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; @@ -62,7 +62,7 @@ private: void Run(); - DecoderPtr ResolveDecoderFromInput(Stream* stream); + DecoderPtr ResolveDecoderFromInput(const QString &decoder_id, const Decoder::CodecStream& stream); RenderTicketPtr ticket_; diff --git a/app/render/stillimagecache.h b/app/render/stillimagecache.h index e87b36974..96516755c 100644 --- a/app/render/stillimagecache.h +++ b/app/render/stillimagecache.h @@ -5,7 +5,7 @@ #include #include "common/rational.h" -#include "project/item/footage/videostream.h" +#include "project/item/footage/footage.h" #include "render/texture.h" namespace olive { @@ -14,7 +14,7 @@ class StillImageCache { public: struct Entry { - Entry(TexturePtr t, VideoStream* s, const QString& cs, bool a, int d, const rational& i, bool w) + Entry(TexturePtr t, const Footage::StreamReference& s, const QString& cs, bool a, int d, const rational& i, bool w) { texture = t; stream = s; @@ -26,7 +26,7 @@ public: } TexturePtr texture; - VideoStream* stream; + Footage::StreamReference stream; QString colorspace; bool alpha_is_associated; int divider; diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index eebf57259..ea95384df 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -24,11 +24,12 @@ namespace olive { -ConformTask::ConformTask(AudioStream *stream, const AudioParams& params) : - stream_(stream), +ConformTask::ConformTask(Footage* footage, int index, const AudioParams& params) : + footage_(footage), + index_(index), params_(params) { - SetTitle(tr("Conforming Audio %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index()))); + SetTitle(tr("Conforming Audio %1:%2").arg(footage_->filename(), QString::number(index_))); } bool ConformTask::Run() diff --git a/app/task/conform/conform.h b/app/task/conform/conform.h index 7e526f740..b38cbd699 100644 --- a/app/task/conform/conform.h +++ b/app/task/conform/conform.h @@ -21,7 +21,7 @@ #ifndef CONFORMTASK_H #define CONFORMTASK_H -#include "project/item/footage/audiostream.h" +#include "project/item/footage/footage.h" #include "render/audioparams.h" #include "task/task.h" @@ -31,13 +31,15 @@ class ConformTask : public Task { Q_OBJECT public: - ConformTask(AudioStream* stream, const AudioParams& params); + ConformTask(Footage* stream, int index, const AudioParams& params); protected: virtual bool Run() override; private: - AudioStream* stream_; + Footage* footage_; + + int index_; AudioParams params_; diff --git a/app/task/export/export.cpp b/app/task/export/export.cpp index 7d97c02a2..927d1ca49 100644 --- a/app/task/export/export.cpp +++ b/app/task/export/export.cpp @@ -25,7 +25,7 @@ namespace olive { -ExportTask::ExportTask(ViewerOutput* viewer_node, +ExportTask::ExportTask(Sequence *viewer_node, ColorManager* color_manager, const ExportParams& params) : RenderTask(viewer_node, params.video_params(), params.audio_params()), diff --git a/app/task/export/export.h b/app/task/export/export.h index 1382d6856..ec4d305b0 100644 --- a/app/task/export/export.h +++ b/app/task/export/export.h @@ -33,7 +33,7 @@ class ExportTask : public RenderTask { Q_OBJECT public: - ExportTask(ViewerOutput *viewer_node, ColorManager *color_manager, const ExportParams ¶ms); + ExportTask(Sequence *viewer_node, ColorManager *color_manager, const ExportParams ¶ms); protected: virtual bool Run() override; diff --git a/app/task/precache/precachetask.cpp b/app/task/precache/precachetask.cpp index 2cc7aaed4..0a70efac7 100644 --- a/app/task/precache/precachetask.cpp +++ b/app/task/precache/precachetask.cpp @@ -24,27 +24,31 @@ namespace olive { -PreCacheTask::PreCacheTask(VideoStream *footage, Sequence* sequence) : - RenderTask(new ViewerOutput(), sequence->video_params(), sequence->audio_params()), - footage_(footage) +PreCacheTask::PreCacheTask(Footage *footage, int index, Sequence* sequence) : + RenderTask(new ViewerOutput(), sequence->video_params(), sequence->audio_params()) { viewer()->set_video_params(sequence->video_params()); viewer()->set_audio_params(sequence->audio_params()); - video_node_ = new MediaInput(); - video_node_->SetStream(footage); + // FIXME: I've been lazy and haven't included support for anything connected to a footage input. + // At the moment, footage nodes have no connectable inputs so it's not a problem, but if + // they ever do, that needs to be addressed immediately. + Q_ASSERT(footage->inputs().isEmpty()); - Node::ConnectEdge(video_node_, NodeInput(viewer(), ViewerOutput::kTextureInput)); + // Copy footage node so it can precache without any modifications from the user screwing it up + footage_ = static_cast(footage->copy()); + index_ = index; + Node::CopyInputs(footage, footage_, false); - SetTitle(tr("Pre-caching %1:%2").arg(footage->footage()->filename(), - QString::number(footage->index()))); + Node::ConnectEdge(footage_, NodeInput(viewer(), ViewerOutput::kTextureInput)); + + SetTitle(tr("Pre-caching %1:%2").arg(footage_->filename())); } PreCacheTask::~PreCacheTask() { // We created this viewer node ourselves, so now we should delete it delete viewer(); - delete video_node_; } bool PreCacheTask::Run() @@ -63,7 +67,7 @@ bool PreCacheTask::Run() } */ - Render(footage_->footage()->project()->color_manager(), + Render(footage_->project()->color_manager(), video_range, TimeRangeList(), RenderMode::kOnline, diff --git a/app/task/precache/precachetask.h b/app/task/precache/precachetask.h index a1f51734d..88afcb001 100644 --- a/app/task/precache/precachetask.h +++ b/app/task/precache/precachetask.h @@ -21,7 +21,6 @@ #ifndef PRECACHETASK_H #define PRECACHETASK_H -#include "node/input/media/media.h" #include "project/item/footage/footage.h" #include "project/item/sequence/sequence.h" #include "task/render/render.h" @@ -32,7 +31,7 @@ class PreCacheTask : public RenderTask { Q_OBJECT public: - PreCacheTask(VideoStream* footage, Sequence* sequence); + PreCacheTask(Footage* footage, int index, Sequence* sequence); virtual ~PreCacheTask() override; @@ -44,9 +43,9 @@ protected: virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override; private: - VideoStream* footage_; + Footage* footage_; - MediaInput* video_node_; + int index_; }; diff --git a/app/task/project/import/import.cpp b/app/task/project/import/import.cpp index 911fe05f7..0fbcfec40 100644 --- a/app/task/project/import/import.cpp +++ b/app/task/project/import/import.cpp @@ -26,6 +26,7 @@ #include "config/config.h" #include "core.h" #include "project/item/footage/footage.h" +#include "widget/nodeview/nodeviewundo.h" namespace olive { @@ -97,12 +98,11 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte f->moveToThread(folder->thread()); - f->set_name(file_info.fileName()); + f->SetLabel(file_info.fileName()); // Create undoable command that adds the items to the model - parent_command->add_child(new ProjectViewModel::AddItemCommand(model_, - folder, - f)); + parent_command->add_child(new NodeAddCommand(folder->parent(), f)); + parent_command->add_child(new NodeEdgeAddCommand(folder, NodeInput(f, Item::kParentInput))); // Recursively follow this path Import(f, entry_list, counter, parent_command); @@ -110,10 +110,11 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte } else { - Footage* footage = Decoder::Probe(model_->project(), file_info.absoluteFilePath(), - &IsCancelled()); + Footage* footage = new Footage(file_info.absoluteFilePath()); - if (footage) { + footage->SetLabel(file_info.fileName()); + + if (footage->IsValid()) { // Move footage to main thread footage->moveToThread(folder->thread()); @@ -121,12 +122,13 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte ValidateImageSequence(footage, import, i); // Create undoable command that adds the items to the model - parent_command->add_child(new ProjectViewModel::AddItemCommand(model_, - folder, - footage)); + parent_command->add_child(new NodeAddCommand(folder->parent(), footage)); + parent_command->add_child(new NodeEdgeAddCommand(folder, NodeInput(footage, Item::kParentInput))); } else { // Add to list so we can tell the user about it later invalid_files_.append(file_info.absoluteFilePath()); + + delete footage; } counter++; @@ -140,13 +142,13 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& info_list, int index) { // Heuristically determine whether this file is part of an image sequence or not - VideoStream* video_stream = static_cast(footage->streams().first()); - + // // By this point we've established that video contains a single still image stream. Now we'll // see if it ends with numbers. if (Decoder::GetImageSequenceDigitCount(footage->filename()) > 0 && !image_sequence_ignore_files_.contains(footage->filename())) { - QSize dim(video_stream->width(), video_stream->height()); + Stream video_stream = footage->GetStreamAt(Stream::kVideo, 0); + QSize dim(video_stream.width(), video_stream.height()); int64_t ind = Decoder::GetImageSequenceIndex(footage->filename()); @@ -156,12 +158,13 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i // See if the same decoder can retrieve surrounding files DecoderPtr decoder = Decoder::CreateFromID(footage->decoder()); - Footage* previous_file = decoder->Probe(previous_img_fn, nullptr); - Footage* next_file = decoder->Probe(next_img_fn, nullptr); + + Footage* previous_file = new Footage(previous_img_fn); + Footage* next_file = new Footage(next_img_fn); // Finally see if these files have the same dimensions - if ((previous_file && CompareStillImageSize(previous_file, dim)) - || (next_file && CompareStillImageSize(next_file, dim))) { + if ((previous_file->IsValid() && CompareStillImageSize(previous_file, dim)) + || (next_file->IsValid() && CompareStillImageSize(next_file, dim))) { // By this point, we've established this file is a still image with a number at the end of // the filename surrounded by adjacent numbers. It could be a still image! But let's ask the // user just in case... @@ -202,34 +205,37 @@ void ProjectImportTask::ValidateImageSequence(Footage *footage, QFileInfoList& i if (is_sequence) { // User has confirmed it is a still image, let's set it accordingly. - video_stream->set_video_type(VideoStream::kVideoTypeImageSequence); + video_stream.set_video_type(Stream::kVideoTypeImageSequence); - rational default_timebase = Config::Current()["DefaultSequenceFrameRate"].value(); - video_stream->set_timebase(default_timebase); - video_stream->set_frame_rate(default_timebase.flipped()); + rational default_timebase = Config::Current()[QStringLiteral("DefaultSequenceFrameRate")].value(); + video_stream.set_timebase(default_timebase); + video_stream.set_frame_rate(default_timebase.flipped()); - video_stream->set_start_time(start_index); - video_stream->set_duration(end_index - start_index + 1); + video_stream.set_start_time(start_index); + video_stream.set_duration(end_index - start_index + 1); + + footage->SetStreamAt(Stream::kVideo, 0, video_stream); } } + + delete previous_file; + delete next_file; } } bool ProjectImportTask::ItemIsStillImageFootageOnly(Footage* footage) { - if (footage->stream_count() != 1) { + if (footage->GetStreamCount() != 1) { // Footage with more than one stream (usually video+audio) most likely isn't an image sequence return false; } - if (footage->streams().first()->type() != Stream::kVideo) { + if (footage->GetStreamAt(0).type() != Stream::kVideo) { // Footage with no video stream definitely isn't an image sequence return false; } - VideoStream* video_stream = static_cast(footage->streams().first()); - - if (video_stream->video_type() != VideoStream::kVideoTypeStill) { + if (footage->GetStreamAt(0).video_type() != Stream::kVideoTypeStill) { // If video type is not a still, this definitely isn't a video stream return false; } @@ -243,9 +249,9 @@ bool ProjectImportTask::CompareStillImageSize(Footage* footage, const QSize &sz) return false; } - VideoStream* video_stream = static_cast(footage->streams().first()); + Stream stream = footage->GetStreamAt(Stream::kVideo, 0); - return video_stream->width() == sz.width() && video_stream->height() == sz.height(); + return stream.width() == sz.width() && stream.height() == sz.height(); } int64_t ProjectImportTask::GetImageSequenceLimit(const QString& start_fn, int64_t start, bool up) diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index c09c129ad..27e387229 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -37,27 +37,30 @@ ProjectLoadTask::ProjectLoadTask(const QString &filename) : bool ProjectLoadTask::Run() { QFile project_file(GetFilename()); - uint project_version = Core::kProjectVersion; if (project_file.open(QFile::ReadOnly | QFile::Text)) { QXmlStreamReader reader(&project_file); + bool ok; + uint project_version = reader.documentVersion().toUInt(&ok); + + if (!ok) { + SetError(tr("Failed to determine project's version identifier.")); + return false; + } else if (project_version > Core::kProjectVersion) { + // Project is newer than we support + SetError(tr("This project is newer than this version of Olive and cannot be opened.")); + return false; + } else if (project_version < 210122) { // Change this if we drop support for a project version + // Project is older than we support + SetError(tr("This project is from a version of Olive that is no longer supported in this version.")); + return false; + } + while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { while(XMLReadNextStartElement(&reader)) { - if (reader.name() == QStringLiteral("version")) { - project_version = reader.readElementText().toUInt(); - - if (project_version > Core::kProjectVersion) { - // Project is newer than we support - SetError(tr("This project is newer than this version of Olive and cannot be opened.")); - return false; - } else if (project_version < 210122) { // Change this if we drop support for a project version - // Project is older than we support - SetError(tr("This project is from a version of Olive that is no longer supported in this version.")); - return false; - } - } else if (reader.name() == QStringLiteral("url")) { + if (reader.name() == QStringLiteral("url")) { project_saved_url_ = reader.readElementText(); } else if (reader.name() == QStringLiteral("project")) { project_ = new Project(); diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index 541a75718..940655e19 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -46,13 +46,11 @@ bool ProjectSaveTask::Run() QXmlStreamWriter writer(&project_file); writer.setAutoFormatting(true); - writer.writeStartDocument(); - - writer.writeStartElement("olive"); - // Version is stored in YYMMDD from whenever the project format was last changed // Allows easy integer math for checking project versions. - writer.writeTextElement("version", QString::number(Core::kProjectVersion)); + writer.writeStartDocument(QString::number(Core::kProjectVersion)); + + writer.writeStartElement("olive"); writer.writeTextElement("url", project_->filename()); diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp index 0529c66ea..dd6ae327d 100644 --- a/app/task/render/render.cpp +++ b/app/task/render/render.cpp @@ -25,7 +25,7 @@ namespace olive { -RenderTask::RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams) : +RenderTask::RenderTask(Sequence* viewer, const VideoParams &vparams, const AudioParams &aparams) : viewer_(viewer), video_params_(vparams), audio_params_(aparams), diff --git a/app/task/render/render.h b/app/task/render/render.h index 9b209565d..8f111f7ec 100644 --- a/app/task/render/render.h +++ b/app/task/render/render.h @@ -35,7 +35,7 @@ class RenderTask : public Task { Q_OBJECT public: - RenderTask(ViewerOutput* viewer, const VideoParams &vparams, const AudioParams &aparams); + RenderTask(Sequence* viewer, const VideoParams &vparams, const AudioParams &aparams); virtual ~RenderTask() override; @@ -53,7 +53,7 @@ protected: virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0; - ViewerOutput* viewer() const + Sequence* viewer() const { return viewer_; } @@ -85,7 +85,7 @@ private: void IncrementRunningTickets(); - ViewerOutput* viewer_; + Sequence* viewer_; VideoParams video_params_; diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index eee700c68..ea219e3ca 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -22,9 +22,9 @@ add_subdirectory(colorlabelmenu) add_subdirectory(colorwheel) add_subdirectory(columnedgridlayout) add_subdirectory(curvewidget) +add_subdirectory(filefield) add_subdirectory(flowlayout) add_subdirectory(focusablelineedit) -add_subdirectory(footagecombobox) add_subdirectory(handmovableview) add_subdirectory(keyframeview) add_subdirectory(manageddisplay) diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index f3f535d1e..ed26d0ffd 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -185,7 +185,7 @@ void CurveWidget::TimeTargetChangedEvent(Node *target) view_->SetTimeTarget(target); } -void CurveWidget::ConnectedNodeChanged(ViewerOutput *n) +void CurveWidget::ConnectedNodeChanged(Sequence *n) { SetTimeTarget(n); } diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index b0a281df7..5c2758811 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -67,7 +67,7 @@ protected: virtual void TimeTargetChangedEvent(Node* target) override; - virtual void ConnectedNodeChanged(ViewerOutput* n) override; + virtual void ConnectedNodeChanged(Sequence* n) override; private: void SetKeyframeButtonEnabled(bool enable); diff --git a/app/node/input/media/CMakeLists.txt b/app/widget/filefield/CMakeLists.txt similarity index 92% rename from app/node/input/media/CMakeLists.txt rename to app/widget/filefield/CMakeLists.txt index 3ff361000..677e41f5d 100644 --- a/app/node/input/media/CMakeLists.txt +++ b/app/widget/filefield/CMakeLists.txt @@ -16,7 +16,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - node/input/media/media.h - node/input/media/media.cpp + widget/filefield/filefield.cpp + widget/filefield/filefield.h PARENT_SCOPE ) diff --git a/app/widget/filefield/filefield.cpp b/app/widget/filefield/filefield.cpp new file mode 100644 index 000000000..222bd6a7b --- /dev/null +++ b/app/widget/filefield/filefield.cpp @@ -0,0 +1,69 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "filefield.h" + +#include +#include +#include + +#include "ui/icons/icons.h" + +namespace olive { + +FileField::FileField(QWidget* parent) : + QWidget(parent) +{ + QHBoxLayout* layout = new QHBoxLayout(this); + + layout->setSpacing(0); + layout->setMargin(0); + + line_edit_ = new QLineEdit(); + connect(line_edit_, &QLineEdit::textChanged, this, &FileField::LineEditChanged); + layout->addWidget(line_edit_); + + browse_btn_ = new QPushButton(); + browse_btn_->setIcon(icon::Open); + connect(browse_btn_, &QPushButton::clicked, this, &FileField::BrowseBtnClicked); + layout->addWidget(browse_btn_); +} + +void FileField::BrowseBtnClicked() +{ + QString s = QFileDialog::getOpenFileName(this, tr("Open File")); + + if (!s.isEmpty()) { + line_edit_->setText(s); + } +} + +void FileField::LineEditChanged(const QString& text) +{ + if (QFileInfo::exists(text)) { + line_edit_->setStyleSheet(QString()); + } else { + line_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}")); + } + + emit FilenameChanged(text); +} + +} diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.h b/app/widget/filefield/filefield.h similarity index 58% rename from app/dialog/footageproperties/streamproperties/streamproperties.h rename to app/widget/filefield/filefield.h index c8457216b..0c7368f8b 100644 --- a/app/dialog/footageproperties/streamproperties/streamproperties.h +++ b/app/widget/filefield/filefield.h @@ -18,27 +18,45 @@ ***/ -#ifndef STREAMPROPERTIES_H -#define STREAMPROPERTIES_H +#ifndef FILEFIELD_H +#define FILEFIELD_H -#include - -#include "common/define.h" -#include "undo/undocommand.h" +#include +#include namespace olive { -class StreamProperties : public QWidget +class FileField : public QWidget { + Q_OBJECT public: - StreamProperties(QWidget* parent = nullptr); + FileField(QWidget* parent = nullptr); - virtual void Accept(MultiUndoCommand*){} + QString GetFilename() const + { + return line_edit_->text(); + } - virtual bool SanityCheck(){return true;} + void SetFilename(const QString& s) + { + line_edit_->setText(s); + } + +signals: + void FilenameChanged(const QString& filename); + +private: + QLineEdit* line_edit_; + + QPushButton* browse_btn_; + +private slots: + void BrowseBtnClicked(); + + void LineEditChanged(const QString &text); }; } -#endif // STREAMPROPERTIES_H +#endif // FILEFIELD_H diff --git a/app/widget/footagecombobox/CMakeLists.txt b/app/widget/footagecombobox/CMakeLists.txt deleted file mode 100644 index 2d83039ff..000000000 --- a/app/widget/footagecombobox/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Olive - Non-Linear Video Editor -# Copyright (C) 2020 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 . - -set(OLIVE_SOURCES - ${OLIVE_SOURCES} - widget/footagecombobox/footagecombobox.h - widget/footagecombobox/footagecombobox.cpp - PARENT_SCOPE -) diff --git a/app/widget/footagecombobox/footagecombobox.cpp b/app/widget/footagecombobox/footagecombobox.cpp deleted file mode 100644 index 5cdb36c1d..000000000 --- a/app/widget/footagecombobox/footagecombobox.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 "footagecombobox.h" - -#include -#include - -#include "ui/icons/icons.h" -#include "widget/menu/menu.h" - -namespace olive { - -FootageComboBox::FootageComboBox(QWidget *parent) : - QComboBox(parent), - root_(nullptr), - footage_(nullptr), - only_show_ready_footage_(true) -{ -} - -void FootageComboBox::showPopup() -{ - if (root_ == nullptr || root_->item_child_count() == 0) { - return; - } - - Menu menu; - - menu.setMinimumWidth(width()); - - TraverseFolder(root_, &menu); - - QAction* selected = menu.exec(parentWidget()->mapToGlobal(pos())); - - if (selected != nullptr) { - SetFootage(Node::ValueToPtr(selected->data())); - - emit FootageChanged(footage_); - } -} - -void FootageComboBox::SetRoot(const Folder *p) -{ - root_ = p; - - clear(); -} - -void FootageComboBox::SetOnlyShowReadyFootage(bool e) -{ - only_show_ready_footage_ = e; -} - -void FootageComboBox::SetFootage(Stream *f) -{ - // Remove existing single item used to show the footage name - footage_ = f; - - UpdateText(); -} - -void FootageComboBox::TraverseFolder(const Folder *f, QMenu *m) const -{ - foreach (Item* child, f->children()) { - - if (child->CanHaveChildren()) { - - Menu* sub = new Menu(child->name(), m); - m->addMenu(sub); - - TraverseFolder(static_cast(child), sub); - - } else if (child->type() == Item::kFootage) { - - Footage* footage = static_cast(child); - - if (footage->IsValid() || !only_show_ready_footage_) { - Menu* stream_menu = new Menu(footage->name(), m); - m->addMenu(stream_menu); - - foreach (Stream* stream, footage->streams()) { - QAction* stream_action = stream_menu->addAction(FootageToString(stream)); - stream_action->setData(Node::PtrToValue(stream)); - stream_action->setIcon(stream->icon()); - } - } - - } - - } -} - -void FootageComboBox::UpdateText() -{ - // Use combobox functions to show the footage name - clear(); - - if (footage_) { - // Use combobox functions to show the footage name - addItem(FootageToString(footage_)); - } -} - -QString FootageComboBox::FootageToString(Stream *f) -{ - return f->description(); -} - -} diff --git a/app/widget/footagecombobox/footagecombobox.h b/app/widget/footagecombobox/footagecombobox.h deleted file mode 100644 index 3c0c59548..000000000 --- a/app/widget/footagecombobox/footagecombobox.h +++ /dev/null @@ -1,71 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2020 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 . - -***/ - -#ifndef FOOTAGECOMBOBOX_H -#define FOOTAGECOMBOBOX_H - -#include -#include - -#include "project/item/footage/footage.h" -#include "project/project.h" - -namespace olive { - -class FootageComboBox : public QComboBox -{ - Q_OBJECT -public: - FootageComboBox(QWidget* parent = nullptr); - - virtual void showPopup() override; - - void SetRoot(const Folder *p); - - void SetOnlyShowReadyFootage(bool e); - - Stream* SelectedFootage() const - { - return footage_; - } - -public slots: - void SetFootage(Stream* f); - -signals: - void FootageChanged(Stream* f); - -private: - void TraverseFolder(const Folder *f, QMenu* m) const; - - void UpdateText(); - - static QString FootageToString(Stream* f); - - const Folder* root_; - - Stream* footage_; - - bool only_show_ready_footage_; -}; - -} - -#endif // FOOTAGECOMBOBOX_H diff --git a/app/widget/keyframeview/keyframeviewundo.cpp b/app/widget/keyframeview/keyframeviewundo.cpp index aa6d782d6..e569e9f60 100644 --- a/app/widget/keyframeview/keyframeviewundo.cpp +++ b/app/widget/keyframeview/keyframeviewundo.cpp @@ -34,7 +34,7 @@ KeyframeSetTypeCommand::KeyframeSetTypeCommand(NodeKeyframe* key, NodeKeyframe:: Project *KeyframeSetTypeCommand::GetRelevantProject() const { - return key_->parent()->parent()->project(); + return key_->parent()->project(); } void KeyframeSetTypeCommand::redo() @@ -65,7 +65,7 @@ KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(NodeKeyframe* key, Project *KeyframeSetBezierControlPoint::GetRelevantProject() const { - return key_->parent()->parent()->project(); + return key_->parent()->project(); } void KeyframeSetBezierControlPoint::redo() diff --git a/app/widget/nodecombobox/nodecombobox.cpp b/app/widget/nodecombobox/nodecombobox.cpp index d8d23c91a..7166f70f7 100644 --- a/app/widget/nodecombobox/nodecombobox.cpp +++ b/app/widget/nodecombobox/nodecombobox.cpp @@ -21,6 +21,7 @@ #include "nodecombobox.h" #include +#include #include #include "node/factory.h" diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index d718973e6..f1470ba27 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -220,7 +220,7 @@ void NodeParamView::TimeChangedEvent(const int64_t ×tamp) UpdateItemTime(timestamp); } -void NodeParamView::ConnectedNodeChanged(ViewerOutput *n) +void NodeParamView::ConnectedNodeChanged(Sequence *n) { // Set viewer as a time target keyframe_view_->SetTimeTarget(n); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 91c842823..9a68e1783 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -96,7 +96,7 @@ protected: virtual void TimebaseChangedEvent(const rational&) override; virtual void TimeChangedEvent(const int64_t &) override; - virtual void ConnectedNodeChanged(ViewerOutput* n) override; + virtual void ConnectedNodeChanged(Sequence* n) override; private: void UpdateItemTime(const int64_t ×tamp); diff --git a/app/widget/nodeparamview/nodeparamviewundo.cpp b/app/widget/nodeparamview/nodeparamviewundo.cpp index 99d45fd07..45268c5e1 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.cpp +++ b/app/widget/nodeparamview/nodeparamviewundo.cpp @@ -34,7 +34,7 @@ NodeParamSetKeyframingCommand::NodeParamSetKeyframingCommand(const NodeInput &in Project *NodeParamSetKeyframingCommand::GetRelevantProject() const { - return input_.node()->parent()->project(); + return input_.node()->project(); } void NodeParamSetKeyframingCommand::redo() @@ -64,7 +64,7 @@ NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* Project *NodeParamSetKeyframeValueCommand::GetRelevantProject() const { - return key_->parent()->parent()->project(); + return key_->parent()->project(); } void NodeParamSetKeyframeValueCommand::redo() @@ -87,7 +87,7 @@ NodeParamInsertKeyframeCommand::NodeParamInsertKeyframeCommand(Node* node, NodeK Project *NodeParamInsertKeyframeCommand::GetRelevantProject() const { - return input_->parent()->project(); + return input_->project(); } void NodeParamInsertKeyframeCommand::redo() @@ -108,7 +108,7 @@ NodeParamRemoveKeyframeCommand::NodeParamRemoveKeyframeCommand(NodeKeyframe* key Project *NodeParamRemoveKeyframeCommand::GetRelevantProject() const { - return input_->parent()->project(); + return input_->project(); } void NodeParamRemoveKeyframeCommand::redo() @@ -138,7 +138,7 @@ NodeParamSetKeyframeTimeCommand::NodeParamSetKeyframeTimeCommand(NodeKeyframe* k Project *NodeParamSetKeyframeTimeCommand::GetRelevantProject() const { - return key_->parent()->parent()->project(); + return key_->parent()->project(); } void NodeParamSetKeyframeTimeCommand::redo() @@ -167,7 +167,7 @@ NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(const NodeKey Project *NodeParamSetStandardValueCommand::GetRelevantProject() const { - return ref_.input().node()->parent()->project(); + return ref_.input().node()->project(); } void NodeParamSetStandardValueCommand::redo() @@ -182,7 +182,7 @@ void NodeParamSetStandardValueCommand::undo() Project *NodeParamArrayInsertCommand::GetRelevantProject() const { - return input_.node()->parent()->project(); + return input_.node()->project(); } } diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index f402c67d6..ba1f75059 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -34,7 +34,7 @@ #include "project/item/sequence/sequence.h" #include "undo/undostack.h" #include "widget/colorbutton/colorbutton.h" -#include "widget/footagecombobox/footagecombobox.h" +#include "widget/filefield/filefield.h" #include "widget/slider/floatslider.h" #include "widget/slider/integerslider.h" @@ -77,9 +77,12 @@ void NodeParamViewWidgetBridge::CreateWidgets() case NodeValue::kMatrix: case NodeValue::kRational: case NodeValue::kSamples: + case NodeValue::kFootageJob: case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoStreamProperties: + case NodeValue::kAudioStreamProperties: break; case NodeValue::kInt: { @@ -120,12 +123,16 @@ void NodeParamViewWidgetBridge::CreateWidgets() break; } case NodeValue::kFile: - // FIXME: File selector + { + FileField* file_field = new FileField(); + widgets_.append(file_field); + connect(file_field, &FileField::FilenameChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; + } case NodeValue::kColor: { // NOTE: Very convoluted way to get back to the project's color manager - ColorButton* color_button = new ColorButton(input_.node()->parent()->project()->color_manager()); + ColorButton* color_button = new ColorButton(input_.node()->project()->color_manager()); widgets_.append(color_button); connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; @@ -151,17 +158,6 @@ void NodeParamViewWidgetBridge::CreateWidgets() connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } - case NodeValue::kFootage: - { - FootageComboBox* footage_combobox = new FootageComboBox(); - footage_combobox->SetRoot(input_.node()->parent()->project()->root()); - - connect(footage_combobox, &FootageComboBox::FootageChanged, this, &NodeParamViewWidgetBridge::WidgetCallback); - - widgets_.append(footage_combobox); - - break; - } } // Check all properties @@ -252,9 +248,12 @@ void NodeParamViewWidgetBridge::WidgetCallback() case NodeValue::kMatrix: case NodeValue::kSamples: case NodeValue::kRational: + case NodeValue::kFootageJob: case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoStreamProperties: + case NodeValue::kAudioStreamProperties: break; case NodeValue::kInt: { @@ -307,8 +306,10 @@ void NodeParamViewWidgetBridge::WidgetCallback() break; } case NodeValue::kFile: - // FIXME: File selector + { + SetInputValue(static_cast(sender())->GetFilename(), 0); break; + } case NodeValue::kColor: { // Sender is a ColorButton @@ -350,12 +351,6 @@ void NodeParamViewWidgetBridge::WidgetCallback() SetInputValue(static_cast(sender())->currentFont().family(), 0); break; } - case NodeValue::kFootage: - { - // Widget is a FootageComboBox - SetInputValue(Node::PtrToValue(static_cast(sender())->SelectedFootage()), 0); - break; - } case NodeValue::kCombo: { // Widget is a QComboBox @@ -403,9 +398,12 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() case NodeValue::kMatrix: case NodeValue::kRational: case NodeValue::kSamples: + case NodeValue::kFootageJob: case NodeValue::kShaderJob: case NodeValue::kSampleJob: case NodeValue::kGenerateJob: + case NodeValue::kVideoStreamProperties: + case NodeValue::kAudioStreamProperties: break; case NodeValue::kInt: { @@ -452,8 +450,13 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() break; } case NodeValue::kFile: - // FIXME: File selector + { + FileField* ff = static_cast(widgets_.first()); + ff->blockSignals(true); + ff->SetFilename(input_.GetValueAtTime(node_time).toString()); + ff->blockSignals(false); break; + } case NodeValue::kColor: { ManagedColor mc = input_.GetValueAtTime(node_time).value(); @@ -494,9 +497,6 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues() cb->blockSignals(false); break; } - case NodeValue::kFootage: - static_cast(widgets_.first())->SetFootage(Node::ValueToPtr(input_.GetValueAtTime(node_time))); - break; } } @@ -620,7 +620,7 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr } // ComboBox strings changing - if (data_type & NodeValue::kCombo) { + if (data_type == NodeValue::kCombo) { QComboBox* cb = static_cast(widgets_.first()); int old_index = cb->currentIndex(); diff --git a/app/widget/nodetableview/nodetabletraverser.cpp b/app/widget/nodetableview/nodetabletraverser.cpp index 07238ee3f..2eead5b81 100644 --- a/app/widget/nodetableview/nodetabletraverser.cpp +++ b/app/widget/nodetableview/nodetabletraverser.cpp @@ -22,21 +22,14 @@ namespace olive { -QVariant NodeTableTraverser::ProcessVideoFootage(VideoStream *video_stream, const rational &input_time) +QVariant NodeTableTraverser::ProcessVideoFootage(const Footage::StreamReference &video_stream, const rational &input_time) { - return QVariant::fromValue(VideoParams(video_stream->width(), - video_stream->height(), - video_stream->timebase(), - video_stream->format(), - video_stream->channel_count(), - video_stream->pixel_aspect_ratio())); + return QVariant::fromValue(video_stream.video_params()); } -QVariant NodeTableTraverser::ProcessAudioFootage(AudioStream *audio_stream, const TimeRange &input_time) +QVariant NodeTableTraverser::ProcessAudioFootage(const Footage::StreamReference &audio_stream, const TimeRange &input_time) { - return QVariant::fromValue(AudioParams(audio_stream->sample_rate(), - audio_stream->channel_layout(), - AudioParams::kInternalFormat)); + return QVariant::fromValue(audio_stream.audio_params()); } } diff --git a/app/widget/nodetableview/nodetabletraverser.h b/app/widget/nodetableview/nodetabletraverser.h index fa5dd25d9..571b105a8 100644 --- a/app/widget/nodetableview/nodetabletraverser.h +++ b/app/widget/nodetableview/nodetabletraverser.h @@ -31,9 +31,9 @@ public: NodeTableTraverser() = default; protected: - virtual QVariant ProcessVideoFootage(VideoStream* video_stream, const rational &input_time); + virtual QVariant ProcessVideoFootage(const Footage::StreamReference&video_stream, const rational &input_time); - virtual QVariant ProcessAudioFootage(AudioStream* audio_stream, const TimeRange &input_time); + virtual QVariant ProcessAudioFootage(const Footage::StreamReference& audio_stream, const TimeRange &input_time); }; diff --git a/app/widget/nodetreeview/nodetreeview.cpp b/app/widget/nodetreeview/nodetreeview.cpp index 33a881ae1..80ffbc27c 100644 --- a/app/widget/nodetreeview/nodetreeview.cpp +++ b/app/widget/nodetreeview/nodetreeview.cpp @@ -1,5 +1,27 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 "nodetreeview.h" +#include + namespace olive { NodeTreeView::NodeTreeView(QWidget *parent) : diff --git a/app/widget/nodetreeview/nodetreeview.h b/app/widget/nodetreeview/nodetreeview.h index 6b66d15d4..8b10d567a 100644 --- a/app/widget/nodetreeview/nodetreeview.h +++ b/app/widget/nodetreeview/nodetreeview.h @@ -1,3 +1,23 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2020 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 . + +***/ + #ifndef NODETREEVIEW_H #define NODETREEVIEW_H diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 0124e563a..b798d09f2 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -367,6 +367,10 @@ void NodeView::mousePressEvent(QMouseEvent *event) if (item) { create_edge_ = new NodeViewEdge(); create_edge_src_ = item; + + create_edge_->SetCurved(scene_.GetEdgesAreCurved()); + create_edge_->SetFlowDirection(scene_.GetFlowDirection()); + scene_.addItem(create_edge_); return; } diff --git a/app/widget/nodeview/nodeviewedge.cpp b/app/widget/nodeview/nodeviewedge.cpp index 34a5dc443..0d4a919d5 100644 --- a/app/widget/nodeview/nodeviewedge.cpp +++ b/app/widget/nodeview/nodeviewedge.cpp @@ -154,7 +154,9 @@ void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir) { flow_dir_ = dir; - Adjust(); + if (from_item_ && to_item_) { + Adjust(); + } } void NodeViewEdge::SetCurved(bool e) diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 1ac1b234c..977e6a9d7 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -61,7 +61,7 @@ void NodeEdgeAddCommand::undo() Project *NodeEdgeAddCommand::GetRelevantProject() const { - return output_.node()->parent()->project(); + return output_.node()->project(); } NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(const NodeOutput &output, const NodeInput &input) : @@ -82,13 +82,17 @@ void NodeEdgeRemoveCommand::undo() Project *NodeEdgeRemoveCommand::GetRelevantProject() const { - return output_.node()->parent()->project(); + return output_.node()->project(); } NodeAddCommand::NodeAddCommand(NodeGraph *graph, Node *node) : graph_(graph), node_(node) { + if (memory_manager_.thread() != node->thread()) { + memory_manager_.moveToThread(node_->thread()); + } + // Ensures that when this command is destroyed, if redo() is never called again, the node will be destroyed too node_->setParent(&memory_manager_); } @@ -105,7 +109,7 @@ void NodeAddCommand::undo() Project *NodeAddCommand::GetRelevantProject() const { - return graph_->project(); + return dynamic_cast(graph_); } NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include_connections) : @@ -139,4 +143,30 @@ void NodeRemoveAndDisconnectCommand::prep() } } +void NodeRenameCommand::AddNode(Node *node, const QString &new_name) +{ + nodes_.append(node); + new_labels_.append(new_name); + old_labels_.append(node->GetLabel()); +} + +void NodeRenameCommand::redo() +{ + for (int i=0; iSetLabel(new_labels_.at(i)); + } +} + +void NodeRenameCommand::undo() +{ + for (int i=0; iSetLabel(old_labels_.at(i)); + } +} + +Project *NodeRenameCommand::GetRelevantProject() const +{ + return nodes_.isEmpty() ? nullptr : nodes_.first()->project(); +} + } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 90ce7eb43..2d97cff6e 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -23,6 +23,7 @@ #include "node/graph.h" #include "node/node.h" +#include "project/project.h" #include "undo/undocommand.h" namespace olive { @@ -104,11 +105,7 @@ public: virtual Project* GetRelevantProject() const override { - if (graph_) { - return graph_->project(); - } else { - return node_->parent()->project(); - } + return dynamic_cast(graph_); } virtual void redo() override @@ -165,7 +162,7 @@ public: if (command_) { return static_cast(command_->child(0))->GetRelevantProject(); } else { - return node_->parent()->project(); + return node_->project(); } } @@ -236,7 +233,7 @@ public: virtual Project* GetRelevantProject() const override { - return a_->parent()->project(); + return a_->project(); } virtual void redo() override @@ -276,7 +273,7 @@ public: virtual Project* GetRelevantProject() const override { - return node_->parent()->project(); + return node_->project(); } virtual void redo() override @@ -320,7 +317,7 @@ public: virtual Project* GetRelevantProject() const override { - return nodes_.first()->parent()->project(); + return nodes_.first()->project(); } private: @@ -328,6 +325,27 @@ private: }; +class NodeRenameCommand : public UndoCommand +{ +public: + NodeRenameCommand() = default; + + void AddNode(Node* node, const QString& new_name); + + virtual void redo() override; + + virtual void undo() override; + + virtual Project * GetRelevantProject() const override; + +private: + QVector nodes_; + + QStringList new_labels_; + QStringList old_labels_; + +}; + } #endif // NODEVIEWUNDO_H diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 12b9be2d2..8e20a776f 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -30,7 +30,6 @@ #include "common/define.h" #include "core.h" -#include "dialog/footageproperties/footageproperties.h" #include "dialog/sequence/sequence.h" #include "projectexplorerundo.h" #include "task/precache/precachetask.h" @@ -149,7 +148,7 @@ void ProjectExplorer::BrowseToFolder(const QModelIndex &index) // Set navbar text to folder's name if (index.isValid()) { Folder* f = static_cast(sort_model_.mapToSource(index).internalPointer()); - nav_bar_->set_text(f->name()); + nav_bar_->set_text(f->GetLabel()); } else { // Or set it to an empty string if the index is valid (which means we're browsing to the root directory) nav_bar_->set_text(QString()); @@ -202,8 +201,7 @@ void ProjectExplorer::ItemDoubleClickedSlot(const QModelIndex &index) Item* i = static_cast(sort_model_.mapToSource(index).internalPointer()); // If the item is a folder, browse to it - if (i->CanHaveChildren() - && (view_type() == ProjectToolbar::ListView || view_type() == ProjectToolbar::IconView)) { + if (dynamic_cast(i) && (view_type() == ProjectToolbar::ListView || view_type() == ProjectToolbar::IconView)) { BrowseToFolder(index); @@ -273,18 +271,16 @@ void ProjectExplorer::ShowContextMenu() if (context_menu_items_.size() == 1) { Item* context_menu_item = context_menu_items_.first(); - switch (context_menu_item->type()) { - case Item::kFolder: - { + if (dynamic_cast(context_menu_item)) { + QAction* open_in_new_tab = menu.addAction(tr("Open in New Tab")); connect(open_in_new_tab, &QAction::triggered, this, &ProjectExplorer::OpenContextMenuItemInNewTab); QAction* open_in_new_window = menu.addAction(tr("Open in New Window")); connect(open_in_new_window, &QAction::triggered, this, &ProjectExplorer::OpenContextMenuItemInNewWindow); - break; - } - case Item::kFootage: - { + + } else if (dynamic_cast(context_menu_item)) { + QString reveal_text; #if defined(Q_OS_WINDOWS) @@ -297,10 +293,7 @@ void ProjectExplorer::ShowContextMenu() QAction* reveal_action = menu.addAction(reveal_text); connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage); - break; - } - case Item::kSequence: - break; + } menu.addSeparator(); @@ -311,15 +304,18 @@ void ProjectExplorer::ShowContextMenu() bool all_items_are_footage_or_sequence = true; foreach (Item* i, context_menu_items_) { - if (i->type() == Item::kFootage && !static_cast(i)->HasEnabledStreamsOfType(Stream::kVideo)) { + Footage* footage_cast_test = dynamic_cast(i); + Sequence* sequence_cast_test = dynamic_cast(i); + + if (footage_cast_test && !footage_cast_test->HasEnabledStreamsOfType(Stream::kVideo)) { all_items_have_video_streams = false; } - if (i->type() != Item::kFootage) { + if (!footage_cast_test) { all_items_are_footage = false; } - if (i->type() != Item::kFootage && i->type() != Item::kSequence) { + if (!footage_cast_test && !sequence_cast_test) { all_items_are_footage_or_sequence = false; } } @@ -328,14 +324,14 @@ void ProjectExplorer::ShowContextMenu() Menu* proxy_menu = new Menu(tr("Pre-Cache"), &menu); menu.addMenu(proxy_menu); - QVector sequences = project()->get_items_of_type(Item::kSequence); + QVector sequences = project()->root()->ListOutputsOfType(); if (sequences.isEmpty()) { QAction* a = proxy_menu->addAction(tr("No sequences exist in project")); a->setEnabled(false); } else { - foreach (Item* i, sequences) { - QAction* a = proxy_menu->addAction(tr("For \"%1\"").arg(i->name())); + foreach (Sequence* i, sequences) { + QAction* a = proxy_menu->addAction(tr("For \"%1\"").arg(i->GetLabel())); a->setData(Node::PtrToValue(i)); } @@ -360,26 +356,20 @@ void ProjectExplorer::ShowItemPropertiesDialog() { Item* sel = context_menu_items_.first(); - switch (sel->type()) { - case Item::kFootage: - { - // FIXME: Support for multiple items - FootagePropertiesDialog fpd(this, static_cast(sel)); - fpd.exec(); - break; - } - case Item::kFolder: - { - // FIXME: Rename dialog probably - break; - } - case Item::kSequence: - { - // FIXME: Support for multiple items + // FIXME: Support for multiple items + if (dynamic_cast(sel)) { + + Core::instance()->LabelNodes({static_cast(sel)}); + + } else if (dynamic_cast(sel)) { + + Core::instance()->LabelNodes({static_cast(sel)}); + + } else if (dynamic_cast(sel)) { + SequenceDialog sd(static_cast(sel), SequenceDialog::kExisting, this); sd.exec(); - break; - } + } } @@ -410,35 +400,30 @@ void ProjectExplorer::RevealSelectedFootage() void ProjectExplorer::OpenContextMenuItemInNewTab() { - Core::instance()->main_window()->FolderOpen(project(), context_menu_items_.first(), false); + Core::instance()->main_window()->FolderOpen(project(), static_cast(context_menu_items_.first()), false); } void ProjectExplorer::OpenContextMenuItemInNewWindow() { - Core::instance()->main_window()->FolderOpen(project(), context_menu_items_.first(), true); + Core::instance()->main_window()->FolderOpen(project(), static_cast(context_menu_items_.first()), true); } void ProjectExplorer::ContextMenuStartProxy(QAction *a) { - QVector video_streams; + Sequence* sequence = Node::ValueToPtr(a->data()); // To get here, the `context_menu_items_` must be all kFootage foreach (Item* i, context_menu_items_) { Footage* f = static_cast(i); - VideoStream* s = static_cast(f->get_first_enabled_stream_of_type(Stream::kVideo)); - if (s) { - video_streams.append(s); + QVector video_streams = f->GetStreamIndexesOfType(Stream::kVideo); + + foreach (int stream, video_streams) { + // Start a background task for proxying + PreCacheTask* proxy_task = new PreCacheTask(f, stream, sequence); + TaskManager::instance()->AddTask(proxy_task); } } - - Sequence* sequence = Node::ValueToPtr(a->data()); - - // Start a background task for proxying - foreach (VideoStream* video_stream, video_streams) { - PreCacheTask* proxy_task = new PreCacheTask(video_stream, sequence); - TaskManager::instance()->AddTask(proxy_task); - } } Project *ProjectExplorer::project() const @@ -456,7 +441,7 @@ QModelIndex ProjectExplorer::get_root_index() const return tree_view_->rootIndex(); } -void ProjectExplorer::set_root(Item *item) +void ProjectExplorer::set_root(Folder *item) { QModelIndex index = sort_model_.mapFromSource(model_.CreateIndexFromItem(item)); @@ -505,10 +490,8 @@ Folder *ProjectExplorer::GetSelectedFolder() const Item* sel_item = selected_items.at(i); // If this item is not a folder, presumably it's parent is - if (!sel_item->CanHaveChildren()) { + if (!dynamic_cast(sel_item)) { sel_item = sel_item->item_parent(); - - Q_ASSERT(sel_item->CanHaveChildren()); } if (folder == nullptr) { @@ -545,28 +528,6 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QVector ProjectExplorer::GetMediaNodesUsingFootage(Footage *item) -{ - QVector list; - - // Get all sequences. - QVector sequences = model_.project()->get_items_of_type(Item::kSequence); - - // Footage can contain multiple streams, all of which need to be dealt with - foreach (Item* s, sequences) { - const QVector& nodes = static_cast(s)->nodes(); - foreach (Node* n, nodes) { - MediaInput* media_node = dynamic_cast(n); - - if (media_node && media_node->stream()->footage() == item) { - list.append(media_node); - } - } - } - - return list; -} - void ProjectExplorer::DeleteSelected() { QVector selected = SelectedItems(); @@ -577,110 +538,78 @@ void ProjectExplorer::DeleteSelected() MultiUndoCommand* command = new MultiUndoCommand(); + bool dont_confirm_footage_in_use = false; + foreach (Item* item, selected) { // Verify whether this item is in use anywhere - switch (item->type()) { - case Item::kSequence: - { - // If this is a sequence, check if it's open and close it if necessary - Sequence* s = static_cast(item); + Footage* footage_cast_test = dynamic_cast(item); + Sequence* sequence_cast_test = dynamic_cast(item); - if (Core::instance()->main_window()->IsSequenceOpen(s)) { - Core::instance()->main_window()->CloseSequence(s); + bool cleared_to_delete = true; + + if (sequence_cast_test) { + if (Core::instance()->main_window()->IsSequenceOpen(sequence_cast_test)) { + Core::instance()->main_window()->CloseSequence(sequence_cast_test); } - break; - } - case Item::kFootage: - { - // If this is footage, check if it's used anywhere in any sequence - Footage* footage = static_cast(item); + } else if (footage_cast_test) { + if (!footage_cast_test->output_connections().empty() && !dont_confirm_footage_in_use) { + // Footage outputs to other nodes, warn the user + QMessageBox msgbox(this); + msgbox.setWindowTitle(tr("Confirm Footage Deletion")); + msgbox.setIcon(QMessageBox::Warning); - QVector footage_nodes = GetMediaNodesUsingFootage(footage); + QStringList connected_nodes; + foreach (const Node::OutputConnection& c, footage_cast_test->output_connections()) { + Node* connected = c.second.node(); - if (!footage_nodes.isEmpty()) { - // Footage is in use, show messagebox asking what to do about it - QList used_in_sequences; - - // Compile list of sequences to assist the user in making this decision - foreach (MediaInput* i, footage_nodes) { - Sequence* media_parent = static_cast(i->parent()); - - if (!used_in_sequences.contains(media_parent)) { - used_in_sequences.append(media_parent); + if (connected->GetLabel().isEmpty()) { + connected_nodes.append(connected->Name()); + } else { + connected_nodes.append(QStringLiteral("%1 (%2)").arg(connected->GetLabel(), connected->Name())); } } - QString sequence_list_str; - foreach (Sequence* s, used_in_sequences) { - sequence_list_str.append(QStringLiteral("%1\n").arg(s->name())); - } + msgbox.setText(tr("The footage \"%1\" is currently connected to the following nodes:\n\n" + "%2\n\n" + "Are you sure you wish to delete this footage?") + .arg(footage_cast_test->filename(), connected_nodes.join('\n'))); - QMessageBox msgbox(this); - msgbox.setWindowTitle(tr("Confirm Footage Deletion")); - msgbox.setText(tr("The footage \"%1\" is currently used in the following sequence(s):\n\n" - "%2\nWhat would you like to do with these clips?") - .arg(footage->filename(), sequence_list_str)); - msgbox.setIcon(QMessageBox::Warning); // Set up buttons - QPushButton* offline_btn = msgbox.addButton(tr("Offline Footage"), QMessageBox::YesRole); - QPushButton* delete_clip_btn = msgbox.addButton(tr("Delete Clips"), QMessageBox::NoRole); + msgbox.addButton(QMessageBox::Yes); + msgbox.addButton(QMessageBox::YesToAll); + msgbox.addButton(QMessageBox::No); msgbox.addButton(QMessageBox::Cancel); // Run messagebox - msgbox.exec(); + int r = msgbox.exec(); - if (msgbox.clickedButton() == offline_btn || msgbox.clickedButton() == delete_clip_btn) { - - // For safety, even if we're deleting clips, we'll offline the footage nodes too - command->add_child(new OfflineFootageCommand(footage_nodes)); - - } - - if (msgbox.clickedButton() == delete_clip_btn) { - - // Delete any blocks that use this footage - QVector blocks_to_remove; - - foreach (Sequence* s, used_in_sequences) { - foreach (Track* track, s->viewer_output()->GetTracks()) { - foreach (Block* b, track->Blocks()) { - QVector deps = b->GetDependencies(); - - foreach (MediaInput* i, footage_nodes) { - if (deps.contains(i)) { - blocks_to_remove.append(b); - break; - } - } - } - } - } - - TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command); - - } else if (msgbox.clickedButton() != offline_btn) { - - // Must have cancelled + switch (r) { + case QMessageBox::Cancel: + // Stop this entire function delete command; return; - + case QMessageBox::No: + cleared_to_delete = false; + break; + case QMessageBox::YesToAll: + dont_confirm_footage_in_use = true; + break; } } - // Close footage if currently open in footage panel - FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType().first(); - if (footage_panel->GetSelectedFootage().contains(footage)) { - footage_panel->SetFootage(nullptr); + if (cleared_to_delete) { + // Close footage if currently open in footage panel + FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType().first(); + if (footage_panel->GetSelectedFootage().contains(footage_cast_test)) { + footage_panel->SetFootage(nullptr); + } } - break; - } - case Item::kFolder: - // Do nothing - break; } - command->add_child(new ProjectViewModel::RemoveItemCommand(&model_, item)); + if (cleared_to_delete) { + command->add_child(new NodeRemoveAndDisconnectCommand(reinterpret_cast(item))); + } } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 98ad94c84..01bc409fe 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -26,7 +26,6 @@ #include #include -#include "node/input/media/media.h" #include "project/project.h" #include "project/projectviewmodel.h" #include "widget/projectexplorer/projectexplorericonview.h" @@ -58,7 +57,7 @@ public: QModelIndex get_root_index() const; - void set_root(Item* item); + void set_root(Folder *item); QVector SelectedItems() const; @@ -103,11 +102,6 @@ signals: void DoubleClickedItem(Item* item); private: - /** - * @brief Check if an item is in use anywhere and return any relevant input nodes - */ - QVector GetMediaNodesUsingFootage(Footage* item); - /** * @brief Get all the blocks that solely rely on an input node * diff --git a/app/widget/projectexplorer/projectexplorerundo.h b/app/widget/projectexplorer/projectexplorerundo.h index 19ec360c0..99919929f 100644 --- a/app/widget/projectexplorer/projectexplorerundo.h +++ b/app/widget/projectexplorer/projectexplorerundo.h @@ -21,51 +21,10 @@ #ifndef PROJECTEXPLORERUNDO_H #define PROJECTEXPLORERUNDO_H -#include "node/input/media/media.h" #include "undo/undocommand.h" namespace olive { -/** - * @brief An undo command for offlining footage when it is deleted from the project explorer - */ -class OfflineFootageCommand : public UndoCommand { -public: - OfflineFootageCommand(const QVector& media) - { - foreach (MediaInput* i, media) { - stream_data_.insert(i, i->stream()); - } - - project_ = media.first()->parent()->project(); - } - - virtual Project* GetRelevantProject() const override - { - return project_; - } - - virtual void redo() override - { - for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) { - it.key()->SetStream(nullptr); - } - } - - virtual void undo() override - { - for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) { - it.key()->SetStream(it.value()); - } - } - -private: - QMap stream_data_; - - Project* project_; - -}; - } #endif // PROJECTEXPLORERUNDO_H diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 68c506c33..9b1023320 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -68,12 +68,12 @@ const int64_t &TimeBasedWidget::GetTimestamp() const return ruler_->GetTime(); } -ViewerOutput *TimeBasedWidget::GetConnectedNode() const +Sequence *TimeBasedWidget::GetConnectedNode() const { return viewer_node_; } -void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node) +void TimeBasedWidget::ConnectViewerNode(Sequence *node) { if (viewer_node_ == node) { return; @@ -244,12 +244,12 @@ void TimeBasedWidget::resizeEvent(QResizeEvent *event) TimelinePoints *TimeBasedWidget::ConnectTimelinePoints() { - return static_cast(viewer_node_->parent()); + return viewer_node_->timeline_points(); } Project *TimeBasedWidget::GetTimelinePointsProject() { - return static_cast(viewer_node_->parent())->project(); + return viewer_node_->project(); } TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const @@ -323,7 +323,10 @@ void TimeBasedWidget::ZoomOut() void TimeBasedWidget::GoToPrevCut() { - if (!GetConnectedNode()) { + // Cuts are only possible in sequences + Sequence* sequence = dynamic_cast(viewer_node_); + + if (!sequence) { return; } @@ -333,7 +336,7 @@ void TimeBasedWidget::GoToPrevCut() int64_t closest_cut = 0; - foreach (Track* track, viewer_node_->GetTracks()) { + foreach (Track* track, sequence->GetTracks()) { int64_t this_track_closest_cut = 0; foreach (Block* block, track->Blocks()) { @@ -354,13 +357,16 @@ void TimeBasedWidget::GoToPrevCut() void TimeBasedWidget::GoToNextCut() { - if (!GetConnectedNode()) { + // Cuts are only possible in sequences + Sequence* sequence = dynamic_cast(viewer_node_); + + if (!sequence) { return; } int64_t closest_cut = INT64_MAX; - foreach (Track* track, GetConnectedNode()->GetTracks()) { + foreach (Track* track, sequence->GetTracks()) { int64_t this_track_closest_cut = Timecode::time_to_timestamp(track->track_length(), timebase()); if (this_track_closest_cut <= GetTimestamp()) { @@ -573,7 +579,7 @@ void TimeBasedWidget::SetMarker() } if (ok) { - Core::instance()->undo_stack()->push(new MarkerAddCommand(static_cast(GetConnectedNode()->parent())->project(), + Core::instance()->undo_stack()->push(new MarkerAddCommand(GetConnectedNode()->project(), points_->markers(), TimeRange(GetTime(), GetTime()), marker_name)); } } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index 083af2f6c..2414d44b1 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -46,9 +46,9 @@ public: void ZoomOut(); - ViewerOutput* GetConnectedNode() const; + Sequence* GetConnectedNode() const; - void ConnectViewerNode(ViewerOutput *node); + void ConnectViewerNode(Sequence *node); void SetScaleAndCenterOnPlayhead(const double& scale); @@ -105,11 +105,11 @@ protected: virtual void ScaleChangedEvent(const double &) override; - virtual void ConnectedNodeChanged(ViewerOutput*){} + virtual void ConnectedNodeChanged(Sequence*){} - virtual void ConnectNodeInternal(ViewerOutput*){} + virtual void ConnectNodeInternal(Sequence*){} - virtual void DisconnectNodeInternal(ViewerOutput*){} + virtual void DisconnectNodeInternal(Sequence*){} void SetAutoMaxScrollBar(bool e); @@ -189,7 +189,7 @@ private: bool UserIsDraggingPlayhead() const; - ViewerOutput* viewer_node_; + Sequence* viewer_node_; TimeRuler* ruler_; diff --git a/app/widget/timelinewidget/timelineundo.h b/app/widget/timelinewidget/timelineundo.h index ad0921c79..73a4a9f54 100644 --- a/app/widget/timelinewidget/timelineundo.h +++ b/app/widget/timelinewidget/timelineundo.h @@ -66,7 +66,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -97,7 +97,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -147,7 +147,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } /** @@ -371,7 +371,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -401,7 +401,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override @@ -434,7 +434,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override @@ -463,7 +463,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -500,7 +500,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } /** @@ -652,7 +652,7 @@ public: virtual Project* GetRelevantProject() const override { - return blocks_.first()->parent()->project(); + return blocks_.first()->project(); } virtual void redo() override @@ -749,7 +749,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override @@ -815,7 +815,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } /** @@ -1024,7 +1024,7 @@ public: virtual Project* GetRelevantProject() const override { - return static_cast(list_->parent())->parent()->project(); + return list_->parent()->project(); } virtual void redo() override @@ -1049,9 +1049,9 @@ public: // We can optimize here by simply shifting the whole cache forward instead of re-caching // everything following this time if (list_->type() == Track::kVideo) { - static_cast(list_->parent())->ShiftVideoCache(out_, in_); + list_->parent()->ShiftVideoCache(out_, in_); } else if (list_->type() == Track::kAudio) { - static_cast(list_->parent())->ShiftAudioCache(out_, in_); + list_->parent()->ShiftAudioCache(out_, in_); } foreach (Track* track, working_tracks_) { @@ -1076,9 +1076,9 @@ public: // We can optimize here by simply shifting the whole cache forward instead of re-caching // everything following this time if (list_->type() == Track::kVideo) { - static_cast(list_->parent())->ShiftVideoCache(in_, out_); + list_->parent()->ShiftVideoCache(in_, out_); } else if (list_->type() == Track::kAudio) { - static_cast(list_->parent())->ShiftAudioCache(in_, out_); + list_->parent()->ShiftAudioCache(in_, out_); } foreach (Track* track, working_tracks_) { @@ -1114,7 +1114,7 @@ private: class TimelineRippleRemoveAreaCommand : public MultiUndoCommand { public: - TimelineRippleRemoveAreaCommand(ViewerOutput* timeline, rational in, rational out) : + TimelineRippleRemoveAreaCommand(Sequence* timeline, rational in, rational out) : timeline_(timeline) { for (int i=0; iparent()->project(); + return timeline_->project(); } private: - ViewerOutput* timeline_; + Sequence* timeline_; }; @@ -1155,7 +1155,7 @@ public: virtual Project* GetRelevantProject() const override { - return static_cast(track_list_->parent())->parent()->project(); + return track_list_->parent()->project(); } virtual void redo() override @@ -1325,9 +1325,9 @@ private: if (all_tracks_unlocked_) { // We rippled all the tracks, so we can shift the whole cache if (track_list_->type() == Track::kVideo) { - static_cast(track_list_->parent())->ShiftVideoCache(pre_latest_out, post_latest_out); + track_list_->parent()->ShiftVideoCache(pre_latest_out, post_latest_out); } else if (track_list_->type() == Track::kAudio) { - static_cast(track_list_->parent())->ShiftAudioCache(pre_latest_out, post_latest_out); + track_list_->parent()->ShiftAudioCache(pre_latest_out, post_latest_out); } } } @@ -1383,7 +1383,7 @@ public: virtual Project* GetRelevantProject() const override { - return timeline_->GetParentGraph()->project(); + return timeline_->parent()->project(); } virtual void redo() override @@ -1419,9 +1419,9 @@ public: QString relevant_input; if (timeline_->type() == Track::kVideo) { - relevant_input = ViewerOutput::kTextureInput; + relevant_input = Sequence::kTextureInput; } else { - relevant_input = ViewerOutput::kSamplesInput; + relevant_input = Sequence::kSamplesInput; } if (!timeline_->parent()->IsInputConnected(relevant_input)) { @@ -1505,7 +1505,7 @@ public: virtual Project* GetRelevantProject() const override { - return timeline_->GetParentGraph()->project(); + return timeline_->parent()->project(); } virtual void redo() override @@ -1604,7 +1604,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override @@ -1637,7 +1637,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -1784,7 +1784,7 @@ private: class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand { public: - TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput* vo, const TimeRangeList& regions) : + TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const TimeRangeList& regions) : timeline_(vo), regions_(regions) { @@ -1797,7 +1797,7 @@ public: virtual Project* GetRelevantProject() const override { - return timeline_->parent()->project(); + return timeline_->project(); } virtual void redo() override @@ -1851,7 +1851,7 @@ public: } private: - ViewerOutput* timeline_; + Sequence* timeline_; TimeRangeList regions_; QVector commands_; @@ -1941,7 +1941,7 @@ public: virtual Project* GetRelevantProject() const override { - return block_->parent()->project(); + return block_->project(); } virtual void redo() override @@ -1986,7 +1986,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override @@ -2153,7 +2153,7 @@ public: virtual Project* GetRelevantProject() const override { - return static_cast(track_list_->parent())->parent()->project(); + return track_list_->parent()->project(); } virtual void redo() override @@ -2166,9 +2166,9 @@ public: if (all_tracks_unlocked_) { // Optimize by shifting over since we have a constant amount of time being inserted if (track_list_->type() == Track::kVideo) { - static_cast(track_list_->parent())->ShiftVideoCache(point_, point_ + length_); + track_list_->parent()->ShiftVideoCache(point_, point_ + length_); } else if (track_list_->type() == Track::kAudio) { - static_cast(track_list_->parent())->ShiftAudioCache(point_, point_ + length_); + track_list_->parent()->ShiftAudioCache(point_, point_ + length_); } } @@ -2205,9 +2205,9 @@ public: if (all_tracks_unlocked_) { // Optimize by shifting over since we have a constant amount of time being inserted if (track_list_->type() == Track::kVideo) { - static_cast(track_list_->parent())->ShiftVideoCache(point_ + length_, point_); + track_list_->parent()->ShiftVideoCache(point_ + length_, point_); } else if (track_list_->type() == Track::kAudio) { - static_cast(track_list_->parent())->ShiftAudioCache(point_ + length_, point_); + track_list_->parent()->ShiftAudioCache(point_ + length_, point_); } } @@ -2326,7 +2326,7 @@ public: virtual Project* GetRelevantProject() const override { - return track_->parent()->project(); + return track_->project(); } virtual void redo() override diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index a90c46ce3..df65828d5 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -218,7 +218,7 @@ void TimelineWidget::ScaleChangedEvent(const double &scale) } } -void TimelineWidget::ConnectNodeInternal(ViewerOutput *n) +void TimelineWidget::ConnectNodeInternal(Sequence *n) { connect(n, &ViewerOutput::TrackAdded, this, &TimelineWidget::AddTrack); connect(n, &ViewerOutput::TrackRemoved, this, &TimelineWidget::RemoveTrack); @@ -229,23 +229,24 @@ void TimelineWidget::ConnectNodeInternal(ViewerOutput *n) SetTimebase(n->video_params().time_base()); for (int i=0;i(n); Track::Type track_type = static_cast(i); TimelineView* view = views_.at(i)->view(); - TrackList* track_list = n->track_list(track_type); + TrackList* track_list = s->track_list(track_type); TrackView* track_view = views_.at(i)->track_view(); track_view->ConnectTrackList(track_list); view->ConnectTrackList(track_list); // Defer to the track to make all the block UI items necessary - const QVector tracks = n->track_list(track_type)->GetTracks(); + const QVector tracks = s->track_list(track_type)->GetTracks(); foreach (Track* track, tracks) { AddTrack(track); } } } -void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n) +void TimelineWidget::DisconnectNodeInternal(Sequence *n) { disconnect(n, &ViewerOutput::TrackAdded, this, &TimelineWidget::AddTrack); disconnect(n, &ViewerOutput::TrackRemoved, this, &TimelineWidget::RemoveTrack); @@ -253,7 +254,8 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n) DeselectAll(); - foreach (Track* track, n->GetTracks()) { + Sequence* s = static_cast(n); + foreach (Track* track, s->GetTracks()) { RemoveTrack(track); } @@ -383,7 +385,7 @@ void TimelineWidget::SplitAtPlayhead() bool some_blocks_are_selected = false; // Get all blocks at the playhead - foreach (Track* track, GetConnectedNode()->GetTracks()) { + foreach (Track* track, sequence()->GetTracks()) { Block* b = track->BlockContainingTime(playhead_time); if (b && b->type() == Block::kClip) { @@ -488,7 +490,7 @@ void TimelineWidget::DeleteSelected(bool ripple) range_list.insert(TimeRange(b->in(), b->out())); } - command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(GetConnectedNode(), range_list)); + command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list)); } Core::instance()->undo_stack()->pushIfHasChildren(command); @@ -501,7 +503,7 @@ void TimelineWidget::IncreaseTrackHeight() } // Increase the height of each track by one "unit" - foreach (Track* t, GetConnectedNode()->GetTracks()) { + foreach (Track* t, sequence()->GetTracks()) { t->SetTrackHeight(t->GetTrackHeight() + Track::kTrackHeightInterval); } } @@ -513,7 +515,7 @@ void TimelineWidget::DecreaseTrackHeight() } // Decrease the height of each track by one "unit" - foreach (Track* t, GetConnectedNode()->GetTracks()) { + foreach (Track* t, sequence()->GetTracks()) { t->SetTrackHeight(qMax(t->GetTrackHeight() - Track::kTrackHeightInterval, Track::kTrackHeightMinimum)); } } @@ -614,7 +616,7 @@ void TimelineWidget::Paste(bool insert) foreach (const BlockPasteData& bpd, paste_data) { qDebug() << "Placing" << bpd.block; - command->add_child(new TrackPlaceBlockCommand(GetConnectedNode()->track_list(bpd.track_type), + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(bpd.track_type), bpd.track_index, bpd.block, paste_start + bpd.in)); @@ -635,12 +637,12 @@ void TimelineWidget::DeleteInToOut(bool ripple) if (ripple) { - command->add_child(new TimelineRippleRemoveAreaCommand(GetConnectedNode(), + command->add_child(new TimelineRippleRemoveAreaCommand(sequence(), GetConnectedTimelinePoints()->workarea()->in(), GetConnectedTimelinePoints()->workarea()->out())); } else { - QVector unlocked_tracks = GetConnectedNode()->GetUnlockedTracks(); + QVector unlocked_tracks = sequence()->GetUnlockedTracks(); foreach (Track* track, unlocked_tracks) { GapBlock* gap = new GapBlock(); @@ -650,7 +652,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) command->add_child(new NodeAddCommand(static_cast(track->parent()), gap)); - command->add_child(new TrackPlaceBlockCommand(GetConnectedNode()->track_list(track->type()), + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track->type()), track->Index(), gap, GetConnectedTimelinePoints()->workarea()->in())); @@ -698,7 +700,7 @@ void TimelineWidget::SetColorLabel(int index) void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command) { for (int i=0;iadd_child(new TrackListInsertGaps(GetConnectedNode()->track_list(static_cast(i)), + command->add_child(new TrackListInsertGaps(sequence()->track_list(static_cast(i)), earliest_point, insert_length)); } @@ -706,7 +708,7 @@ void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational Track *TimelineWidget::GetTrackFromReference(const Track::Reference &ref) const { - return GetConnectedNode()->track_list(ref.type())->GetTrackAt(ref.index()); + return sequence()->track_list(ref.type())->GetTrackAt(ref.index()); } int TimelineWidget::GetTrackY(const Track::Reference &ref) @@ -970,7 +972,7 @@ void TimelineWidget::ShowSequenceDialog() return; } - SequenceDialog sd(static_cast(GetConnectedNode()->parent()), SequenceDialog::kExisting, this); + SequenceDialog sd(sequence(), SequenceDialog::kExisting, this); sd.exec(); } @@ -1127,7 +1129,7 @@ QVector TimelineWidget::GetEditToInfo(const rational& play Timeline::MovementMode mode) { // Get list of unlocked tracks - QVector tracks = GetConnectedNode()->GetUnlockedTracks(); + QVector tracks = sequence()->GetUnlockedTracks(); // Create list to cache nearest times and the blocks at this point QVector info_list(tracks.size()); @@ -1206,7 +1208,7 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode) rational in_ripple = qMin(closest_point_to_playhead, playhead_time); rational out_ripple = qMax(closest_point_to_playhead, playhead_time); - TimelineRippleRemoveAreaCommand* c = new TimelineRippleRemoveAreaCommand(GetConnectedNode(), + TimelineRippleRemoveAreaCommand* c = new TimelineRippleRemoveAreaCommand(sequence(), in_ripple, out_ripple); @@ -1290,7 +1292,7 @@ QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q mapped_rect = mapped_rect.normalized(); // Get tracks - TrackList* track_list = GetConnectedNode()->track_list(static_cast(i)); + TrackList* track_list = sequence()->track_list(static_cast(i)); for (int j=0; jGetTrackCount(); j++) { int track_top = view->GetTrackY(j); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 8a1b51fb7..bc4990923 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -91,6 +91,14 @@ public: void SetColorLabel(int index); + /** + * @brief Timelines should always be connected to sequences + */ + Sequence* sequence() const + { + return static_cast(GetConnectedNode()); + } + const QVector& GetSelectedBlocks() const { return selected_blocks_; @@ -237,8 +245,8 @@ protected: virtual void TimeChangedEvent(const int64_t &) override; virtual void ScaleChangedEvent(const double &) override; - virtual void ConnectNodeInternal(ViewerOutput* n) override; - virtual void DisconnectNodeInternal(ViewerOutput* n) override; + virtual void ConnectNodeInternal(Sequence* n) override; + virtual void DisconnectNodeInternal(Sequence* n) override; virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata) override; virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override; diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index 44776322e..53463e8e6 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -102,7 +102,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeAddCommand(graph, clip)); - command->add_child(new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track.type()), + command->add_child(new TrackPlaceBlockCommand(static_cast(parent()->GetConnectedNode())->track_list(track.type()), track.index(), clip, ghost_->GetAdjustedIn())); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index a3783568c..ed4fb1f81 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -32,7 +32,6 @@ #include "node/audio/volume/volume.h" #include "node/distort/transform/transformdistortnode.h" #include "node/generator/matrix/matrix.h" -#include "node/input/media/media.h" #include "node/math/math/math.h" #include "project/item/sequence/sequence.h" #include "widget/nodeview/nodeviewundo.h" @@ -97,7 +96,7 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event) Item* item = reinterpret_cast(item_ptr); // Check if Item is Footage - if (item->type() == Item::kFootage) { + if (dynamic_cast(item)) { Footage* f = static_cast(item); @@ -225,8 +224,10 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QVectorstreams()) { - Track::Type track_type = TrackTypeFromStreamType(stream->type()); + foreach (const QString& output, footage.footage()->outputs()) { + Footage::StreamReference ref = footage.footage()->GetReferenceFromOutput(output); + + Track::Type track_type = TrackTypeFromStreamType(ref.type()); quint64 cached_enabled_streams = enabled_streams; enabled_streams >>= 1; @@ -239,8 +240,10 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QVectortype() == Stream::kVideo - && static_cast(stream)->video_type() == VideoStream::kVideoTypeStill) { + Stream stream = ref.GetStream(); + + if (ref.type() == Stream::kVideo + && stream.video_type() == Stream::kVideoTypeStill) { // Stream is essentially length-less - we may use the default still image length in config, // or we may use another stream's length depending on the circumstance contains_image_stream = true; @@ -251,7 +254,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QVectorworkarea()->range().length()); ghost->SetMediaIn(footage.footage()->workarea()->in()); } else { - int64_t stream_duration = Timecode::rescale_timestamp_ceil(stream->duration(), stream->timebase(), dest_tb); + int64_t stream_duration = Timecode::rescale_timestamp_ceil(stream.duration(), stream.timebase(), dest_tb); footage_duration = qMax(footage_duration, Timecode::timestamp_to_time(stream_duration, dest_tb)); } } @@ -261,7 +264,7 @@ void ImportTool::FootageToGhosts(rational ghost_start, const QVectorSetData(TimelineViewGhostItem::kAttachedFootage, Node::PtrToValue(stream)); + ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(TimelineViewGhostItem::AttachedFootage({footage.footage(), output}))); ghost->SetMode(Timeline::kMove); footage_ghosts.append(ghost); @@ -310,12 +313,12 @@ void ImportTool::DropGhosts(bool insert) MultiUndoCommand* command = new MultiUndoCommand(); NodeGraph* dst_graph = nullptr; - ViewerOutput* viewer_node = nullptr; - Sequence* open_sequence = nullptr; + Sequence* sequence = nullptr; + bool open_sequence = false; if (parent()->GetConnectedNode()) { - viewer_node = parent()->GetConnectedNode(); - dst_graph = static_cast(parent()->GetConnectedNode()->parent()); + sequence = parent()->GetConnectedNode(); + dst_graph = parent()->GetConnectedNode()->parent(); } else { // There's no active timeline here, ask the user what to do @@ -382,19 +385,18 @@ void ImportTool::DropGhosts(bool insert) } if (sequence_is_valid) { - new_sequence->add_default_nodes(); + dst_graph = Core::instance()->GetActiveProject(); - command->add_child(new ProjectViewModel::AddItemCommand(Core::instance()->GetActiveProjectModel(), - Core::instance()->GetSelectedFolderInActiveProject(), - new_sequence)); + command->add_child(new NodeAddCommand(dst_graph, new_sequence)); + command->add_child(new NodeEdgeAddCommand(Core::instance()->GetSelectedFolderInActiveProject(), NodeInput(new_sequence, Item::kParentInput))); + new_sequence->add_default_nodes(command); FootageToGhosts(0, dragged_footage_, new_sequence->video_params().time_base(), 0); - dst_graph = new_sequence; - viewer_node = new_sequence->viewer_output(); + sequence = new_sequence; // Set this as the sequence to open - open_sequence = new_sequence; + open_sequence = true; } else { // If the sequence is valid, ownership is passed to AddItemCommand. // Otherwise, we're responsible for deleting it. @@ -416,38 +418,32 @@ void ImportTool::DropGhosts(bool insert) for (int i=0;iGetGhostItems().size();i++) { TimelineViewGhostItem* ghost = parent()->GetGhostItems().at(i); - Stream* footage_stream = Node::ValueToPtr(ghost->GetData(TimelineViewGhostItem::kAttachedFootage)); + TimelineViewGhostItem::AttachedFootage footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value(); + + NodeOutput corresponding_output(footage_stream.footage, footage_stream.output); ClipBlock* clip = new ClipBlock(); clip->set_media_in(ghost->GetMediaIn()); clip->set_length_and_media_out(ghost->GetLength()); - clip->SetLabel(footage_stream->footage()->name()); + clip->SetLabel(footage_stream.footage->GetLabel()); command->add_child(new NodeAddCommand(dst_graph, clip)); - switch (footage_stream->type()) { + switch (footage_stream.footage->GetTypeFromOutput(footage_stream.output)) { case Stream::kVideo: { - MediaInput* video_input = new MediaInput(); - video_input->SetStream(footage_stream); - command->add_child(new NodeAddCommand(dst_graph, video_input)); - TransformDistortNode* transform = new TransformDistortNode(); command->add_child(new NodeAddCommand(dst_graph, transform)); - command->add_child(new NodeEdgeAddCommand(video_input, NodeInput(transform, TransformDistortNode::kTextureInput))); + command->add_child(new NodeEdgeAddCommand(corresponding_output, NodeInput(transform, TransformDistortNode::kTextureInput))); command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn))); break; } case Stream::kAudio: { - MediaInput* audio_input = new MediaInput(); - audio_input->SetStream(footage_stream); - command->add_child(new NodeAddCommand(dst_graph, audio_input)); - VolumeNode* volume_node = new VolumeNode(); command->add_child(new NodeAddCommand(dst_graph, volume_node)); - command->add_child(new NodeEdgeAddCommand(audio_input, NodeInput(volume_node, VolumeNode::kSamplesInput))); + command->add_child(new NodeEdgeAddCommand(corresponding_output, NodeInput(volume_node, VolumeNode::kSamplesInput))); command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn))); break; } @@ -455,7 +451,7 @@ void ImportTool::DropGhosts(bool insert) break; } - command->add_child(new TrackPlaceBlockCommand(viewer_node->track_list(ghost->GetAdjustedTrack().type()), + command->add_child(new TrackPlaceBlockCommand(sequence->track_list(ghost->GetAdjustedTrack().type()), ghost->GetAdjustedTrack().index(), clip, ghost->GetAdjustedIn())); @@ -464,9 +460,9 @@ void ImportTool::DropGhosts(bool insert) // Link any clips so far that share the same Footage with this one for (int j=0;j(parent()->GetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage)); + TimelineViewGhostItem::AttachedFootage footage_compare = parent()->GetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value(); - if (footage_compare->footage() == footage_stream->footage()) { + if (footage_compare.footage == footage_stream.footage) { Block::Link(block_items.at(j), clip); } } @@ -474,7 +470,7 @@ void ImportTool::DropGhosts(bool insert) } if (open_sequence) { - command->add_child(new OpenSequenceCommand(open_sequence)); + command->add_child(new OpenSequenceCommand(sequence)); } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 0d189c92e..5a131e0c9 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -644,7 +644,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) } const Track::Reference& track_ref = p.ghost->GetAdjustedTrack(); - command->add_child(new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()), + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track_ref.type()), track_ref.index(), block, p.ghost->GetAdjustedIn())); diff --git a/app/widget/timelinewidget/tool/ripple.cpp b/app/widget/timelinewidget/tool/ripple.cpp index 61360c0a2..805535f46 100644 --- a/app/widget/timelinewidget/tool/ripple.cpp +++ b/app/widget/timelinewidget/tool/ripple.cpp @@ -58,7 +58,7 @@ void RippleTool::InitiateDrag(Block *clicked_item, } // For each track that does NOT have a ghost, we need to make one for Gaps - foreach (Track* track, parent()->GetConnectedNode()->GetTracks()) { + foreach (Track* track, sequence()->GetTracks()) { if (track->IsLocked()) { continue; } @@ -144,7 +144,7 @@ void RippleTool::FinishDrag(TimelineViewMouseEvent *event) for (int i=0;iadd_child(new TrackListRippleToolCommand(parent()->GetConnectedNode()->track_list(static_cast(i)), + command->add_child(new TrackListRippleToolCommand(sequence()->track_list(static_cast(i)), info_list.at(i), movement, drag_movement_mode())); diff --git a/app/widget/timelinewidget/tool/tool.cpp b/app/widget/timelinewidget/tool/tool.cpp index 0550122e2..7b52a450c 100644 --- a/app/widget/timelinewidget/tool/tool.cpp +++ b/app/widget/timelinewidget/tool/tool.cpp @@ -39,6 +39,11 @@ TimelineWidget *TimelineTool::parent() return parent_; } +Sequence *TimelineTool::sequence() +{ + return parent_->sequence(); +} + Timeline::MovementMode TimelineTool::FlipTrimMode(const Timeline::MovementMode &trim_mode) { if (trim_mode == Timeline::kTrimIn) { diff --git a/app/widget/timelinewidget/tool/tool.h b/app/widget/timelinewidget/tool/tool.h index ac69eefdc..67ffc9a18 100644 --- a/app/widget/timelinewidget/tool/tool.h +++ b/app/widget/timelinewidget/tool/tool.h @@ -53,6 +53,8 @@ public: TimelineWidget* parent(); + Sequence* sequence(); + static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode); static rational SnapMovementToTimebase(const rational& start, rational movement, const rational& timebase); diff --git a/app/widget/timelinewidget/tool/transition.cpp b/app/widget/timelinewidget/tool/transition.cpp index 0c8bb52bf..6769d80f4 100644 --- a/app/widget/timelinewidget/tool/transition.cpp +++ b/app/widget/timelinewidget/tool/transition.cpp @@ -126,7 +126,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event) command->add_child(new NodeAddCommand(static_cast(parent()->GetConnectedNode()->parent()), transition)); - command->add_child(new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track.type()), + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), track.index(), transition, ghost_->GetAdjustedIn())); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 44a083c5d..f701ca7ee 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -31,7 +31,6 @@ #include "common/flipmodifiers.h" #include "common/qtutils.h" #include "common/timecodefunctions.h" -#include "node/input/media/media.h" #include "project/item/footage/footage.h" #include "ui/colorcoding.h" diff --git a/app/widget/timelinewidget/view/timelineviewghostitem.h b/app/widget/timelinewidget/view/timelineviewghostitem.h index c7a77de4d..e65f03464 100644 --- a/app/widget/timelinewidget/view/timelineviewghostitem.h +++ b/app/widget/timelinewidget/view/timelineviewghostitem.h @@ -23,6 +23,7 @@ #include +#include "node/output/track/track.h" #include "project/item/footage/footage.h" #include "timeline/timelinecommon.h" @@ -42,6 +43,11 @@ public: kTrimShouldBeIgnored }; + struct AttachedFootage { + Footage* footage; + QString output; + }; + TimelineViewGhostItem() : track_adj_(0), mode_(Timeline::kNone), @@ -267,4 +273,6 @@ private: } +Q_DECLARE_METATYPE(olive::TimelineViewGhostItem::AttachedFootage) + #endif // TIMELINEVIEWGHOSTITEM_H diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index cea4a66e4..78f7a2869 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -32,12 +32,6 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) : ViewerWidget(parent), footage_(nullptr) { - video_node_ = new MediaInput(); - video_node_->setParent(&sequence_); - - audio_node_ = new MediaInput(); - audio_node_->setParent(&sequence_); - connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag); controls_->SetAudioVideoDragButtonsVisible(true); @@ -57,56 +51,25 @@ void FootageViewerWidget::SetFootage(Footage *footage) ConnectViewerNode(nullptr); - video_node_->SetStream(nullptr); - audio_node_->SetStream(nullptr); - - Node::DisconnectEdge(video_node_, NodeInput(sequence_.viewer_output(), ViewerOutput::kTextureInput)); - Node::DisconnectEdge(audio_node_, NodeInput(sequence_.viewer_output(), ViewerOutput::kSamplesInput)); + Node::DisconnectEdge(sequence_.GetConnectedOutput(ViewerOutput::kTextureInput), NodeInput(&sequence_, ViewerOutput::kTextureInput)); + Node::DisconnectEdge(sequence_.GetConnectedOutput(ViewerOutput::kSamplesInput), NodeInput(&sequence_, ViewerOutput::kSamplesInput)); } footage_ = footage; if (footage_) { // Update sequence media name - sequence_.viewer_output()->SetLabel(footage_->name()); + sequence_.SetLabel(footage_->GetLabel()); // Reset parameters and then attempt to set from footage sequence_.set_default_parameters(); sequence_.set_parameters_from_footage({footage_}); - // Use first of each stream - VideoStream* video_stream = nullptr; - AudioStream* audio_stream = nullptr; + // Try to connect video stream + TryConnectingType(footage, Stream::kVideo); + TryConnectingType(footage, Stream::kAudio); - foreach (Stream* s, footage_->streams()) { - if (!s->enabled()) { - continue; - } - - if (!audio_stream && s->type() == Stream::kAudio) { - audio_stream = static_cast(s); - } - - if (!video_stream && s->type() == Stream::kVideo) { - video_stream = static_cast(s); - } - - if (audio_stream && video_stream) { - break; - } - } - - if (video_stream) { - video_node_->SetStream(video_stream); - Node::ConnectEdge(video_node_, NodeInput(sequence_.viewer_output(), ViewerOutput::kTextureInput)); - } - - if (audio_stream) { - audio_node_->SetStream(audio_stream); - Node::ConnectEdge(audio_node_, NodeInput(sequence_.viewer_output(), ViewerOutput::kSamplesInput)); - } - - ConnectViewerNode(sequence_.viewer_output(), footage_->project()->color_manager()); + ConnectViewerNode(&sequence_, footage_->project()->color_manager()); SetTimestamp(cached_timestamps_.value(footage_, 0)); } else { @@ -134,7 +97,7 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl QMimeData* mimedata = new QMimeData(); QByteArray encoded_data; - QDataStream stream(&encoded_data, QIODevice::WriteOnly); + QDataStream data_stream(&encoded_data, QIODevice::WriteOnly); quint64 enabled_stream_flags = GetFootage()->get_enabled_stream_flags(); @@ -142,9 +105,11 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl if (!enable_video || !enable_audio) { quint64 stream_disabler = 0x1; - foreach (Stream* s, GetFootage()->streams()) { - if ((s->type() == Stream::kVideo && !enable_video) - || (s->type() == Stream::kAudio && !enable_audio)) { + for (int i=0; iGetStreamCount(); i++) { + Stream stream = GetFootage()->GetStreamAt(i); + + if ((stream.type() == Stream::kVideo && !enable_video) + || (stream.type() == Stream::kAudio && !enable_audio)) { enabled_stream_flags &= ~stream_disabler; } @@ -152,7 +117,7 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl } } - stream << enabled_stream_flags << -1 << reinterpret_cast(GetFootage()); + data_stream << enabled_stream_flags << -1 << reinterpret_cast(GetFootage()); mimedata->setData("application/x-oliveprojectitemdata", encoded_data); drag->setMimeData(mimedata); @@ -160,6 +125,32 @@ void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enabl drag->exec(); } +void FootageViewerWidget::TryConnectingType(Footage *footage, Stream::Type type) +{ + for (int i=0; ; i++) { + Stream stream = footage_->GetStreamAt(type, i); + + // Found end of stream list + if (!stream.IsValid()) { + break; + } + + if (stream.enabled()) { + QString s = Footage::GetStringFromReference(type, i); + + QString input_param; + + if (type == Stream::kVideo) { + input_param = ViewerOutput::kTextureInput; + } else { + input_param = ViewerOutput::kSamplesInput; + } + + Node::ConnectEdge(NodeOutput(footage, s), NodeInput(&sequence_, input_param)); + } + } +} + void FootageViewerWidget::StartFootageDrag() { StartFootageDragInternal(true, true); diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index 036a96c1f..00ed0aa53 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -21,7 +21,6 @@ #ifndef FOOTAGEVIEWERWIDGET_H #define FOOTAGEVIEWERWIDGET_H -#include "node/input/media/media.h" #include "node/output/viewer/viewer.h" #include "viewer.h" @@ -44,14 +43,12 @@ protected: private: void StartFootageDragInternal(bool enable_video, bool enable_audio); + void TryConnectingType(Footage* footage, Stream::Type type); + Footage* footage_; Sequence sequence_; - MediaInput* video_node_; - - MediaInput* audio_node_; - QHash cached_timestamps_; private slots: diff --git a/app/widget/viewer/gizmotraverser.cpp b/app/widget/viewer/gizmotraverser.cpp index 4e9f44954..cd8a9b2a2 100644 --- a/app/widget/viewer/gizmotraverser.cpp +++ b/app/widget/viewer/gizmotraverser.cpp @@ -22,14 +22,14 @@ namespace olive { -QVariant GizmoTraverser::ProcessVideoFootage(VideoStream *stream, const rational &input_time) +QVariant GizmoTraverser::ProcessVideoFootage(const Footage::StreamReference &ref, const rational &input_time) { Q_UNUSED(input_time) - VideoStream* image_stream = static_cast(stream); + Stream stream = ref.GetStream(); - return QVector2D(image_stream->width() * image_stream->pixel_aspect_ratio().toDouble(), - image_stream->height()); + return QVector2D(stream.width() * stream.pixel_aspect_ratio().toDouble(), + stream.height()); } QVariant GizmoTraverser::ProcessShader(const Node *node, const TimeRange &range, const ShaderJob &job) diff --git a/app/widget/viewer/gizmotraverser.h b/app/widget/viewer/gizmotraverser.h index 7fe8ec29d..5330a74e1 100644 --- a/app/widget/viewer/gizmotraverser.h +++ b/app/widget/viewer/gizmotraverser.h @@ -34,7 +34,7 @@ public: } protected: - virtual QVariant ProcessVideoFootage(VideoStream* stream, const rational &input_time) override; + virtual QVariant ProcessVideoFootage(const Footage::StreamReference& stream, const rational &input_time) override; virtual QVariant ProcessShader(const Node *node, const TimeRange &range, const ShaderJob& job) override; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 4d20618fc..48b4b88cb 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -168,7 +168,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) last_time_ = i; } -void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) +void ViewerWidget::ConnectNodeInternal(Sequence *n) { connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SetViewerResolution); connect(n, &ViewerOutput::PixelAspectChanged, this, &ViewerWidget::SetViewerPixelAspect); @@ -193,7 +193,7 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) if (override_color_manager_) { using_manager = override_color_manager_; } else if (n->parent()) { - using_manager = static_cast(n->parent())->project()->color_manager(); + using_manager = n->project()->color_manager(); } else { qWarning() << "Failed to find a suitable color manager for the connected viewer node"; using_manager = nullptr; @@ -220,7 +220,7 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n) ForceUpdate(); } -void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) +void ViewerWidget::DisconnectNodeInternal(Sequence *n) { PauseInternal(); @@ -252,7 +252,7 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n) QMetaObject::invokeMethod(this, "UpdateStack", Qt::QueuedConnection); } -void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n) +void ViewerWidget::ConnectedNodeChanged(Sequence *n) { auto_cacher_.SetViewerNode(n); } @@ -285,7 +285,7 @@ bool ViewerWidget::IsPlaying() const return playback_speed_ != 0; } -void ViewerWidget::ConnectViewerNode(ViewerOutput *node, ColorManager* color_manager) +void ViewerWidget::ConnectViewerNode(Sequence *node, ColorManager* color_manager) { override_color_manager_ = color_manager; diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 7f24c46fa..0128ff126 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -63,7 +63,7 @@ public: bool IsPlaying() const; - void ConnectViewerNode(ViewerOutput* node, ColorManager *color_manager = nullptr); + void ConnectViewerNode(Sequence *node, ColorManager *color_manager = nullptr); /** * @brief Enable or disable the color management menu @@ -153,9 +153,9 @@ protected: virtual void TimebaseChangedEvent(const rational &) override; virtual void TimeChangedEvent(const int64_t &) override; - virtual void ConnectNodeInternal(ViewerOutput *) override; - virtual void DisconnectNodeInternal(ViewerOutput *) override; - virtual void ConnectedNodeChanged(ViewerOutput*n) override; + virtual void ConnectNodeInternal(Sequence *) override; + virtual void DisconnectNodeInternal(Sequence *) override; + virtual void ConnectedNodeChanged(Sequence*n) override; virtual void ScaleChangedEvent(const double& s) override; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index d99c46fbd..f2acf1c12 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -156,7 +156,7 @@ MainWindowLayoutInfo MainWindow::SaveLayout() const foreach (TimelinePanel* panel, timeline_panels_) { if (panel->GetConnectedViewer()) { - info.add_sequence({static_cast(panel->GetConnectedViewer()->parent()), + info.add_sequence({static_cast(panel->GetConnectedViewer()), panel->SaveSplitterState()}); } } @@ -170,7 +170,7 @@ TimelinePanel* MainWindow::OpenSequence(Sequence *sequence, bool enable_focus) { // See if this sequence is already open, and switch to it if so foreach (TimelinePanel* tl, timeline_panels_) { - if (tl->GetConnectedViewer() == sequence->viewer_output()) { + if (tl->GetConnectedViewer() == sequence) { tl->raise(); return tl; } @@ -186,10 +186,10 @@ TimelinePanel* MainWindow::OpenSequence(Sequence *sequence, bool enable_focus) enable_focus = false; } - panel->ConnectViewerNode(sequence->viewer_output()); + panel->ConnectViewerNode(sequence); if (enable_focus) { - TimelineFocused(sequence->viewer_output()); + TimelineFocused(sequence); } return panel; @@ -202,7 +202,7 @@ void MainWindow::CloseSequence(Sequence *sequence) QList copy = timeline_panels_; foreach (TimelinePanel* tp, copy) { - if (tp->GetConnectedViewer() == sequence->viewer_output()) { + if (tp->GetConnectedViewer() == sequence) { RemoveTimelinePanel(tp); } } @@ -211,7 +211,7 @@ void MainWindow::CloseSequence(Sequence *sequence) bool MainWindow::IsSequenceOpen(Sequence *sequence) const { foreach (TimelinePanel* tp, timeline_panels_) { - if (tp->GetConnectedViewer() == sequence->viewer_output()) { + if (tp->GetConnectedViewer() == sequence) { return true; } } @@ -219,7 +219,7 @@ bool MainWindow::IsSequenceOpen(Sequence *sequence) const return false; } -void MainWindow::FolderOpen(Project* p, Item *i, bool floating) +void MainWindow::FolderOpen(Project* p, Folder *i, bool floating) { ProjectPanel* panel = PanelManager::instance()->CreatePanel(this); @@ -329,29 +329,23 @@ void MainWindow::ProjectOpen(Project *p) void MainWindow::ProjectClose(Project *p) { // Close any open sequences from project - QVector open_sequences = p->get_items_of_type(Item::kSequence); - - foreach (Item* item, open_sequences) { - Sequence* seq = static_cast(item); + QVector open_sequences = p->root()->ListOutputsOfType(); + foreach (Sequence* seq, open_sequences) { if (IsSequenceOpen(seq)) { CloseSequence(seq); } } // Close any open footage in footage viewer - QVector footage = p->get_items_of_type(Item::kFootage); + QVector footage_in_project = p->root()->ListOutputsOfType(); QVector footage_in_viewer = footage_viewer_panel_->GetSelectedFootage(); if (!footage_in_viewer.isEmpty()) { - // FootageViewer only has the one footage item - Footage* f = footage_in_viewer.first(); - - foreach (Item* i, footage) { - if (f == i) { - footage_viewer_panel_->SetFootage(nullptr); - break; - } + // FootageViewer only has the one footage item, check if it's in the project in which case + // we'll close it + if (footage_in_project.contains(footage_in_viewer.first())) { + footage_viewer_panel_->SetFootage(nullptr); } } @@ -547,19 +541,12 @@ void MainWindow::RemoveProjectPanel(ProjectPanel *panel) } } -void MainWindow::TimelineFocused(ViewerOutput* viewer) +void MainWindow::TimelineFocused(Sequence* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); param_panel_->ConnectViewerNode(viewer); curve_panel_->ConnectViewerNode(viewer); - - Sequence* seq = nullptr; - - if (viewer) { - seq = static_cast(viewer->parent()); - } - - node_panel_->SetGraph(seq); + node_panel_->SetGraph(viewer ? viewer->parent() : nullptr); } void MainWindow::FocusedPanelChanged(PanelWidget *panel) diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 3b543880b..240216d1e 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -67,7 +67,7 @@ public: bool IsSequenceOpen(Sequence* sequence) const; - void FolderOpen(Project* p, Item* i, bool floating); + void FolderOpen(Project* p, Folder *i, bool floating); ScopePanel* AppendScopePanel(); @@ -130,7 +130,7 @@ private: void RemoveProjectPanel(ProjectPanel* panel); - void TimelineFocused(ViewerOutput *viewer); + void TimelineFocused(Sequence *viewer); QByteArray premaximized_state_;