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:
+40
-84
@@ -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 ÷r)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!stream_) {
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve video on a closed decoder";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -102,19 +104,14 @@ FramePtr Decoder::RetrieveVideo(const rational &timecode, const int ÷r)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream_->type() != Stream::kVideo) {
|
||||
qCritical() << "Tried to retrieve video from a non-video stream";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return RetrieveVideoInternal(timecode, divider);
|
||||
}
|
||||
|
||||
SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QAtomicInt *cancelled)
|
||||
SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams ¶ms, const QString& cache_path, const QAtomicInt *cancelled)
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (!stream_) {
|
||||
if (!stream_.IsValid()) {
|
||||
qCritical() << "Can't retrieve audio on a closed decoder";
|
||||
return nullptr;
|
||||
}
|
||||
@@ -124,13 +121,8 @@ SampleBufferPtr Decoder::RetrieveAudio(const TimeRange &range, const AudioParams
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stream_->type() != Stream::kAudio) {
|
||||
qCritical() << "Tried to retrieve audio from a non-audio stream";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Determine if we already have a conformed version
|
||||
QString conform_filename = GetConformedFilename(params);
|
||||
QString conform_filename = GetConformedFilename(cache_path, params);
|
||||
CurrentlyConforming want_conform = {stream_, params};
|
||||
|
||||
currently_conforming_mutex_.lock();
|
||||
@@ -179,9 +171,9 @@ void Decoder::Close()
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
|
||||
if (stream_) {
|
||||
if (stream_.IsValid()) {
|
||||
CloseInternal();
|
||||
stream_ = nullptr;
|
||||
stream_.Reset();
|
||||
} else {
|
||||
qWarning() << "Tried to close a decoder that wasn't open";
|
||||
}
|
||||
@@ -191,7 +183,7 @@ void Decoder::Close()
|
||||
* DECODER STATIC PUBLIC MEMBERS
|
||||
*/
|
||||
|
||||
QVector<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 ¶ms)
|
||||
QString Decoder::GetConformedFilename(const QString& cache_path, const AudioParams ¶ms)
|
||||
{
|
||||
QString index_fn = GetIndexFilename();
|
||||
QString index_fn = QStringLiteral("%1.%2:%3").arg(FileFunctions::GetUniqueFileIdentifier(stream_.filename()),
|
||||
QString::number(stream_.stream()));
|
||||
|
||||
index_fn = QDir(cache_path).filePath(index_fn);
|
||||
|
||||
index_fn.append('.');
|
||||
index_fn.append(QString::number(params.sample_rate()));
|
||||
@@ -284,15 +230,20 @@ QString Decoder::GetConformedFilename(const AudioParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
QString Decoder::GetIndexFilename()
|
||||
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
|
||||
{
|
||||
return QDir(stream_->footage()->project()->cache_path()).filePath(FileFunctions::GetUniqueFileIdentifier(stream()->footage()->filename()).append(QString::number(stream()->index())));
|
||||
return Timecode::time_to_timestamp(time, timebase) + start_time;
|
||||
}
|
||||
|
||||
void Decoder::SignalProcessingProgress(const int64_t &ts)
|
||||
Decoder::CodecStream Decoder::GetCodecStreamFromStreamReference(const Footage::StreamReference &ref)
|
||||
{
|
||||
if (stream()->duration() != AV_NOPTS_VALUE && stream()->duration() != 0) {
|
||||
emit IndexProgress(static_cast<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
@@ -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 ¶ms, const QAtomicInt* cancelled);
|
||||
|
||||
void SignalProcessingProgress(const int64_t& ts);
|
||||
void SignalProcessingProgress(int64_t ts, int64_t duration);
|
||||
|
||||
/**
|
||||
* @brief Get the destination filename of an audio stream conformed to a set of parameters
|
||||
*/
|
||||
QString GetConformedFilename(const AudioParams ¶ms);
|
||||
|
||||
QString GetIndexFilename();
|
||||
QString GetConformedFilename(const QString &cache_path, const AudioParams ¶ms);
|
||||
|
||||
struct CurrentlyConforming {
|
||||
Stream* stream;
|
||||
CodecStream stream;
|
||||
AudioParams params;
|
||||
|
||||
bool operator==(const CurrentlyConforming& rhs) const
|
||||
@@ -223,11 +259,13 @@ protected:
|
||||
*
|
||||
* This function is NOT thread safe and should therefore only be called by thread safe functions.
|
||||
*/
|
||||
Stream* stream() const
|
||||
const CodecStream& stream() const
|
||||
{
|
||||
return stream_;
|
||||
}
|
||||
|
||||
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time);
|
||||
|
||||
static QMutex currently_conforming_mutex_;
|
||||
static QWaitCondition currently_conforming_wait_cond_;
|
||||
static QVector<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
@@ -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 ÷r)
|
||||
/*FramePtr FFmpegDecoder::RetrieveStillImage(const rational &timecode, const int ÷r)
|
||||
{
|
||||
// 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 ÷r)
|
||||
{
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef FOOTAGEMETA_H
|
||||
#define FOOTAGEMETA_H
|
||||
|
||||
struct FootageData {
|
||||
struct StreamData {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // FOOTAGEMETA_H
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+31
-8
@@ -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
|
||||
+2
-2
@@ -18,11 +18,11 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "preferencestab.h"
|
||||
#include "configdialogbasetab.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
bool PreferencesTab::Validate()
|
||||
bool ConfigDialogBaseTab::Validate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
+4
-3
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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")));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -36,7 +36,7 @@ void NodeCopyPasteService::CopyNodesToClipboard(const QVector<Node *> &nodes, vo
|
||||
QXmlStreamWriter writer(©_str);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
writer.writeStartDocument(QString::number(Core::kProjectVersion));
|
||||
writer.writeStartElement(QStringLiteral("olive"));
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
@@ -65,12 +65,15 @@ QVector<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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
QModelIndex get_root_index() const;
|
||||
|
||||
void set_root(Item* item);
|
||||
void set_root(Folder* item);
|
||||
|
||||
QVector<Item *> SelectedItems() const;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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_));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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_;
|
||||
|
||||
};
|
||||
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
viewer_output_->set_audio_params(params);
|
||||
return tr("%1 FPS").arg(video_params().time_base().flipped().toDouble());
|
||||
}
|
||||
|
||||
void Sequence::set_default_parameters()
|
||||
@@ -279,45 +133,48 @@ void Sequence::set_parameters_from_footage(const QVector<Footage *> 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user