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_;