implement entire project in nodes

Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly.
This commit is contained in:
itsmattkc
2021-02-15 21:31:57 +11:00
parent 155470bec1
commit d7c5ac4b44
174 changed files with 3515 additions and 4658 deletions
+40 -84
View File
@@ -22,7 +22,6 @@
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#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::CurrentlyConforming> 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 &divider)
{
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 &divider)
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 &params, const QAtomicInt *cancelled)
SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams &params, 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<DecoderPtr> ReceiveListOfAllDecoders()
QVector<DecoderPtr> Decoder::ReceiveListOfAllDecoders()
{
QVector<DecoderPtr> decoders;
@@ -203,55 +195,6 @@ QVector<DecoderPtr> 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<DecoderPtr> decoder_list = ReceiveListOfAllDecoders();
// Pass Footage through each Decoder's probe function
for (int i=0;i<decoder_list.size();i++) {
if (cancelled && *cancelled) {
return nullptr;
}
DecoderPtr decoder = decoder_list.at(i);
Footage* footage = decoder->Probe(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 &params)
QString Decoder::GetConformedFilename(const QString& cache_path, const AudioParams &params)
{
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 &params)
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<double>(ts) / static_cast<double>(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<double>(ts) / static_cast<double>(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);
}
}
+71 -31
View File
@@ -25,6 +25,7 @@ extern "C" {
#include <libswresample/swresample.h>
}
#include <QFileInfo>
#include <QMutex>
#include <QObject>
#include <QWaitCondition>
@@ -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<DecoderPtr> 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 &params, 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 &params);
QString GetIndexFilename();
QString GetConformedFilename(const QString &cache_path, const AudioParams &params);
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<CurrentlyConforming> 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)
+105 -141
View File
@@ -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<AVPixelFormat>(s->codecpar->format));
@@ -92,20 +92,18 @@ bool FFmpegDecoder::OpenInternal()
return false;
}
FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &divider)
/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int &divider)
{
// This is a still image
VideoStream* is = static_cast<VideoStream*>(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<VideoStream*>(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 &divider)
{
VideoStream* vs = static_cast<VideoStream*>(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<Stream*> streams(fmt_ctx->nb_streams);
// Dump it into the Footage object
for (unsigned int i=0;i<fmt_ctx->nb_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<AVPixelFormat>(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<uint64_t>(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<VideoStream*>(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<AVPixelFormat>(instance_.avstream()->codecpar->format),
scaled_width,
scaled_height,
+3 -4
View File
@@ -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();
+10
View File
@@ -0,0 +1,10 @@
#ifndef FOOTAGEMETA_H
#define FOOTAGEMETA_H
struct FootageData {
struct StreamData {
};
};
#endif // FOOTAGEMETA_H
+19 -53
View File
@@ -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<OIIO::TypeDesc::BASETYPE>(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<OIIO::TypeDesc::BASETYPE>(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<VideoStream*>(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<VideoStream*>(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();
+1 -3
View File
@@ -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_;
+1 -1
View File
@@ -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();
+9 -1
View File
@@ -24,7 +24,6 @@
#include <QXmlStreamReader>
#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);
+1
View File
@@ -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());
+32 -24
View File
@@ -351,12 +351,10 @@ void Core::DialogProjectPropertiesShow()
void Core::DialogExportShow()
{
ViewerOutput* viewer = GetSequenceToExport();
Sequence* viewer = GetSequenceToExport();
if (viewer) {
Sequence* sequence = dynamic_cast<Sequence*>(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<Project> p = std::unique_ptr<Project>(plm.GetLoadedProject());
QVector<Item*> 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;i<items.size();i++) {
std::cout << "[" << i << "] " << items.at(i)->name().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<TimeBasedPanel>();
@@ -1064,7 +1071,7 @@ void Core::SetPreferenceForRenderMode(RenderMode::Mode mode, const QString &pref
Config::Current()[GetRenderModePreferencePrefix(mode, preference)] = value;
}
void Core::LabelNodes(const QVector<Node *> &nodes) const
void Core::LabelNodes(const QVector<Node *> &nodes)
{
if (nodes.isEmpty()) {
return;
@@ -1090,9 +1097,13 @@ void Core::LabelNodes(const QVector<Node *> &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<Footage*> project_footage = project->root()->ListOutputsOfType<Footage>();
QVector<Footage*> footage_we_couldnt_validate;
QVector<Item*> project_footage = project->get_items_of_type(Item::kFootage);
foreach (Item* item, project_footage) {
Footage* footage = static_cast<Footage*>(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();
+2 -2
View File
@@ -241,7 +241,7 @@ public:
/**
* @brief Show a dialog to the user to rename a set of nodes
*/
void LabelNodes(const QVector<Node *> &nodes) const;
void LabelNodes(const QVector<Node *> &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
+1 -1
View File
@@ -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)
@@ -14,11 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
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
)
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "configdialogbase.h"
#include <QDialogButtonBox>
#include <QSplitter>
#include <QVBoxLayout>
#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);
}
}
@@ -18,18 +18,41 @@
***/
#include "audiostreamproperties.h"
#ifndef CONFIGBASE_H
#define CONFIGBASE_H
#include <QDialog>
#include <QListWidget>
#include <QStackedWidget>
#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<ConfigDialogBaseTab*> tabs_;
};
}
#endif // CONFIGBASE_H
@@ -18,11 +18,11 @@
***/
#include "preferencestab.h"
#include "configdialogbasetab.h"
namespace olive {
bool PreferencesTab::Validate()
bool ConfigDialogBaseTab::Validate()
{
return true;
}
@@ -24,17 +24,18 @@
#include <QWidget>
#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;
};
}
+25 -29
View File
@@ -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<Sequence*>(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<VideoParams::Format>(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<double>(viewer_node_->video_params().width()) / static_cast<double>(viewer_node_->video_params().height());
video_aspect_ratio_ = static_cast<double>(sequence_->video_params().width()) / static_cast<double>(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<void(ViewerWidget::*)(const ColorTransform&)>(&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<Sequence*>(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<ExportParams::VideoScalingMethod>(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<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue()));
+2 -3
View File
@@ -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_;
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "footageproperties.h"
#include <QGridLayout>
#include <QLabel>
#include <QComboBox>
#include <QLineEdit>
#include <QDialogButtonBox>
#include <QTreeWidgetItem>
#include <QGroupBox>
#include <QListWidget>
#include <QCheckBox>
#include <QSpinBox>
#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;i<footage_->streams().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<VideoStream*>(stream)));
break;
case Stream::kAudio:
stacked_widget_->addWidget(new AudioStreamProperties(static_cast<AudioStream*>(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;i<stacked_widget_->count();i++) {
if (!static_cast<StreamProperties*>(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;i<footage_->streams().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;i<stacked_widget_->count();i++) {
static_cast<StreamProperties*>(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_);
}
}
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef MEDIAPROPERTIESDIALOG_H
#define MEDIAPROPERTIESDIALOG_H
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QDoubleSpinBox>
#include <QLineEdit>
#include <QListWidget>
#include <QStackedWidget>
#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
@@ -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 <http://www.gnu.org/licenses/>.
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
)
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "streamproperties.h"
namespace olive {
StreamProperties::StreamProperties(QWidget *parent) :
QWidget(parent)
{
}
}
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "videostreamproperties.h"
#include <QGridLayout>
#include <QGroupBox>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#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;i<number_of_colorspaces;i++) {
QString colorspace = config->getColorSpaceNameByIndex(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<VideoStream*>(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<VideoParams::Interlacing>(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<VideoParams::Interlacing>(video_interlace_combo_->currentIndex()),
pixel_aspect_combo_->GetPixelAspectRatio()));
}
if (stream_->video_type() == VideoStream::kVideoTypeImageSequence) {
VideoStream* video_stream = static_cast<VideoStream*>(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());
}
}
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef VIDEOSTREAMPROPERTIES_H
#define VIDEOSTREAMPROPERTIES_H
#include <QCheckBox>
#include <QComboBox>
#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
@@ -65,7 +65,7 @@ FootageRelinkDialog::FootageRelinkDialog(const QVector<Footage *> &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")));
+1 -51
View File
@@ -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);
}
}
+3 -18
View File
@@ -28,7 +28,7 @@
#include <QStackedWidget>
#include <QTabWidget>
#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<PreferencesTab*> tabs_;
PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar);
};
@@ -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
)
@@ -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<color_btns_.size(); i++) {
@@ -25,19 +25,19 @@
#include <QLineEdit>
#include <QPushButton>
#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:
/**
@@ -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
@@ -25,17 +25,17 @@
#include <QComboBox>
#include <QPushButton>
#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:
/**
@@ -106,8 +106,10 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
node_group);
}
void PreferencesBehaviorTab::Accept()
void PreferencesBehaviorTab::Accept(MultiUndoCommand *command)
{
Q_UNUSED(command)
QMap<QTreeWidgetItem*, QString>::const_iterator iterator;
for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) {
@@ -23,17 +23,17 @@
#include <QTreeWidget>
#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);
@@ -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());
}
@@ -25,14 +25,14 @@
#include <QLineEdit>
#include <QPushButton>
#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_;
@@ -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();
@@ -25,19 +25,19 @@
#include <QComboBox>
#include <QSpinBox>
#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);
@@ -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;i<key_shortcut_fields_.size();i++) {
key_shortcut_fields_.at(i)->set_action_shortcut();
@@ -24,18 +24,18 @@
#include <QMenuBar>
#include <QTreeWidget>
#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:
/**
+7 -8
View File
@@ -21,6 +21,7 @@
#ifndef PRESETMANAGER_H
#define PRESETMANAGER_H
#include <memory>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
@@ -62,8 +63,6 @@ private:
};
using PresetPtr = std::shared_ptr<Preset>;
template <typename T>
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<T>();
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<PresetPtr>& GetPresetData() const
const QVector<Preset*>& GetPresetData() const
{
return custom_preset_data_;
}
private:
QVector<PresetPtr> custom_preset_data_;
QVector<Preset*> custom_preset_data_;
QString preset_name_;
+6 -6
View File
@@ -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_);
}
}
@@ -79,12 +79,19 @@ SequenceDialogPresetTab::SequenceDialogPresetTab(QWidget* parent) :
}
}
SequenceDialogPresetTab::~SequenceDialogPresetTab()
{
qDeleteAll(child_presets_);
}
void SequenceDialogPresetTab::SaveParametersAsPreset(SequencePreset preset)
{
PresetPtr preset_ptr = std::make_shared<SequencePreset>(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<SequencePreset*>(preset_data.get()));
emit PresetChanged(*static_cast<SequencePreset*>(preset_data));
}
}
@@ -36,6 +36,8 @@ class SequenceDialogPresetTab : public QWidget, public PresetManager<SequencePre
public:
SequenceDialogPresetTab(QWidget* parent = nullptr);
virtual ~SequenceDialogPresetTab() override;
public slots:
void SaveParametersAsPreset(SequencePreset preset);
@@ -54,17 +56,19 @@ private:
QTreeWidgetItem* GetSelectedItem();
QTreeWidgetItem* GetSelectedCustomPreset();
void AddStandardItem(QTreeWidgetItem* folder, PresetPtr preset, const QString &description = QString());
void AddStandardItem(QTreeWidgetItem* folder, Preset* preset, const QString &description = QString());
void AddCustomItem(QTreeWidgetItem* folder, PresetPtr preset, int index, const QString& description = QString());
void AddCustomItem(QTreeWidgetItem* folder, Preset* preset, int index, const QString& description = QString());
void AddItemInternal(QTreeWidgetItem* folder, PresetPtr preset, bool is_custom, int index, const QString& description = QString());
void AddItemInternal(QTreeWidgetItem* folder, Preset* preset, bool is_custom, int index, const QString& description = QString());
QTreeWidget* preset_tree_;
QTreeWidgetItem* my_presets_folder_;
QList<PresetPtr> default_preset_data_;
QVector<Preset*> default_preset_data_;
QVector<Preset*> child_presets_;
private slots:
void SelectedItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
+13 -13
View File
@@ -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<SequencePreset>(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
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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);
}
}
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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;
+11 -3
View File
@@ -20,6 +20,8 @@
#include "factory.h"
#include <QCoreApplication>
#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<Node*> 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;
+3 -1
View File
@@ -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
+5 -1
View File
@@ -20,8 +20,12 @@
#include "graph.h"
#include <QChildEvent>
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<Node*>(event->child());
+1 -1
View File
@@ -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:
-1
View File
@@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(media)
add_subdirectory(time)
set(OLIVE_SOURCES
-100
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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<Node::CategoryID> MediaInput::Category() const
{
return {kCategoryInput};
}
Stream *MediaInput::stream() const
{
return Node::ValueToPtr<Stream>(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);
}
}
-82
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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<CategoryID> 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
+2 -2
View File
@@ -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)));
+1 -1
View File
@@ -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;
};
+3 -3
View File
@@ -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);
}
+1 -1
View File
@@ -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_;
+48 -63
View File
@@ -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<NodeGraph*>(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<Project*>(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<NodeOutput, NodeInput>({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<NodeOutput, NodeInput>({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<Stream>(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<VideoStream*>(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<const char*>(&image_stream->pixel_aspect_ratio()), sizeof(rational));
}
// Footage timestamp
if (stream->type() == Stream::kVideo) {
VideoStream* video_stream = static_cast<VideoStream*>(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<const char*>(&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 *> 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();
}
}
+12 -3
View File
@@ -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<QString>& 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:
+5 -2
View File
@@ -36,7 +36,7 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector<Node *> &nodes, vo
QXmlStreamWriter writer(&copy_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<Node *> NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph,
}
QXmlStreamReader reader(clipboard);
uint data_version = reader.documentVersion().toUInt();
QVector<Node*> 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<Node *> 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);
}
+3 -3
View File
@@ -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));
}
}
+2 -2
View File
@@ -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;
+4 -4
View File
@@ -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<NodeGraph*>(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<ViewerOutput*>(QObject::parent());
return static_cast<Sequence*>(QObject::parent());
}
int TrackList::ArraySize() const
+3 -3
View File
@@ -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;
+2 -271
View File
@@ -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<Track::kCount;i++) {
// Create track input
QString track_input_id = kTrackInputFormat.arg(i);
AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
IgnoreInvalidationsFrom(track_input_id);
TrackList* list = new TrackList(this, static_cast<Track::Type>(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<Track *> ViewerOutput::GetUnlockedTracks() const
{
QVector<Track*> tracks = GetTracks();
for (int i=0;i<tracks.size();i++) {
if (tracks.at(i)->IsLocked()) {
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<rational>();
}
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<rational>();
}
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<Track::kCount;i++) {
QString input_name;
switch (static_cast<Track::Type>(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;
}
}
}
}
}
+2 -128
View File
@@ -21,17 +21,7 @@
#ifndef VIEWER_H
#define VIEWER_H
#include <QUuid>
#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<CategoryID> 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<Track *> &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<Track *> 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<TrackList*> track_lists_;
QVector<Track*> track_cache_;
rational last_length_;
FrameHashCache video_frame_cache_;
AudioPlaybackCache audio_playback_cache_;
int operation_stack_;
private slots:
void UpdateTrackCache();
void VerifyLength();
};
}
+14 -22
View File
@@ -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<NodeValue> video_footage_to_retrieve;
QList<NodeValue> audio_footage_to_retrieve;
QList<NodeValue> footage_jobs_to_run;
QList<NodeValue> shader_jobs_to_run;
QList<NodeValue> sample_jobs_to_run;
QList<NodeValue> 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<NodeValue>* take_this_value_list = nullptr;
if (v.type() == NodeValue::kFootage) {
Stream* s = Node::ValueToPtr<Stream>(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<VideoStream>(v.data());
Footage::StreamReference job = v.data().value<Footage::StreamReference>();
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<AudioStream>(v.data());
Footage::StreamReference job = v.data().value<Footage::StreamReference>();
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);
+2 -3
View File
@@ -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);
+16 -5
View File
@@ -27,6 +27,7 @@
#include <QVector4D>
#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<rational>().toString();
} else if (data_type == kFootage) {
return QString::number(value.value<quintptr>());
} 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<QVector4D>(value);
case kCombo: return ValueToBytesInternal<int>(value);
case kVideoStreamProperties:
case kAudioStreamProperties:
return value.value<Stream>().toBytes();
// These types have no persistent input
case kNone:
case kFootage:
case kFootageJob:
case kTexture:
case kSamples:
case kShaderJob:
@@ -177,6 +181,10 @@ QVector<QVariant> NodeValue::split_normal_value_into_track_values(Type type, con
QVariant NodeValue::combine_track_values_into_normal_value(Type type, const QVector<QVariant> &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:
+23 -7
View File
@@ -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
*
+1 -1
View File
@@ -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();
+6 -6
View File
@@ -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<Footage*>(item)) {
// Open this footage in a FootageViewer
PanelManager::instance()->MostRecentlyFocused<FootageViewerPanel>()->SetFootage(static_cast<Footage*>(item));
} else if (item->type() == Item::kSequence) {
} else if (dynamic_cast<Sequence*>(item)) {
// Open this sequence in the Timeline
Core::instance()->main_window()->OpenSequence(static_cast<Sequence*>(item));
}
@@ -210,10 +210,10 @@ void ProjectPanel::UpdateSubtitle()
if (explorer_->get_root_index().isValid()) {
QString folder_path;
Item* item = static_cast<Item*>(explorer_->get_root_index().internalPointer());
Folder* item = static_cast<Folder*>(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<Footage *> ProjectPanel::GetSelectedFootage() const
QVector<Footage*> footage;
foreach (Item* i, items) {
if (i->type() == Item::kFootage) {
if (dynamic_cast<Footage*>(i)) {
footage.append(static_cast<Footage*>(i));
}
}
+1 -1
View File
@@ -42,7 +42,7 @@ public:
QModelIndex get_root_index() const;
void set_root(Item* item);
void set_root(Folder* item);
QVector<Item *> SelectedItems() const;
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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;
+40 -55
View File
@@ -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<Folder*>(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<quintptr>(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<Item*>(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<Item*>(input.node());
if (item) {
int child_index = item_children_.indexOf(item);
emit BeginRemoveItem(item, child_index);
item_children_.removeAt(child_index);
emit EndRemoveItem();
}
}
+100 -6
View File
@@ -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<CategoryID> 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<Item*>& children() const
{
return item_children_;
}
/**
* @brief Returns a list of nodes that are of a certain type that this node outputs to
*/
template <typename T>
QVector<T*> ListOutputsOfType(bool recursive = true) const
{
QVector<T *> 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<typename T>
static void ListOutputsOfTypeInternal(const Folder* n, QVector<T*>& list, bool recursive)
{
foreach (const Node::OutputConnection& c, n->output_connections()) {
Node* connected = c.second.node();
T* cast_test = dynamic_cast<T*>(connected);
if (cast_test) {
// Avoid duplicates
if (!list.contains(cast_test)) {
list.append(cast_test);
}
}
if (recursive) {
Folder* subfolder = dynamic_cast<Folder*>(connected);
if (subfolder) {
ListOutputsOfTypeInternal(subfolder, list, recursive);
}
}
}
}
QVector<Item*> item_children_;
};
+2 -6
View File
@@ -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
)
-97
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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_));
}
}
-68
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef AUDIOSTREAM_H
#define AUDIOSTREAM_H
#include <QVector>
#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
+544 -109
View File
@@ -22,28 +22,51 @@
#include <QCoreApplication>
#include <QDir>
#include <QStandardPaths>
#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<DecoderPtr> 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; i<footage_info.streams.size(); i++) {
const Stream& s = footage_info.streams.at(i);
if (s.type() == Stream::kVideo || s.type() == Stream::kAudio) {
QString id = GetInputIDOfIndex(i);
NodeValue::Type type;
if (s.type() == Stream::kVideo) {
type = NodeValue::kVideoStreamProperties;
} else {
type = NodeValue::kAudioStreamProperties;
}
// Create input for properties
AddInput(id, type, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
SetStandardValue(id, QVariant::fromValue(s));
inputs_for_stream_properties_.insert(i, id);
// Create output for stream
Footage::StreamReference ref = GetReferenceFromRealIndex(i);
QString out_id = GetStringFromReference(ref);
AddOutput(out_id);
outputs_for_streams_.insert(i, out_id);
}
}
SetValid();
}
} else {
set_timestamp(0);
}
}
}
QString Footage::DescribeStream(int index) const
{
Stream stream = GetStreamAt(index);
switch (stream.type()) {
case Stream::kVideo:
if (stream.video_type() == Stream::kVideoTypeStill) {
return tr("%1: Image - %2x%3").arg(QString::number(index),
QString::number(stream.width()),
QString::number(stream.height()));
} else {
return tr("%1: Video - %2x%3").arg(QString::number(index),
QString::number(stream.width()),
QString::number(stream.height()));
}
case Stream::kAudio:
return QCoreApplication::translate("Stream", "%1: Audio - %2 Channel(s), %3Hz")
.arg(QString::number(index),
QString::number(stream.channel_count()),
QString::number(stream.sample_rate()));
case Stream::kUnknown:
case Stream::kData:
case Stream::kSubtitle:
case Stream::kAttachment:
break;
}
return tr("%1: Unknown").arg(QString::number(index));
}
Footage::StreamReference Footage::GetReferenceFromOutput(const QString &s) const
{
Stream::Type type = GetTypeFromOutput(s);
if (type != Stream::kUnknown) {
bool ok;
int index = s.mid(2).toInt(&ok);
if (ok) {
return StreamReference(this, type, index);
}
}
return StreamReference();
}
int Footage::GetStreamTypeCount(Stream::Type type) const
{
int count = 0;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream s = GetStreamAt(it.key());
if (s.type() == type) {
count++;
}
}
return count;
}
void Footage::Clear()
{
// Clear all streams
ClearStreams();
// Clear all dynamically created inputs
foreach (const QString& s, inputs_for_stream_properties_) {
RemoveInput(s);
}
inputs_for_stream_properties_.clear();
// Clear all dynamically created outputs
foreach (const QString& s, outputs_for_streams_) {
RemoveOutput(s);
}
outputs_for_streams_.clear();
// Reset stream count
stream_count_ = 0;
// Clear decoder link
decoder_.clear();
// Reset ready state
valid_ = false;
@@ -101,14 +270,14 @@ void Footage::SetValid()
valid_ = true;
}
const QString &Footage::filename() const
QString Footage::filename() const
{
return filename_;
return GetStandardValue(kFilenameInput).toString();
}
void Footage::set_filename(const QString &s)
{
filename_ = s;
SetStandardValue(kFilenameInput, s);
}
const qint64 &Footage::timestamp() const
@@ -121,37 +290,117 @@ void Footage::set_timestamp(const qint64 &t)
timestamp_ = t;
}
void Footage::add_stream(Stream* s)
int64_t Footage::GetTimeInTimebaseUnits(int index, const rational &time) const
{
// Set its footage parent to this
s->setParent(this);
Stream s = GetStreamAt(index);
// Add a copy of this stream to the list
streams_.append(s);
}
void Footage::add_streams(const QVector<Stream *> &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<Stream>();
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<Stream>();
if (stream.enabled() && stream.type() == type) {
return stream;
}
}
return Stream();
}
QVector<int> Footage::GetStreamIndexesOfType(Stream::Type type) const
{
QVector<int> indexes;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream stream = GetStandardValue(it.value()).value<Stream>();
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<VideoStream*>(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<VideoStream*>(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<Stream>();
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<VideoStream*>(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<VideoStream*>(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<AudioStream*>(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; i<stream_count_; i++) {
if (IsStreamEnabled(i)) {
enabled_streams |= (1 << i);
}
stream_enabler <<= 1;
}
return enabled_streams;
}
void Footage::ClearStreams()
{
// Delete all streams
streams_.clear();
}
bool Footage::HasEnabledStreamsOfType(const Stream::Type &type) const
{
// Return true if any streams are video streams
foreach (Stream* stream, streams_) {
if (stream->enabled() && stream->type() == type) {
for (int i=0; i<stream_count_; i++) {
Stream s = GetStreamAt(i);
if (s.enabled() && s.type() == type) {
return true;
}
}
@@ -291,37 +532,29 @@ bool Footage::HasEnabledStreamsOfType(const Stream::Type &type) const
return false;
}
Stream *Footage::get_first_enabled_stream_of_type(const Stream::Type &type) const
{
foreach (Stream* stream, streams_) {
if (stream->enabled() && 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<Footage> 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<const char*>(&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<const char*>(&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; i<stream_count_; i++) {
Stream s = GetStreamAt(i);
if (s.enabled()) {
tip.append("\n");
tip.append(s->description());
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);
}
}
+262 -75
View File
@@ -25,9 +25,9 @@
#include <QDateTime>
#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<CategoryID> 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<Stream*>& 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<Stream*>& 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>();
}
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<int> 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<int, QString> inputs_for_stream_properties_;
/**
* @brief List of dynamic outputs added for streams
*/
QMap<int, QString> outputs_for_streams_;
/**
* @brief Internal timestamp object
*/
qint64 timestamp_;
/**
* @brief Internal streams array
*/
QVector<Stream*> 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
+83 -138
View File
@@ -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<Stream::Type>(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<Type>(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<VideoType>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("interlacing")) {
interlacing_ = static_cast<VideoParams::Interlacing>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("pixelformat")) {
pixel_format_ = static_cast<VideoParams::Format>(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<quintptr>(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<Footage*>(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
}
}
+268 -69
View File
@@ -21,35 +21,20 @@
#ifndef STREAM_H
#define STREAM_H
#include <memory>
#include <QCoreApplication>
#include <QMutex>
#include <QString>
#include <QVector>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#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<const char*>(&type_), sizeof(type_));
arr.append(reinterpret_cast<const char*>(&timebase_), sizeof(timebase_));
arr.append(reinterpret_cast<const char*>(&duration_), sizeof(duration_));
arr.append(reinterpret_cast<const char*>(&channel_count_), sizeof(channel_count_));
arr.append(reinterpret_cast<const char*>(&enabled_), sizeof(enabled_));
arr.append(reinterpret_cast<const char*>(&width_), sizeof(width_));
arr.append(reinterpret_cast<const char*>(&height_), sizeof(height_));
arr.append(reinterpret_cast<const char*>(&pixel_aspect_ratio_), sizeof(pixel_aspect_ratio_));
arr.append(reinterpret_cast<const char*>(&video_type_), sizeof(video_type_));
arr.append(reinterpret_cast<const char*>(&interlacing_), sizeof(interlacing_));
arr.append(reinterpret_cast<const char*>(&pixel_format_), sizeof(pixel_format_));
arr.append(reinterpret_cast<const char*>(&frame_rate_), sizeof(frame_rate_));
arr.append(reinterpret_cast<const char*>(&start_time_), sizeof(start_time_));
arr.append(reinterpret_cast<const char*>(&premultiplied_alpha_), sizeof(premultiplied_alpha_));
arr.append(colorspace_.toUtf8());
arr.append(reinterpret_cast<const char*>(&sample_rate_), sizeof(sample_rate_));
arr.append(reinterpret_cast<const char*>(&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<Stream>;
}
Q_DECLARE_METATYPE(olive::Stream)
#endif // STREAM_H
-198
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "videostream.h"
#include <QFile>
#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<VideoParams::Interlacing>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("type")) {
set_video_type(static_cast<VideoType>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("format")) {
set_format(static_cast<VideoParams::Format>(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();
}
}
}
-179
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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
+13 -100
View File
@@ -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<Folder*>(GetConnectedNode(kParentInput));
}
void Item::set_project(Project *project)
void Item::Retranslate()
{
project_ = project;
foreach (Item* i, item_children_) {
i->set_project(project_);
}
}
QVector<Item *> Item::get_children_of_type(Type type, bool recursive) const
{
QVector<Item *> 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<Item*>(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"));
}
}
+7 -67
View File
@@ -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<Item*>& 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<Item*> 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*> item_children_;
Item* item_parent_;
Project* project_;
QString name_;
QString tooltip_;
};
+346 -207
View File
@@ -20,7 +20,6 @@
#include "sequence.h"
#include <QCoreApplication>
#include <QThread>
#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<Track::kCount;i++) {
// Create track input
QString track_input_id = kTrackInputFormat.arg(i);
AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
IgnoreInvalidationsFrom(track_input_id);
TrackList* list = new TrackList(this, static_cast<Track::Type>(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<VideoParams::Format>(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<VideoParams::Interlacing>(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<AudioParams::Format>(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<quintptr>(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 &params)
{
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 *> footage)
bool found_audio_params = false;
foreach (Footage* f, footage) {
foreach (Stream* s, f->streams()) {
if (!s->enabled()) {
for (int i=0; i<f->GetStreamCount(); 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<VideoStream*>(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<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt()),
static_cast<VideoParams::Format>(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<AudioStream*>(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 *> footage)
}
}
ViewerOutput *Sequence::viewer_output() const
QVector<Track *> Sequence::GetUnlockedTracks() const
{
return viewer_output_;
QVector<Track*> tracks = GetTracks();
for (int i=0;i<tracks.size();i++) {
if (tracks.at(i)->IsLocked()) {
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<Track::kCount;i++) {
QString input_name;
switch (static_cast<Track::Type>(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<rational>();
}
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<rational>();
}
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)
}
}
+169 -28
View File
@@ -21,12 +21,24 @@
#ifndef SEQUENCE_H
#define SEQUENCE_H
#include <QUuid>
#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<CategoryID> 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 *> footage);
ViewerOutput* viewer_output() const;
const QVector<Track *> &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<Track *> 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<TrackList*> track_lists_;
QVector<Track*> 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();
};
+47 -20
View File
@@ -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<Item *> 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<Item*> footage = this->get_items_of_type(Item::kFootage);
QVector<Footage*> footage = root()->ListOutputsOfType<Footage>();
foreach (Item* item, footage) {
foreach (Stream* s, static_cast<Footage*>(item)->streams()) {
if (s->type() == Stream::kVideo) {
static_cast<VideoStream*>(s)->ColorConfigChanged();
}
}
foreach (Footage* item, footage) {
item->InvalidateAll(QString());
//static_cast<VideoStream*>(s)->ColorConfigChanged();
}
}
void Project::DefaultColorSpaceChanged()
{
QVector<Item*> footage = this->get_items_of_type(Item::kFootage);
QVector<Footage*> footage = root_.ListOutputsOfType<Footage>();
foreach (Item* item, footage) {
foreach (Stream* s, static_cast<Footage*>(item)->streams()) {
if (s->type() == Stream::kVideo) {
static_cast<VideoStream*>(s)->DefaultColorSpaceChanged();
}
}
foreach (Footage* item, footage) {
item->InvalidateAll(QString());
//static_cast<VideoStream*>(s)->DefaultColorSpaceChanged();
}
}
+1 -3
View File
@@ -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<Item*> get_items_of_type(Item::Type type) const;
bool is_modified() const;
void set_modified(bool e);
+120 -216
View File
@@ -25,7 +25,7 @@
#include <QUrl>
#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<Folder*>(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<Folder*>(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<Folder*>(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<Folder*>(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<Item*>(index.internalPointer())->type() == Item::kFootage) {
if (dynamic_cast<Folder*>(static_cast<Item*>(index.internalPointer()))) {
stream_flags = static_cast<Footage*>(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<Folder*>(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<Folder*>(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<Folder*>(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<Folder*>(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;i<parent->item_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<Item*>(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<Folder*>(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<Item*>(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<Folder*>(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<Item*>(c.second.node());
if (item) {
DisconnectItem(item);
}
}
}
}
void ProjectViewModel::FolderBeginInsertItem(Item *n, int insert_index)
{
Folder* folder = static_cast<Folder*>(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<Folder*>(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<Item*>(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<Folder*>(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_);
}
}

Some files were not shown because too many files have changed in this diff Show More