implement entire project in nodes

Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly.
This commit is contained in:
itsmattkc
2021-02-15 21:31:57 +11:00
parent 155470bec1
commit d7c5ac4b44
174 changed files with 3515 additions and 4658 deletions
+40 -55
View File
@@ -27,80 +27,65 @@
namespace olive {
Item::Type Folder::type() const
Folder::Folder()
{
return kFolder;
}
bool Folder::CanHaveChildren() const
{
return true;
}
QIcon Folder::icon()
QIcon Folder::icon() const
{
return icon::Folder;
}
void Folder::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt *cancelled)
bool ChildExistsWithNameInternal(const Folder* n, const QString& s)
{
XMLAttributeLoop(reader, attr) {
if (cancelled && *cancelled) {
return;
}
foreach (const Node::OutputConnection& c, n->output_connections()) {
Node* connected = c.second.node();
if (attr.name() == QStringLiteral("name")) {
set_name(attr.value().toString());
} else if (attr.name() == QStringLiteral("ptr")) {
xml_node_data.item_ptrs.insert(attr.value().toULongLong(), this);
}
}
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
Item* child;
if (reader->name() == QStringLiteral("folder")) {
child = new Folder();
} else if (reader->name() == QStringLiteral("footage")) {
child = new Footage();
} else if (reader->name() == QStringLiteral("sequence")) {
child = new Sequence();
if (connected->GetLabel() == s) {
return true;
} else {
reader->skipCurrentElement();
continue;
}
Folder* subfolder = dynamic_cast<Folder*>(connected);
child->setParent(this);
child->Load(reader, xml_node_data, version, cancelled);
if (subfolder && ChildExistsWithNameInternal(subfolder, s)) {
return true;
}
}
}
return false;
}
void Folder::Save(QXmlStreamWriter *writer) const
bool Folder::ChildExistsWithName(const QString &s) const
{
writer->writeAttribute(QStringLiteral("name"), name());
return ChildExistsWithNameInternal(this, s);
}
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
void Folder::OutputConnectedEvent(const QString &output, const NodeInput &input)
{
Q_UNUSED(output)
foreach (Item* child, children()) {
switch (child->type()) {
case Item::kFootage:
writer->writeStartElement(QStringLiteral("footage"));
break;
case Item::kSequence:
writer->writeStartElement(QStringLiteral("sequence"));
break;
case Item::kFolder:
writer->writeStartElement(QStringLiteral("folder"));
break;
}
Item* item = dynamic_cast<Item*>(input.node());
child->Save(writer);
if (item) {
// The insert index is always our "count" because we only support appending in our internal
// model. For sorting/organizing, a QSortFilterProxyModel is used instead.
emit BeginInsertItem(item, item_child_count());
item_children_.append(item);
emit EndInsertItem();
}
}
writer->writeEndElement(); // footage/folder/sequence
void Folder::OutputDisconnectedEvent(const QString &output, const NodeInput &input)
{
Q_UNUSED(output)
Item* item = dynamic_cast<Item*>(input.node());
if (item) {
int child_index = item_children_.indexOf(item);
emit BeginRemoveItem(item, child_index);
item_children_.removeAt(child_index);
emit EndRemoveItem();
}
}
+100 -6
View File
@@ -21,6 +21,7 @@
#ifndef FOLDER_H
#define FOLDER_H
#include "node/node.h"
#include "project/item/item.h"
namespace olive {
@@ -33,20 +34,113 @@ namespace olive {
*/
class Folder : public Item
{
Q_OBJECT
public:
Folder() = default;
Folder();
virtual Type type() const override;
virtual Node* copy() const override
{
return new Folder();
}
virtual bool CanHaveChildren() const override;
virtual QString Name() const override
{
return tr("Folder");
}
virtual QIcon icon() override;
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.folder");
}
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override;
virtual QVector<CategoryID> Category() const override
{
return {kCategoryProject};
}
virtual void Save(QXmlStreamWriter* writer) const override;
virtual QString Description() const override
{
return tr("Organize several items into a single collection.");
}
virtual QIcon icon() const override;
bool ChildExistsWithName(const QString& s) const;
int item_child_count() const
{
return item_children_.size();
}
Item* item_child(int i) const
{
return item_children_.at(i);
}
const QVector<Item*>& children() const
{
return item_children_;
}
/**
* @brief Returns a list of nodes that are of a certain type that this node outputs to
*/
template <typename T>
QVector<T*> ListOutputsOfType(bool recursive = true) const
{
QVector<T *> list;
ListOutputsOfTypeInternal(this, list, recursive);
return list;
}
int index_of_child(Item* item) const
{
return item_children_.indexOf(item);
}
signals:
void BeginInsertItem(Item* n, int index);
void EndInsertItem();
void BeginRemoveItem(Item* n, int index);
void EndRemoveItem();
protected:
virtual void OutputConnectedEvent(const QString& output, const NodeInput& input) override;
virtual void OutputDisconnectedEvent(const QString& output, const NodeInput& input) override;
private:
template<typename T>
static void ListOutputsOfTypeInternal(const Folder* n, QVector<T*>& list, bool recursive)
{
foreach (const Node::OutputConnection& c, n->output_connections()) {
Node* connected = c.second.node();
T* cast_test = dynamic_cast<T*>(connected);
if (cast_test) {
// Avoid duplicates
if (!list.contains(cast_test)) {
list.append(cast_test);
}
}
if (recursive) {
Folder* subfolder = dynamic_cast<Folder*>(connected);
if (subfolder) {
ListOutputsOfTypeInternal(subfolder, list, recursive);
}
}
}
}
QVector<Item*> item_children_;
};
+2 -6
View File
@@ -17,13 +17,9 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
project/item/footage/audiostream.h
project/item/footage/audiostream.cpp
project/item/footage/footage.h
project/item/footage/footage.cpp
project/item/footage/stream.h
project/item/footage/footage.h
project/item/footage/stream.cpp
project/item/footage/videostream.h
project/item/footage/videostream.cpp
project/item/footage/stream.h
PARENT_SCOPE
)
-97
View File
@@ -1,97 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "audiostream.h"
#include "common/xmlutils.h"
namespace olive {
AudioStream::AudioStream()
{
set_type(kAudio);
}
QString AudioStream::description() const
{
return QCoreApplication::translate("Stream", "%1: Audio - %2 Channel(s), %3Hz")
.arg(QString::number(index()),
QString::number(channels()),
QString::number(sample_rate()));
}
const int &AudioStream::channels() const
{
return channels_;
}
void AudioStream::set_channels(const int &channels)
{
channels_ = channels;
}
const uint64_t &AudioStream::channel_layout() const
{
return layout_;
}
void AudioStream::set_channel_layout(const uint64_t &layout)
{
layout_ = layout;
}
const int &AudioStream::sample_rate() const
{
return sample_rate_;
}
void AudioStream::set_sample_rate(const int &sample_rate)
{
sample_rate_ = sample_rate;
}
QIcon AudioStream::icon() const
{
return icon::Audio;
}
void AudioStream::LoadCustomParameters(QXmlStreamReader *reader)
{
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("channels")) {
set_channels(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("layout")) {
set_channel_layout(reader->readElementText().toULongLong());
} else if (reader->name() == QStringLiteral("rate")) {
set_sample_rate(reader->readElementText().toInt());
} else {
reader->skipCurrentElement();
}
}
}
void AudioStream::SaveCustomParameters(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("channels"), QString::number(channels_));
writer->writeTextElement(QStringLiteral("layout"), QString::number(layout_));
writer->writeTextElement(QStringLiteral("rate"), QString::number(sample_rate_));
}
}
-68
View File
@@ -1,68 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef AUDIOSTREAM_H
#define AUDIOSTREAM_H
#include <QVector>
#include "common/rational.h"
#include "render/audioparams.h"
#include "stream.h"
namespace olive {
/**
* @brief A Stream derivative containing audio-specific information
*/
class AudioStream : public Stream
{
Q_OBJECT
public:
AudioStream();
virtual QString description() const override;
const int& channels() const;
void set_channels(const int& channels);
const uint64_t& channel_layout() const;
void set_channel_layout(const uint64_t& channel_layout);
const int& sample_rate() const;
void set_sample_rate(const int& sample_rate);
virtual QIcon icon() const override;
protected:
virtual void LoadCustomParameters(QXmlStreamReader *reader) override;
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override;
private:
int channels_;
uint64_t layout_;
int sample_rate_;
};
}
#endif // AUDIOSTREAM_H
+544 -109
View File
@@ -22,28 +22,51 @@
#include <QCoreApplication>
#include <QDir>
#include <QStandardPaths>
#include "codec/decoder.h"
#include "common/filefunctions.h"
#include "common/xmlutils.h"
#include "config/config.h"
#include "core.h"
#include "render/job/footagejob.h"
#include "ui/icons/icons.h"
namespace olive {
Footage::Footage()
const QString Footage::kFilenameInput = QStringLiteral("file_in");
const QString Footage::kStreamPropertiesFormat = QStringLiteral("stream_properties:%1");
#define super Item
Footage::Footage(const QString &filename) :
super(true, false),
stream_count_(0),
cancelled_(nullptr)
{
AddInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
Clear();
set_filename(filename);
}
Footage::~Footage()
void Footage::Retranslate()
{
ClearStreams();
super::Retranslate();
SetInputName(kFilenameInput, tr("Filename"));
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
StreamReference ref = GetReferenceFromRealIndex(it.key());
SetInputName(it.value(), QStringLiteral("%1 %2").arg(GetStreamTypeName(ref.type()), QString::number(ref.index())));
}
}
void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled)
void Footage::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled)
{
Q_UNUSED(xml_node_data)
Q_UNUSED(version)
while (XMLReadNextStartElement(reader)) {
@@ -51,16 +74,8 @@ void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint ve
return;
}
if (reader->name() == QStringLiteral("name")) {
set_name(reader->readElementText());
} else if (reader->name() == QStringLiteral("filename")) {
set_filename(reader->readElementText());
} else if (reader->name() == QStringLiteral("stream")) {
add_stream(Stream::Load(reader, xml_node_data, cancelled));
} else if (reader->name() == QStringLiteral("timestamp")) {
if (reader->name() == QStringLiteral("timestamp")) {
set_timestamp(reader->readElementText().toLongLong());
} else if (reader->name() == QStringLiteral("decoder")) {
set_decoder(reader->readElementText());
} else if (reader->name() == QStringLiteral("points")) {
TimelinePoints::Load(reader);
} else {
@@ -69,28 +84,182 @@ void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint ve
}
}
void Footage::Save(QXmlStreamWriter *writer) const
void Footage::SaveInternal(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("name"), name());
writer->writeTextElement(QStringLiteral("filename"), filename());
writer->writeTextElement(QStringLiteral("timestamp"), QString::number(timestamp_));
writer->writeTextElement(QStringLiteral("decoder"), decoder_);
writer->writeStartElement(QStringLiteral("points"));
TimelinePoints::Save(writer);
TimelinePoints::Save(writer);
writer->writeEndElement(); // points
}
foreach (Stream* stream, streams_) {
writer->writeStartElement(QStringLiteral("stream"));
stream->Save(writer);
writer->writeEndElement(); // stream
void Footage::InputValueChangedEvent(const QString &input, int element)
{
Q_UNUSED(element)
if (input == kFilenameInput) {
// Reset internal stream cache
Clear();
// Determine if file still exists
QFileInfo info(filename());
if (info.exists()) {
// Grab timestamp
set_timestamp(info.lastModified().toMSecsSinceEpoch());
// Determine if we've already cached the metadata of this file
QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename()));
MetadataCache footage_info;
if (QFileInfo::exists(meta_cache_file)) {
// Load meta cache file
footage_info = LoadStreamCache(meta_cache_file);
} else {
// Probe and create cache
QVector<DecoderPtr> decoder_list = Decoder::ReceiveListOfAllDecoders();
foreach (DecoderPtr decoder, decoder_list) {
footage_info.streams = decoder->Probe(filename(), cancelled_);
if (!footage_info.streams.isEmpty()) {
footage_info.decoder = decoder->id();
SetValid();
break;
}
}
if (!SaveStreamCache(meta_cache_file, footage_info)) {
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
}
}
stream_count_ = footage_info.streams.size();
if (!footage_info.streams.isEmpty()) {
set_decoder(footage_info.decoder);
for (int i=0; i<footage_info.streams.size(); i++) {
const Stream& s = footage_info.streams.at(i);
if (s.type() == Stream::kVideo || s.type() == Stream::kAudio) {
QString id = GetInputIDOfIndex(i);
NodeValue::Type type;
if (s.type() == Stream::kVideo) {
type = NodeValue::kVideoStreamProperties;
} else {
type = NodeValue::kAudioStreamProperties;
}
// Create input for properties
AddInput(id, type, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
SetStandardValue(id, QVariant::fromValue(s));
inputs_for_stream_properties_.insert(i, id);
// Create output for stream
Footage::StreamReference ref = GetReferenceFromRealIndex(i);
QString out_id = GetStringFromReference(ref);
AddOutput(out_id);
outputs_for_streams_.insert(i, out_id);
}
}
SetValid();
}
} else {
set_timestamp(0);
}
}
}
QString Footage::DescribeStream(int index) const
{
Stream stream = GetStreamAt(index);
switch (stream.type()) {
case Stream::kVideo:
if (stream.video_type() == Stream::kVideoTypeStill) {
return tr("%1: Image - %2x%3").arg(QString::number(index),
QString::number(stream.width()),
QString::number(stream.height()));
} else {
return tr("%1: Video - %2x%3").arg(QString::number(index),
QString::number(stream.width()),
QString::number(stream.height()));
}
case Stream::kAudio:
return QCoreApplication::translate("Stream", "%1: Audio - %2 Channel(s), %3Hz")
.arg(QString::number(index),
QString::number(stream.channel_count()),
QString::number(stream.sample_rate()));
case Stream::kUnknown:
case Stream::kData:
case Stream::kSubtitle:
case Stream::kAttachment:
break;
}
return tr("%1: Unknown").arg(QString::number(index));
}
Footage::StreamReference Footage::GetReferenceFromOutput(const QString &s) const
{
Stream::Type type = GetTypeFromOutput(s);
if (type != Stream::kUnknown) {
bool ok;
int index = s.mid(2).toInt(&ok);
if (ok) {
return StreamReference(this, type, index);
}
}
return StreamReference();
}
int Footage::GetStreamTypeCount(Stream::Type type) const
{
int count = 0;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream s = GetStreamAt(it.key());
if (s.type() == type) {
count++;
}
}
return count;
}
void Footage::Clear()
{
// Clear all streams
ClearStreams();
// Clear all dynamically created inputs
foreach (const QString& s, inputs_for_stream_properties_) {
RemoveInput(s);
}
inputs_for_stream_properties_.clear();
// Clear all dynamically created outputs
foreach (const QString& s, outputs_for_streams_) {
RemoveOutput(s);
}
outputs_for_streams_.clear();
// Reset stream count
stream_count_ = 0;
// Clear decoder link
decoder_.clear();
// Reset ready state
valid_ = false;
@@ -101,14 +270,14 @@ void Footage::SetValid()
valid_ = true;
}
const QString &Footage::filename() const
QString Footage::filename() const
{
return filename_;
return GetStandardValue(kFilenameInput).toString();
}
void Footage::set_filename(const QString &s)
{
filename_ = s;
SetStandardValue(kFilenameInput, s);
}
const qint64 &Footage::timestamp() const
@@ -121,37 +290,117 @@ void Footage::set_timestamp(const qint64 &t)
timestamp_ = t;
}
void Footage::add_stream(Stream* s)
int64_t Footage::GetTimeInTimebaseUnits(int index, const rational &time) const
{
// Set its footage parent to this
s->setParent(this);
Stream s = GetStreamAt(index);
// Add a copy of this stream to the list
streams_.append(s);
}
void Footage::add_streams(const QVector<Stream *> &streams)
{
foreach (Stream* s, streams) {
s->setParent(this);
if (!s.IsValid()) {
return AV_NOPTS_VALUE;
}
streams_.append(streams);
return Timecode::time_to_timestamp(time, s.timebase()) + s.start_time();
}
Stream* Footage::stream(int index) const
int Footage::GetRealStreamIndex(Stream::Type type, int index) const
{
return streams_.at(index);
int lookup_index = 0;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream stream = GetStandardValue(it.value()).value<Stream>();
if (stream.type() == type) {
if (lookup_index == index) {
return it.key();
} else {
lookup_index++;
}
}
}
return -1;
}
int Footage::stream_count() const
QString Footage::GetStringFromReference(Stream::Type type, int index)
{
return streams_.size();
QString type_string;
if (type == Stream::kVideo) {
type_string = QStringLiteral("v");
} else if (type == Stream::kAudio) {
type_string = QStringLiteral("a");
} else {
return QString();
}
return QStringLiteral("%1:%2").arg(type_string, QString::number(index));
}
Item::Type Footage::type() const
Footage::StreamReference Footage::GetReferenceFromRealIndex(int real_index) const
{
return kFootage;
Stream s = GetStreamAt(real_index);
if (!s.IsValid()) {
// Return invalid/null reference
return StreamReference();
}
int index_in_type = 0;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
if (it.key() == real_index) {
break;
} else {
Stream temp = GetStreamAt(it.key());
if (temp.type() == s.type()) {
index_in_type++;
}
}
}
return StreamReference(this, s.type(), index_in_type);
}
Stream::Type Footage::GetTypeFromOutput(const QString &s) const
{
if (s.at(1) == ':') {
if (s.at(0) == 'v') {
// Video stream
return Stream::kVideo;
} else if (s.at(0) == 'a') {
// Audio stream
return Stream::kAudio;
}
}
return Stream::kUnknown;
}
Stream Footage::GetFirstEnabledStreamOfType(Stream::Type type) const
{
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream stream = GetStandardValue(it.value()).value<Stream>();
if (stream.enabled() && stream.type() == type) {
return stream;
}
}
return Stream();
}
QVector<int> Footage::GetStreamIndexesOfType(Stream::Type type) const
{
QVector<int> indexes;
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream stream = GetStandardValue(it.value()).value<Stream>();
if (stream.enabled() && stream.type() == type) {
indexes.append(it.key());
}
}
return indexes;
}
const QString &Footage::decoder() const
@@ -164,17 +413,17 @@ void Footage::set_decoder(const QString &id)
decoder_ = id;
}
QIcon Footage::icon()
QIcon Footage::icon() const
{
if (valid_ && !streams_.isEmpty()) {
if (valid_ && !inputs_for_stream_properties_.isEmpty()) {
// Prioritize video > audio > image
Stream* s = get_first_enabled_stream_of_type(Stream::kVideo);
Stream s = GetFirstEnabledStreamOfType(Stream::kVideo);
if (s && static_cast<VideoStream*>(s)->video_type() != VideoStream::kVideoTypeStill) {
if (s.IsValid() && s.video_type() != Stream::kVideoTypeStill) {
return icon::Video;
} else if (HasEnabledStreamsOfType(Stream::kAudio)) {
return icon::Audio;
} else if (s && static_cast<VideoStream*>(s)->video_type() == VideoStream::kVideoTypeStill) {
} else if (s.IsValid() && s.video_type() == Stream::kVideoTypeStill) {
return icon::Image;
}
}
@@ -185,32 +434,31 @@ QIcon Footage::icon()
QString Footage::duration()
{
// Find longest stream duration
Stream* longest_stream = nullptr;
Stream longest_stream;
rational longest;
foreach (Stream* stream, streams_) {
if (stream->enabled() && (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio)) {
rational this_stream_dur = Timecode::timestamp_to_time(stream->duration(),
stream->timebase());
for (auto it=inputs_for_stream_properties_.cbegin(); it!=inputs_for_stream_properties_.cend(); it++) {
Stream s = GetStandardValue(it.value()).value<Stream>();
if (s.enabled() && (s.type() == Stream::kVideo || s.type() == Stream::kAudio)) {
rational this_stream_dur = Timecode::timestamp_to_time(s.duration(), s.timebase());
if (this_stream_dur > longest) {
longest_stream = stream;
longest_stream = s;
longest = this_stream_dur;
}
}
}
if (longest_stream) {
if (longest_stream->type() == Stream::kVideo) {
VideoStream* video_stream = static_cast<VideoStream*>(longest_stream);
if (longest_stream.IsValid()) {
if (longest_stream.type() == Stream::kVideo) {
if (longest_stream.video_type() != Stream::kVideoTypeStill) {
int64_t duration = longest_stream.duration();
rational frame_rate_timebase = longest_stream.frame_rate().flipped();
if (video_stream->video_type() != VideoStream::kVideoTypeStill) {
int64_t duration = video_stream->duration();
rational frame_rate_timebase = video_stream->frame_rate().flipped();
if (video_stream->timebase() != frame_rate_timebase) {
if (longest_stream.timebase() != frame_rate_timebase) {
// Convert from timebase to frame rate
rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase());
rational duration_time = Timecode::timestamp_to_time(duration, longest_stream.timebase());
duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase);
}
@@ -218,7 +466,7 @@ QString Footage::duration()
frame_rate_timebase,
Core::instance()->GetTimecodeDisplay());
}
} else if (longest_stream->type() == Stream::kAudio) {
} else if (longest_stream.type() == Stream::kAudio) {
// If we're showing in a timecode, we prefer showing audio in seconds instead
Timecode::Display display = Core::instance()->GetTimecodeDisplay();
if (display == Timecode::kTimecodeDropFrame
@@ -226,8 +474,8 @@ QString Footage::duration()
display = Timecode::kTimecodeSeconds;
}
return Timecode::timestamp_to_timecode(longest_stream->duration(),
longest_stream->timebase(),
return Timecode::timestamp_to_timecode(longest_stream.duration(),
longest_stream.timebase(),
display);
}
}
@@ -237,21 +485,21 @@ QString Footage::duration()
QString Footage::rate()
{
if (streams_.isEmpty()) {
if (inputs_for_stream_properties_.isEmpty()) {
return QString();
}
if (HasEnabledStreamsOfType(Stream::kVideo)) {
// This is a video editor, prioritize video streams
VideoStream* video_stream = static_cast<VideoStream*>(get_first_enabled_stream_of_type(Stream::kVideo));
Stream video_stream = GetFirstEnabledStreamOfType(Stream::kVideo);
if (video_stream->video_type() != VideoStream::kVideoTypeStill) {
return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream->frame_rate().toDouble());
if (video_stream.video_type() != Stream::kVideoTypeStill) {
return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream.frame_rate().toDouble());
}
} else if (HasEnabledStreamsOfType(Stream::kAudio)) {
// No video streams, return audio
AudioStream* audio_stream = static_cast<AudioStream*>(streams_.first());
return QCoreApplication::translate("Footage", "%1 Hz").arg(audio_stream->sample_rate());
Stream audio_stream = GetStreamAt(0);
return QCoreApplication::translate("Footage", "%1 Hz").arg(audio_stream.sample_rate());
}
return QString();
@@ -260,30 +508,23 @@ QString Footage::rate()
quint64 Footage::get_enabled_stream_flags() const
{
quint64 enabled_streams = 0;
quint64 stream_enabler = 1;
foreach (Stream* s, streams_) {
if (s->enabled()) {
enabled_streams |= stream_enabler;
for (int i=0; i<stream_count_; i++) {
if (IsStreamEnabled(i)) {
enabled_streams |= (1 << i);
}
stream_enabler <<= 1;
}
return enabled_streams;
}
void Footage::ClearStreams()
{
// Delete all streams
streams_.clear();
}
bool Footage::HasEnabledStreamsOfType(const Stream::Type &type) const
{
// Return true if any streams are video streams
foreach (Stream* stream, streams_) {
if (stream->enabled() && stream->type() == type) {
for (int i=0; i<stream_count_; i++) {
Stream s = GetStreamAt(i);
if (s.enabled() && s.type() == type) {
return true;
}
}
@@ -291,37 +532,29 @@ bool Footage::HasEnabledStreamsOfType(const Stream::Type &type) const
return false;
}
Stream *Footage::get_first_enabled_stream_of_type(const Stream::Type &type) const
{
foreach (Stream* stream, streams_) {
if (stream->enabled() && stream->type() == type) {
return stream;
}
}
return nullptr;
}
bool Footage::CompareFootageToFile(Footage *footage, const QString &filename)
{
// Heuristic to determine if file has changed
QFileInfo info(filename);
if (info.exists()) {
if (info.lastModified().toMSecsSinceEpoch() == footage->timestamp()) {
/*if (info.lastModified().toMSecsSinceEpoch() == footage->timestamp()) {
// Footage has not been modified and is where we expect
return true;
} else {
// Footage may have changed and we'll have to re-probe it. It also may not have, in which
// case nothing needs to change.
std::unique_ptr<Footage> item(Decoder::Probe(footage->project(), filename, nullptr));
DecoderPtr decoder = Decoder::CreateFromID(footage->decoder());
if (item) {
// Item is the same type, that's a good sign. Let's look for any differences.
// FIXME: Implement this
Streams probed_streams = decoder->Probe(filename, nullptr);
if (probed_streams == footage->streams_) {
return true;
}
}
}*/
// Simplified, since our footage node is much more tolerant, we'll try this
return true;
}
// Footage file couldn't be found or resolved to something we didn't expect
@@ -333,16 +566,106 @@ bool Footage::CompareFootageToItsFilename(Footage *footage)
return CompareFootageToFile(footage, footage->filename());
}
void Footage::Hash(const QString& output, QCryptographicHash &hash, const rational &time) const
{
super::Hash(output, hash, time);
// Translate output ID to stream
StreamReference ref = GetReferenceFromOutput(output);
QString fn = filename();
if (!fn.isEmpty()) {
Stream stream = GetStreamAt(GetReferenceFromOutput(output));
if (stream.IsValid()) {
// Add footage details to hash
// Footage filename
hash.addData(filename().toUtf8());
// Footage last modified date
hash.addData(QString::number(timestamp()).toUtf8());
// Footage stream
hash.addData(QString::number(ref.index()).toUtf8());
if (ref.type() == Stream::kVideo) {
// Current color config and space
hash.addData(project()->color_manager()->GetConfigFilename().toUtf8());
hash.addData(stream.colorspace().toUtf8());
// Alpha associated setting
hash.addData(QString::number(stream.premultiplied_alpha()).toUtf8());
// Pixel aspect ratio
hash.addData(reinterpret_cast<const char*>(&stream.pixel_aspect_ratio()), sizeof(stream.pixel_aspect_ratio()));
// Footage timestamp
if (stream.video_type() != Stream::kVideoTypeStill) {
int64_t video_ts = Timecode::time_to_timestamp(time, stream.timebase());
// Add timestamp in units of the video stream's timebase
hash.addData(reinterpret_cast<const char*>(&video_ts), sizeof(int64_t));
// Add start time - used for both image sequences and video streams
hash.addData(QString::number(stream.start_time()).toUtf8());
}
}
}
}
}
NodeValueTable Footage::Value(const QString &output, NodeValueDatabase &value) const
{
StreamReference ref = GetReferenceFromOutput(output);
// Pop filename from table
QString file = value[kFilenameInput].Take(NodeValue::kFile).toString();
// Merge table
NodeValueTable table = value.Merge();
// If the file exists and the reference is valid, push a footage job to the renderer
if (QFileInfo(file).exists() && ref.IsValid()) {
table.Push(NodeValue::kFootageJob, QVariant::fromValue(ref), this);
}
return table;
}
QString Footage::GetStreamTypeName(Stream::Type type)
{
switch (type) {
case Stream::kVideo:
return tr("Video");
case Stream::kAudio:
return tr("Audio");
case Stream::kSubtitle:
return tr("Subtitle");
case Stream::kData:
return tr("Data");
case Stream::kAttachment:
return tr("Attachment");
case Stream::kUnknown:
break;
}
return tr("Unknown");
}
void Footage::UpdateTooltip()
{
if (valid_) {
QString tip = QCoreApplication::translate("Footage", "Filename: %1").arg(filename());
if (!streams_.isEmpty()) {
foreach (Stream* s, streams_) {
if (s->enabled()) {
if (!inputs_for_stream_properties_.isEmpty()) {
for (int i=0; i<stream_count_; i++) {
Stream s = GetStreamAt(i);
if (s.enabled()) {
tip.append("\n");
tip.append(s->description());
tip.append(DescribeStream(i));
}
}
}
@@ -353,4 +676,116 @@ void Footage::UpdateTooltip()
}
}
Footage::MetadataCache Footage::LoadStreamCache(const QString &filename)
{
MetadataCache cache;
QFile file(filename);
if (file.open(QFile::ReadOnly)) {
QXmlStreamReader reader(&file);
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("streamcache")) {
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("decoder")) {
cache.decoder = reader.readElementText();
} else if (reader.name() == QStringLiteral("streams")) {
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("stream")) {
Stream s;
s.Load(&reader);
cache.streams.append(s);
} else {
reader.skipCurrentElement();
}
}
} else {
reader.skipCurrentElement();
}
}
} else {
reader.skipCurrentElement();
}
}
file.close();
}
return cache;
}
bool Footage::SaveStreamCache(const QString &filename, const Footage::MetadataCache &data)
{
QFile file(filename);
if (!file.open(QFile::WriteOnly)) {
return false;
}
QXmlStreamWriter writer(&file);
writer.writeStartDocument();
writer.writeStartElement(QStringLiteral("streamcache"));
writer.writeTextElement(QStringLiteral("decoder"), data.decoder);
writer.writeStartElement(QStringLiteral("streams"));
foreach (const Stream& s, data.streams) {
writer.writeStartElement(QStringLiteral("stream"));
s.Save(&writer);
writer.writeEndElement(); // stream
}
writer.writeEndElement(); // streams
writer.writeEndElement(); // streamcache
writer.writeEndDocument();
file.close();
return true;
}
void Footage::CheckFootage()
{
QString fn = filename();
if (!fn.isEmpty()) {
QFileInfo info(fn);
qint64 current_file_timestamp = info.lastModified().toMSecsSinceEpoch();
if (current_file_timestamp != timestamp()) {
// File has changed!
set_timestamp(current_file_timestamp);
InvalidateAll(kFilenameInput);
}
}
}
QString Footage::StreamReference::video_colorspace(bool default_if_empty) const
{
if (IsValid()) {
Stream stream = footage_->GetStreamAt(type_, index_);
if (stream.IsValid()) {
if (stream.colorspace().isEmpty() && default_if_empty) {
return footage_->project()->color_manager()->GetDefaultInputColorSpace();
} else {
return stream.colorspace();
}
}
}
return QString();
}
uint qHash(const Footage::StreamReference &ref, uint seed)
{
return qHash(ref.footage(), seed) ^ qHash(ref.type(), seed) ^ qHash(ref.index(), seed);
}
}
+262 -75
View File
@@ -25,9 +25,9 @@
#include <QDateTime>
#include "common/rational.h"
#include "node/node.h"
#include "project/item/item.h"
#include "project/item/footage/audiostream.h"
#include "project/item/footage/videostream.h"
#include "stream.h"
#include "timeline/timelinepoints.h"
namespace olive {
@@ -46,24 +46,34 @@ public:
/**
* @brief Footage Constructor
*/
Footage();
Footage(const QString& filename = QString());
/**
* @brief Footage Destructor
*
* Makes sure Stream objects are cleared properly
*/
virtual ~Footage() override;
virtual Node* copy() const override
{
return new Footage();
}
/**
* @brief Load function
*/
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override;
virtual QString Name() const override
{
return tr("Footage");
}
/**
* @brief Save function
*/
virtual void Save(QXmlStreamWriter *writer) const override;
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.footage");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryProject};
}
virtual QString Description() const override
{
return tr("Import video, audio, or still image files into the composition.");
}
virtual void Retranslate() override;
/**
* @brief Reset Footage state ready for running through Probe() again
@@ -90,7 +100,7 @@ public:
/**
* @brief Return the current filename of this Footage object
*/
const QString& filename() const;
QString filename() const;
/**
* @brief Set the filename
@@ -123,54 +133,189 @@ public:
*/
void set_timestamp(const qint64 &t);
/**
* @brief Add a stream metadata object to this footage
*
* Usually done during a Decoder::Probe() function for retrieving metadata about the video/audio/other streams
* inside a container. Streams can have non-video/audio types so that they can be equivalent to the file's actual
* stream list, though the only streams officially supported are video and audio streams.
*
* @param s
*
* A pointer to a stream object. The Footage takes ownership of this object and will free it when it's deleted.
*/
void add_stream(Stream *s);
void add_streams(const QVector<Stream*>& streams);
/**
* @brief Retrieve a stream at the given index.
*
* @param index
*
* The index will be equivalent to the stream's index in the file (or in FFmpeg
* terms AVStream->file_index). Must be < stream_count().
*
* @return
*
* The stream at the index provided
*/
Stream *stream(int index) const;
/**
* @brief Returns a list of the streams in this Footage
*/
const QVector<Stream*>& streams() const
void SetCancelPointer(const QAtomicInt* c)
{
return streams_;
cancelled_ = c;
}
/**
* @brief Retrieve total number of streams in this Footage file
*/
int stream_count() const;
class StreamReference
{
public:
StreamReference()
{
footage_ = nullptr;
type_ = Stream::kUnknown;
index_ = -1;
}
/**
* @brief Item::Type() override
*
* @return kFootage
*/
virtual Type type() const override;
StreamReference(const Footage* footage, Stream::Type type, int index)
{
footage_ = footage;
type_ = type;
index_ = index;
}
bool operator==(const StreamReference& rhs) const
{
return footage_ == rhs.footage_ && type_ == rhs.type_ && index_ == rhs.index_;
}
bool IsValid() const
{
return footage_ && index_ >= 0;
}
void Reset()
{
*this = StreamReference();
}
const Footage* footage() const
{
return footage_;
}
Stream::Type type() const
{
return type_;
}
int index() const
{
return index_;
}
Stream GetStream() const
{
if (IsValid()) {
return footage_->GetStreamAt(*this);
} else {
return Stream();
}
}
int64_t GetTimeInTimebaseUnits(const rational& timecode) const
{
if (IsValid()) {
return footage_->GetTimeInTimebaseUnits(type_, index_, timecode);
} else {
return -1;
}
}
int GetRealStreamIndex() const
{
if (IsValid()) {
return footage_->GetRealStreamIndex(*this);
} else {
return -1;
}
}
QString filename() const
{
if (footage_) {
return footage_->filename();
} else {
return QString();
}
}
VideoParams video_params() const
{
return GetStream().video_params();
}
AudioParams audio_params() const
{
return GetStream().audio_params();
}
int64_t duration() const
{
return GetStream().duration();
}
QString video_colorspace(bool default_if_empty = true) const;
private:
const Footage* footage_;
Stream::Type type_;
int index_;
};
Stream GetStreamAt(int index) const
{
return GetStandardValue(GetInputIDOfIndex(index)).value<Stream>();
}
Stream GetStreamAt(Stream::Type type, int index_within_type) const
{
return GetStreamAt(GetRealStreamIndex(type, index_within_type));
}
Stream GetStreamAt(const StreamReference& ref) const
{
return GetStreamAt(ref.type(), ref.index());
}
void SetStreamAt(int index, const Stream& stream)
{
SetStandardValue(GetInputIDOfIndex(index), QVariant::fromValue(stream));
}
void SetStreamAt(Stream::Type type, int index_within_type, const Stream& stream)
{
SetStreamAt(GetRealStreamIndex(type, index_within_type), stream);
}
void SetStreamAt(const StreamReference& ref, const Stream& stream)
{
SetStreamAt(ref.type(), ref.index(), stream);
}
int64_t GetTimeInTimebaseUnits(int index, const rational& time) const;
int64_t GetTimeInTimebaseUnits(Stream::Type type, int index_within_type, const rational& time) const
{
return GetTimeInTimebaseUnits(GetRealStreamIndex(type, index_within_type), time);
}
int GetRealStreamIndex(Stream::Type type, int index_within_type) const;
int GetRealStreamIndex(const StreamReference& ref) const
{
return GetRealStreamIndex(ref.type(), ref.index());
}
static QString GetStringFromReference(Stream::Type type, int index);
static QString GetStringFromReference(const StreamReference& ref)
{
return GetStringFromReference(ref.type(), ref.index());
}
StreamReference GetReferenceFromRealIndex(int real_index) const;
Stream::Type GetTypeFromOutput(const QString& output) const;
StreamReference GetReferenceFromOutput(const QString& s) const;
int GetStreamCount() const
{
return stream_count_;
}
int GetStreamTypeCount(Stream::Type type) const;
bool IsStreamEnabled(int index) const
{
return GetStreamAt(index).enabled();
}
Stream GetFirstEnabledStreamOfType(Stream::Type type) const;
QVector<int> GetStreamIndexesOfType(Stream::Type type) const;
Stream::Type GetStreamType(int index);
/**
* @brief Get the Decoder ID set when this Footage was probed
@@ -186,7 +331,7 @@ public:
*/
void set_decoder(const QString& id);
virtual QIcon icon() override;
virtual QIcon icon() const override;
virtual QString duration() override;
@@ -203,16 +348,38 @@ public:
*/
bool HasEnabledStreamsOfType(const Stream::Type& type) const;
Stream* get_first_enabled_stream_of_type(const Stream::Type& type) const;
static bool CompareFootageToFile(Footage* footage, const QString& filename);
static bool CompareFootageToItsFilename(Footage* footage);
private:
virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time) const override;
virtual NodeValueTable Value(const QString &output, NodeValueDatabase& value) const override;
static QString GetStreamTypeName(Stream::Type type);
static const QString kFilenameInput;
static const QString kStreamPropertiesFormat;
protected:
/**
* @brief Internal function to delete all Stream children and empty the array
* @brief Load function
*/
void ClearStreams();
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override;
/**
* @brief Save function
*/
virtual void SaveInternal(QXmlStreamWriter *writer) const override;
virtual void InputValueChangedEvent(const QString &input, int element) override;
private:
QString DescribeStream(int index) const;
struct MetadataCache {
QString decoder;
Streams streams;
};
/**
* @brief Update the icon based on the Footage status
@@ -230,30 +397,50 @@ private:
*/
void UpdateTooltip();
MetadataCache LoadStreamCache(const QString& filename);
bool SaveStreamCache(const QString& filename, const MetadataCache& data);
static QString GetInputIDOfIndex(int index)
{
return kStreamPropertiesFormat.arg(index);
}
/**
* @brief Internal filename string
* @brief List of dynamic inputs added for stream properties
*/
QString filename_;
QMap<int, QString> inputs_for_stream_properties_;
/**
* @brief List of dynamic outputs added for streams
*/
QMap<int, QString> outputs_for_streams_;
/**
* @brief Internal timestamp object
*/
qint64 timestamp_;
/**
* @brief Internal streams array
*/
QVector<Stream*> streams_;
/**
* @brief Internal attached decoder ID
*/
QString decoder_;
int stream_count_;
bool valid_;
const QAtomicInt* cancelled_;
private slots:
void CheckFootage();
};
uint qHash(const Footage::StreamReference& ref, uint seed = 0);
}
Q_DECLARE_METATYPE(olive::Footage::StreamReference)
#endif // FOOTAGE_H
+83 -138
View File
@@ -20,167 +20,112 @@
#include "stream.h"
#include "footage.h"
#include "ui/icons/icons.h"
#include "common/xmlutils.h"
namespace olive {
Stream::Stream() :
type_(kUnknown),
enabled_(true)
void Stream::Load(QXmlStreamReader *reader)
{
}
Stream::~Stream()
{
}
Stream *Stream::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
{
Stream* stream = nullptr;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("type")) {
Stream::Type type = static_cast<Stream::Type>(attr.value().toInt());
switch (type) {
case Stream::kVideo:
stream = new VideoStream();
break;
case Stream::kAudio:
stream = new AudioStream();
break;
default:
stream = new Stream();
stream->set_type(type);
break;
}
// This is the only attribute we need
*this = Stream(static_cast<Type>(attr.value().toInt()));
break;
}
}
if (!stream) {
return nullptr;
}
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("ptr")) {
//xml_node_data.footage_ptrs.insert(reader->readElementText().toULongLong(), stream);
} else if (reader->name() == QStringLiteral("index")) {
stream->set_index(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("timebase")) {
stream->set_timebase(rational::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("duration")) {
stream->set_duration(reader->readElementText().toLongLong());
} else if (reader->name() == QStringLiteral("enabled")) {
stream->set_enabled(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("custom")) {
stream->LoadCustomParameters(reader);
if (reader->name() == QStringLiteral("global")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("timebase")) {
timebase_ = rational::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("duration")) {
duration_ = reader->readElementText().toLongLong();
} else if (reader->name() == QStringLiteral("channelcount")) {
channel_count_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("enabled")) {
enabled_ = reader->readElementText().toInt();
} else {
reader->skipCurrentElement();
}
}
} else if (reader->name() == QStringLiteral("video")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("width")) {
width_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("height")) {
height_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("pixelaspectratio")) {
pixel_aspect_ratio_ = rational::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("videotype")) {
video_type_ = static_cast<VideoType>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("interlacing")) {
interlacing_ = static_cast<VideoParams::Interlacing>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("pixelformat")) {
pixel_format_ = static_cast<VideoParams::Format>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("framerate")) {
frame_rate_ = rational::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("starttime")) {
start_time_ = reader->readElementText().toLongLong();
} else if (reader->name() == QStringLiteral("premultipliedalpha")) {
premultiplied_alpha_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("colorspace")) {
colorspace_ = reader->readElementText();
} else {
reader->skipCurrentElement();
}
}
} else if (reader->name() == QStringLiteral("audio")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("samplerate")) {
sample_rate_ = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("channellayout")) {
channel_layout_ = reader->readElementText().toULongLong();
} else {
reader->skipCurrentElement();
}
}
} else {
reader->skipCurrentElement();
}
}
return stream;
}
void Stream::Save(QXmlStreamWriter *writer) const
{
writer->writeAttribute(QStringLiteral("type"), QString::number(type_));
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeStartElement(QStringLiteral("global"));
writer->writeTextElement(QStringLiteral("timebase"), timebase_.toString());
writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_));
writer->writeTextElement(QStringLiteral("channelcount"), QString::number(channel_count_));
writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_));
writer->writeEndElement(); // global
writer->writeTextElement(QStringLiteral("index"), QString::number(index_));
writer->writeStartElement(QStringLiteral("video"));
writer->writeTextElement(QStringLiteral("width"), QString::number(width_));
writer->writeTextElement(QStringLiteral("height"), QString::number(height_));
writer->writeTextElement(QStringLiteral("pixelaspectratio"), pixel_aspect_ratio_.toString());
writer->writeTextElement(QStringLiteral("videotype"), QString::number(video_type_));
writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_));
writer->writeTextElement(QStringLiteral("pixelformat"), QString::number(pixel_format_));
writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString());
writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_));
writer->writeTextElement(QStringLiteral("premultipliedalpha"), QString::number(premultiplied_alpha_));
writer->writeTextElement(QStringLiteral("colorspace"), colorspace_);
writer->writeEndElement(); // video
writer->writeTextElement(QStringLiteral("timebase"), timebase_.toString());
writer->writeTextElement(QStringLiteral("duration"), QString::number(duration_));
writer->writeTextElement(QStringLiteral("enabled"), QString::number(enabled_));
writer->writeStartElement(QStringLiteral("custom"));
SaveCustomParameters(writer);
writer->writeEndElement();
}
QString Stream::description() const
{
return QCoreApplication::translate("Stream", "%1: Unknown").arg(index());
}
const Stream::Type &Stream::type() const
{
return type_;
}
void Stream::set_type(const Stream::Type &type)
{
type_ = type;
}
Footage *Stream::footage() const
{
return dynamic_cast<Footage*>(parent());
}
const rational &Stream::timebase() const
{
return timebase_;
}
void Stream::set_timebase(const rational &timebase)
{
timebase_ = timebase;
}
const int &Stream::index() const
{
return index_;
}
void Stream::set_index(const int &index)
{
index_ = index;
}
const int64_t &Stream::duration() const
{
return duration_;
}
void Stream::set_duration(const int64_t &duration)
{
duration_ = duration;
emit ParametersChanged();
}
bool Stream::enabled() const
{
return enabled_;
}
void Stream::set_enabled(bool e)
{
enabled_ = e;
}
QIcon Stream::icon() const
{
return QIcon();
}
void Stream::LoadCustomParameters(QXmlStreamReader* reader)
{
reader->skipCurrentElement();
}
void Stream::SaveCustomParameters(QXmlStreamWriter*) const
{
writer->writeStartElement(QStringLiteral("audio"));
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(sample_rate_));
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(channel_layout_));
writer->writeEndElement(); // audio
}
}
+268 -69
View File
@@ -21,35 +21,20 @@
#ifndef STREAM_H
#define STREAM_H
#include <memory>
#include <QCoreApplication>
#include <QMutex>
#include <QString>
#include <QVector>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include "common/rational.h"
#include "ui/icons/icons.h"
#include "render/audioparams.h"
#include "render/videoparams.h"
namespace olive {
class Footage;
struct XMLNodeData;
/**
* @brief A base class for keeping metadata about a media stream.
*
* A Stream can contain video data, audio data, subtitle data,
* etc. and a Stream object stores metadata about it.
*
* The Stream class is fairly simple and is intended to be subclassed for data that pertains specifically to one
* Stream::Type. \see VideoStream and \see AudioStream.
*/
class Stream : public QObject
{
Q_OBJECT
class Stream {
public:
enum Type {
kUnknown,
kUnknown = -1,
kVideo,
kAudio,
kData,
@@ -57,69 +42,283 @@ public:
kAttachment
};
/**
* @brief Stream constructor
*/
Stream();
enum VideoType {
kVideoTypeVideo,
kVideoTypeStill,
kVideoTypeImageSequence
};
/**
* @brief Required virtual destructor, serves no purpose
*/
virtual ~Stream() override;
static Stream* Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled);
void Save(QXmlStreamWriter *writer) const;
virtual QString description() const;
const Type& type() const;
void set_type(const Type& type);
Footage* footage() const;
const rational& timebase() const;
void set_timebase(const rational& timebase);
const int& index() const;
void set_index(const int& index);
const int64_t& duration() const;
void set_duration(const int64_t& duration);
bool enabled() const;
void set_enabled(bool e);
virtual QIcon icon() const;
QMutex* mutex()
Stream(Type type = kUnknown) :
type_(type)
{
return &mutex_;
Init();
}
protected:
virtual void LoadCustomParameters(QXmlStreamReader *reader);
bool IsValid() const
{
return type_ != kUnknown;
}
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const;
Type type() const
{
return type_;
}
signals:
void ParametersChanged();
const rational& timebase() const
{
return timebase_;
}
void set_timebase(const rational& timebase)
{
timebase_ = timebase;
}
int64_t duration() const
{
return duration_;
}
void set_duration(int64_t duration)
{
duration_ = duration;
}
int channel_count() const
{
return channel_count_;
}
void set_channel_count(int c)
{
channel_count_ = c;
}
bool enabled() const
{
return enabled_;
}
void set_enabled(bool e)
{
enabled_ = e;
}
int width() const
{
return width_;
}
void set_width(int w)
{
width_ = w;
}
int height() const
{
return height_;
}
void set_height(int h)
{
height_ = h;
}
const rational& pixel_aspect_ratio() const
{
return pixel_aspect_ratio_;
}
void set_pixel_aspect_ratio(const rational& pixel_aspect_ratio)
{
pixel_aspect_ratio_ = pixel_aspect_ratio;
}
VideoType video_type() const
{
return video_type_;
}
void set_video_type(VideoType t)
{
video_type_ = t;
}
VideoParams::Interlacing interlacing() const
{
return interlacing_;
}
void set_interlacing(VideoParams::Interlacing interlacing)
{
interlacing_ = interlacing;
}
VideoParams::Format pixel_format() const
{
return pixel_format_;
}
void set_pixel_format(VideoParams::Format pixel_format)
{
pixel_format_ = pixel_format;
}
const rational& frame_rate() const
{
return frame_rate_;
}
void set_frame_rate(const rational& frame_rate)
{
frame_rate_ = frame_rate;
}
int64_t start_time() const
{
return start_time_;
}
void set_start_time(int64_t start_time)
{
start_time_ = start_time;
}
bool premultiplied_alpha() const
{
return premultiplied_alpha_;
}
void set_premultiplied_alpha(bool premultiplied_alpha)
{
premultiplied_alpha_ = premultiplied_alpha;
}
const QString& colorspace() const
{
return colorspace_;
}
void set_colorspace(const QString& c)
{
colorspace_ = c;
}
int sample_rate() const
{
return sample_rate_;
}
void set_sample_rate(int sample_rate)
{
sample_rate_ = sample_rate;
}
uint64_t channel_layout() const
{
return channel_layout_;
}
void set_channel_layout(uint64_t channel_layout)
{
channel_layout_ = channel_layout;
}
VideoParams video_params() const
{
if (type_ == kVideo) {
return VideoParams(width_, height_, timebase_,
pixel_format_, channel_count_, pixel_aspect_ratio_,
interlacing_);
} else {
return VideoParams();
}
}
AudioParams audio_params() const
{
if (type_ == kAudio) {
return AudioParams(sample_rate_, channel_layout_, AudioParams::kInternalFormat);
} else {
return AudioParams();
}
}
void Load(QXmlStreamReader* reader);
void Save(QXmlStreamWriter* writer) const;
QByteArray toBytes() const
{
QByteArray arr;
arr.append(reinterpret_cast<const char*>(&type_), sizeof(type_));
arr.append(reinterpret_cast<const char*>(&timebase_), sizeof(timebase_));
arr.append(reinterpret_cast<const char*>(&duration_), sizeof(duration_));
arr.append(reinterpret_cast<const char*>(&channel_count_), sizeof(channel_count_));
arr.append(reinterpret_cast<const char*>(&enabled_), sizeof(enabled_));
arr.append(reinterpret_cast<const char*>(&width_), sizeof(width_));
arr.append(reinterpret_cast<const char*>(&height_), sizeof(height_));
arr.append(reinterpret_cast<const char*>(&pixel_aspect_ratio_), sizeof(pixel_aspect_ratio_));
arr.append(reinterpret_cast<const char*>(&video_type_), sizeof(video_type_));
arr.append(reinterpret_cast<const char*>(&interlacing_), sizeof(interlacing_));
arr.append(reinterpret_cast<const char*>(&pixel_format_), sizeof(pixel_format_));
arr.append(reinterpret_cast<const char*>(&frame_rate_), sizeof(frame_rate_));
arr.append(reinterpret_cast<const char*>(&start_time_), sizeof(start_time_));
arr.append(reinterpret_cast<const char*>(&premultiplied_alpha_), sizeof(premultiplied_alpha_));
arr.append(colorspace_.toUtf8());
arr.append(reinterpret_cast<const char*>(&sample_rate_), sizeof(sample_rate_));
arr.append(reinterpret_cast<const char*>(&channel_layout_), sizeof(channel_layout_));
return arr;
}
private:
rational timebase_;
int64_t duration_;
int index_;
void Init()
{
duration_ = AV_NOPTS_VALUE;
channel_count_ = 0;
enabled_ = true;
width_ = 0;
height_ = 0;
video_type_ = VideoType::kVideoTypeVideo;
interlacing_ = VideoParams::kInterlaceNone;
pixel_format_ = VideoParams::kFormatInvalid;
start_time_ = 0;
premultiplied_alpha_ = false;
sample_rate_ = 0;
channel_layout_ = 0;
}
// Global members
Type type_;
rational timebase_;
int64_t duration_;
int channel_count_;
bool enabled_;
QMutex mutex_;
// Video members
int width_;
int height_;
rational pixel_aspect_ratio_;
VideoType video_type_;
VideoParams::Interlacing interlacing_;
VideoParams::Format pixel_format_;
rational frame_rate_;
int64_t start_time_;
bool premultiplied_alpha_;
QString colorspace_;
// Audio members
int sample_rate_;
uint64_t channel_layout_;
};
using Streams = QVector<Stream>;
}
Q_DECLARE_METATYPE(olive::Stream)
#endif // STREAM_H
-198
View File
@@ -1,198 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "videostream.h"
#include <QFile>
#include "common/timecodefunctions.h"
#include "common/xmlutils.h"
#include "footage.h"
#include "project/project.h"
#include "render/colormanager.h"
namespace olive {
VideoStream::VideoStream() :
premultiplied_alpha_(false),
interlacing_(VideoParams::kInterlaceNone),
video_type_(VideoStream::kVideoTypeVideo),
pixel_aspect_ratio_(1),
start_time_(0)
{
set_type(Stream::kVideo);
}
QString VideoStream::description() const
{
if (video_type_ == VideoStream::kVideoTypeStill) {
return QCoreApplication::translate("Stream", "%1: Image - %2x%3").arg(QString::number(index()),
QString::number(width()),
QString::number(height()));
} else {
return QCoreApplication::translate("Stream", "%1: Video - %2x%3").arg(QString::number(index()),
QString::number(width()),
QString::number(height()));
}
}
const rational &VideoStream::frame_rate() const
{
return frame_rate_;
}
void VideoStream::set_frame_rate(const rational &frame_rate)
{
frame_rate_ = frame_rate;
}
const int64_t &VideoStream::start_time() const
{
return start_time_;
}
void VideoStream::set_start_time(const int64_t &start_time)
{
start_time_ = start_time;
emit ParametersChanged();
}
int64_t VideoStream::get_time_in_timebase_units(const rational &time) const
{
return Timecode::time_to_timestamp(time, timebase()) + start_time();
}
QIcon VideoStream::icon() const
{
if (video_type_ == kVideoTypeStill) {
return icon::Image;
} else {
return icon::Video;
}
}
void VideoStream::LoadCustomParameters(QXmlStreamReader *reader)
{
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("width")) {
set_width(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("height")) {
set_height(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("premultiplied")) {
set_premultiplied_alpha(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("colorspace")) {
set_colorspace(reader->readElementText());
} else if (reader->name() == QStringLiteral("interlacing")) {
set_interlacing(static_cast<VideoParams::Interlacing>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("type")) {
set_video_type(static_cast<VideoType>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("format")) {
set_format(static_cast<VideoParams::Format>(reader->readElementText().toInt()));
} else if (reader->name() == QStringLiteral("channels")) {
set_channel_count(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("pixelaspect")) {
set_pixel_aspect_ratio(rational::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("framerate")) {
set_frame_rate(rational::fromString(reader->readElementText()));
} else if (reader->name() == QStringLiteral("starttime")) {
set_start_time(reader->readElementText().toLongLong());
} else {
reader->skipCurrentElement();
}
}
}
void VideoStream::SaveCustomParameters(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("width"), QString::number(width_));
writer->writeTextElement(QStringLiteral("height"), QString::number(height_));
writer->writeTextElement(QStringLiteral("premultiplied"), QString::number(premultiplied_alpha_));
writer->writeTextElement(QStringLiteral("colorspace"), colorspace_);
writer->writeTextElement(QStringLiteral("interlacing"), QString::number(interlacing_));
writer->writeTextElement(QStringLiteral("type"), QString::number(video_type_));
writer->writeTextElement(QStringLiteral("format"), QString::number(format_));
writer->writeTextElement(QStringLiteral("channels"), QString::number(channel_count_));
writer->writeTextElement(QStringLiteral("pixelaspect"), pixel_aspect_ratio_.toString());
writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString());
writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_));
}
bool VideoStream::premultiplied_alpha()
{
QMutexLocker locker(mutex());
return premultiplied_alpha_;
}
void VideoStream::set_premultiplied_alpha(bool e)
{
mutex()->lock();
premultiplied_alpha_ = e;
mutex()->unlock();
emit ParametersChanged();
}
const QString &VideoStream::colorspace(bool default_if_empty)
{
QMutexLocker locker(mutex());
if (colorspace_.isEmpty() && default_if_empty) {
return footage()->project()->color_manager()->GetDefaultInputColorSpace();
} else {
return colorspace_;
}
}
void VideoStream::set_colorspace(const QString &color)
{
mutex()->lock();
colorspace_ = color;
mutex()->unlock();
emit ParametersChanged();
}
void VideoStream::ColorConfigChanged()
{
ColorManager* color_manager = footage()->project()->color_manager();
// Check if this colorspace is in the new config
if (!colorspace_.isEmpty()) {
QStringList colorspaces = color_manager->ListAvailableColorspaces();
if (!colorspaces.contains(colorspace_)) {
// Set to empty if not
colorspace_.clear();
}
}
// Either way, the color calculation has likely changed so we signal here
emit ParametersChanged();
}
void VideoStream::DefaultColorSpaceChanged()
{
// If no colorspace is set, this stream uses the default color space and it's just changed
if (colorspace_.isEmpty()) {
emit ParametersChanged();
}
}
}
-179
View File
@@ -1,179 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2020 Olive Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef VIDEOSTREAM_H
#define VIDEOSTREAM_H
#include "render/videoparams.h"
#include "stream.h"
namespace olive {
/**
* @brief A Stream derivative containing video-specific information
*/
class VideoStream : public Stream
{
Q_OBJECT
public:
VideoStream();
enum VideoType {
kVideoTypeVideo,
kVideoTypeStill,
kVideoTypeImageSequence
};
virtual QString description() const override;
VideoType video_type() const
{
return video_type_;
}
void set_video_type(VideoType t)
{
video_type_ = t;
}
const int& width() const
{
return width_;
}
void set_width(const int& width)
{
width_ = width;
}
const int& height() const
{
return height_;
}
void set_height(const int& height)
{
height_ = height;
}
const VideoParams::Format& format() const
{
return format_;
}
void set_format(const VideoParams::Format& format)
{
format_ = format;
}
int channel_count() const
{
return channel_count_;
}
void set_channel_count(int c)
{
channel_count_ = c;
}
bool premultiplied_alpha();
void set_premultiplied_alpha(bool e);
const QString& colorspace(bool default_if_empty = true);
void set_colorspace(const QString& color);
VideoParams::Interlacing interlacing() const
{
return interlacing_;
}
void set_interlacing(VideoParams::Interlacing i)
{
interlacing_ = i;
emit ParametersChanged();
}
const rational& pixel_aspect_ratio() const
{
return pixel_aspect_ratio_;
}
void set_pixel_aspect_ratio(const rational& r)
{
// Auto-correct null aspect ratio to 1:1
if (r.isNull()) {
pixel_aspect_ratio_ = 1;
} else {
pixel_aspect_ratio_ = r;
}
emit ParametersChanged();
}
/**
* @brief Get this video stream's frame rate
*
* Used purely for metadata, rendering uses the timebase instead.
*/
const rational& frame_rate() const;
void set_frame_rate(const rational& frame_rate);
const int64_t& start_time() const;
void set_start_time(const int64_t& start_time);
int64_t get_time_in_timebase_units(const rational& time) const;
virtual QIcon icon() const override;
public slots:
void ColorConfigChanged();
void DefaultColorSpaceChanged();
protected:
virtual void LoadCustomParameters(QXmlStreamReader *reader) override;
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override;
private:
int width_;
int height_;
bool premultiplied_alpha_;
QString colorspace_;
VideoParams::Interlacing interlacing_;
VideoType video_type_;
VideoParams::Format format_;
int channel_count_;
rational pixel_aspect_ratio_;
rational frame_rate_;
int64_t start_time_;
};
}
#endif // VIDEOSTREAM_H
+13 -100
View File
@@ -20,31 +20,21 @@
#include "item.h"
#include "folder/folder.h"
namespace olive {
#define super QObject
Item::Item() :
item_parent_(nullptr),
project_(nullptr)
{
}
const QString Item::kParentInput = QStringLiteral("parent_in");
Item::~Item()
Item::Item(bool create_folder_input, bool create_default_output) :
Node(create_default_output)
{
setParent(nullptr);
}
const QString &Item::name() const
{
return name_;
}
void Item::set_name(const QString &n)
{
name_ = n;
NameChangedEvent(n);
if (create_folder_input) {
// Hierarchy input for items
AddInput(kParentInput, NodeValue::kNone);
}
}
const QString &Item::tooltip() const
@@ -67,91 +57,14 @@ QString Item::rate()
return QString();
}
Project *Item::project() const
Folder *Item::item_parent() const
{
return project_;
return dynamic_cast<Folder*>(GetConnectedNode(kParentInput));
}
void Item::set_project(Project *project)
void Item::Retranslate()
{
project_ = project;
foreach (Item* i, item_children_) {
i->set_project(project_);
}
}
QVector<Item *> Item::get_children_of_type(Type type, bool recursive) const
{
QVector<Item *> list;
foreach (Item* item, item_children_) {
if (item->type() == type) {
list.append(item);
}
if (recursive && item->CanHaveChildren()) {
list.append(item->get_children_of_type(type, recursive));
}
}
return list;
}
bool Item::CanHaveChildren() const
{
return false;
}
bool Item::ChildExistsWithName(const QString &name)
{
return ChildExistsWithNameInternal(name, this);
}
void Item::NameChangedEvent(const QString &)
{
}
void Item::childEvent(QChildEvent *event)
{
super::childEvent(event);
Item* cast_test = dynamic_cast<Item*>(event->child());
if (cast_test) {
if (event->type() == QEvent::ChildAdded) {
item_children_.append(cast_test);
cast_test->item_parent_ = this;
cast_test->set_project(project_);
} else if (event->type() == QEvent::ChildRemoved) {
item_children_.removeOne(cast_test);
cast_test->item_parent_ = nullptr;
cast_test->set_project(nullptr);
}
}
}
bool Item::ChildExistsWithNameInternal(const QString &name, Item *folder)
{
// Loop through all children
foreach (Item* child, folder->item_children_) {
// If this child has the same name, return true
if (child->name() == name) {
return true;
} else if (child->CanHaveChildren()) {
// If the child has children, run function recursively on this item
if (ChildExistsWithNameInternal(name, child)) {
// If it returns true, we've found a child so we can return now
return true;
}
}
}
return false;
SetInputName(kParentInput, tr("Folder"));
}
}
+7 -67
View File
@@ -30,10 +30,11 @@
#include "common/threadedobject.h"
#include "common/xmlutils.h"
#include "project/item/footage/stream.h"
#include "node/node.h"
namespace olive {
class Folder;
class Project;
/**
@@ -42,90 +43,29 @@ class Project;
* Project objects implement a parent-child hierarchy of Items that can be used throughout the Project. The Item class
* itself is abstract and will need to be subclassed to be used in a Project.
*/
class Item : public QObject
class Item : public Node
{
Q_OBJECT
public:
enum Type {
kFolder,
kFootage,
kSequence
};
/**
* @brief Item constructor
*/
Item();
/**
* @brief Required virtual Item destructor
*/
virtual ~Item() override;
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) = 0;
virtual void Save(QXmlStreamWriter* writer) const = 0;
virtual Type type() const = 0;
int item_child_count() const
{
return item_children_.size();
}
Item* item_child(int i) const
{
return item_children_.at(i);
}
const QVector<Item*>& children() const
{
return item_children_;
}
const QString& name() const;
void set_name(const QString& n);
Item(bool create_folder_input = true, bool create_default_output = true);
const QString& tooltip() const;
void set_tooltip(const QString& t);
virtual QIcon icon() = 0;
virtual QString duration();
virtual QString rate();
Item *item_parent() const
{
return item_parent_;
}
Folder *item_parent() const;
Project* project() const;
static const QString kParentInput;
void set_project(Project* project);
QVector<Item*> get_children_of_type(Type type, bool recursive) const;
virtual bool CanHaveChildren() const;
bool ChildExistsWithName(const QString& name);
protected:
virtual void NameChangedEvent(const QString& name);
virtual void childEvent(QChildEvent *event) override;
virtual void Retranslate() override;
private:
static bool ChildExistsWithNameInternal(const QString& name, Item* folder);
QVector<Item*> item_children_;
Item* item_parent_;
Project* project_;
QString name_;
QString tooltip_;
};
+346 -207
View File
@@ -20,7 +20,6 @@
#include "sequence.h"
#include <QCoreApplication>
#include <QThread>
#include "config/config.h"
@@ -38,192 +37,67 @@
namespace olive {
Sequence::Sequence()
{
viewer_output_ = new ViewerOutput();
viewer_output_->SetCanBeDeleted(false);
viewer_output_->setParent(this);
connect(viewer_output_, &ViewerOutput::LabelChanged, this, &Sequence::set_name);
}
const QString Sequence::kTextureInput = QStringLiteral("tex_in");
const QString Sequence::kSamplesInput = QStringLiteral("samples_in");
const QString Sequence::kTrackInputFormat = QStringLiteral("track_in_%1");
void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt *cancelled)
{
{
XMLAttributeLoop(reader, attr) {
if (cancelled && *cancelled) {
return;
}
#define super Item
if (attr.name() == QStringLiteral("name")) {
set_name(attr.value().toString());
} else if (attr.name() == QStringLiteral("ptr")) {
xml_node_data.item_ptrs.insert(attr.value().toULongLong(), this);
}
Sequence::Sequence(bool viewer_only_mode) :
Item(!viewer_only_mode, true),
video_frame_cache_(this),
audio_playback_cache_(this),
operation_stack_(0)
{
AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable));
if (!viewer_only_mode) {
// Create TrackList instances
track_lists_.resize(Track::kCount);
for (int i=0;i<Track::kCount;i++) {
// Create track input
QString track_input_id = kTrackInputFormat.arg(i);
AddInput(track_input_id, NodeValue::kNone, InputFlags(kInputFlagNotKeyframable | kInputFlagArray));
IgnoreInvalidationsFrom(track_input_id);
TrackList* list = new TrackList(this, static_cast<Track::Type>(i), track_input_id);
track_lists_.replace(i, list);
connect(list, &TrackList::TrackListChanged, this, &Sequence::UpdateTrackCache);
connect(list, &TrackList::LengthChanged, this, &Sequence::VerifyLength);
connect(list, &TrackList::TrackAdded, this, &Sequence::TrackAdded);
connect(list, &TrackList::TrackRemoved, this, &Sequence::TrackRemoved);
}
}
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("video")) {
int video_width = 0, video_height = 0, preview_div = 1;
rational video_timebase, video_pixel_aspect;
VideoParams::Interlacing video_interlacing = VideoParams::kInterlaceNone;
VideoParams::Format preview_format = VideoParams::kFormatInvalid;
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("width")) {
video_width = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("height")) {
video_height = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("timebase")) {
video_timebase = rational::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("divider")) {
preview_div = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("format")) {
preview_format = static_cast<VideoParams::Format>(reader->readElementText().toInt());
} else if (reader->name() == QStringLiteral("pixelaspect")) {
video_pixel_aspect = rational::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("interlacing")) {
video_interlacing = static_cast<VideoParams::Interlacing>(reader->readElementText().toInt());
} else {
reader->skipCurrentElement();
}
}
set_video_params(VideoParams(video_width, video_height, video_timebase, preview_format,
VideoParams::kInternalChannelCount, video_pixel_aspect,
video_interlacing, preview_div));
} else if (reader->name() == QStringLiteral("audio")) {
int rate = 0;
uint64_t layout = 0;
AudioParams::Format format = AudioParams::kFormatInvalid;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("rate")) {
rate = reader->readElementText().toInt();
} else if (reader->name() == QStringLiteral("layout")) {
layout = reader->readElementText().toULongLong();
} else if (reader->name() == QStringLiteral("format")) {
format = static_cast<AudioParams::Format>(reader->readElementText().toInt());
} else {
reader->skipCurrentElement();
}
}
set_audio_params(AudioParams(rate, layout, format));
} else if (reader->name() == QStringLiteral("points")) {
TimelinePoints::Load(reader);
} else if (reader->name() == QStringLiteral("node") || reader->name() == QStringLiteral("viewer")) {
Node* node;
if (reader->name() == QStringLiteral("node")) {
node = nullptr;
{
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
QString id = attr.value().toString();
node = NodeFactory::CreateFromID(id);
break;
}
}
}
} else {
node = viewer_output_;
}
if (node) {
node->Load(reader, xml_node_data, cancelled);
node->setParent(this);
}
} else {
reader->skipCurrentElement();
}
}
// Make connections
XMLConnectNodes(xml_node_data);
// Link blocks
XMLLinkBlocks(xml_node_data);
// Create UUID for this node
uuid_ = QUuid::createUuid();
}
void Sequence::Save(QXmlStreamWriter *writer) const
Sequence::~Sequence()
{
writer->writeAttribute(QStringLiteral("name"), name());
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeStartElement(QStringLiteral("video"));
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params().width()));
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params().height()));
writer->writeTextElement(QStringLiteral("timebase"), video_params().time_base().toString());
writer->writeTextElement(QStringLiteral("pixelaspect"), video_params().pixel_aspect_ratio().toString());
writer->writeTextElement(QStringLiteral("interlacing"), QString::number(video_params().interlacing()));
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params().divider()));
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params().format()));
writer->writeEndElement(); // video
writer->writeStartElement(QStringLiteral("audio"));
writer->writeTextElement(QStringLiteral("rate"), QString::number(audio_params().sample_rate()));
writer->writeTextElement(QStringLiteral("layout"), QString::number(audio_params().channel_layout()));
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params().format()));
writer->writeEndElement(); // audio
// Write TimelinePoints
writer->writeStartElement(QStringLiteral("points"));
TimelinePoints::Save(writer);
writer->writeEndElement(); // points
foreach (Node* node, nodes()) {
if (node != viewer_output_) {
writer->writeStartElement(QStringLiteral("node"));
writer->writeAttribute(QStringLiteral("id"), node->id());
node->Save(writer);
writer->writeEndElement(); // node;
}
}
writer->writeStartElement(QStringLiteral("viewer"));
writer->writeAttribute(QStringLiteral("id"), viewer_output_->id());
viewer_output_->Save(writer);
writer->writeEndElement(); // viewer;
DisconnectAll();
}
void Sequence::add_default_nodes()
void Sequence::add_default_nodes(MultiUndoCommand* command)
{
// Create tracks and connect them to the viewer
TimelineAddTrackCommand(viewer_output_->track_list(Track::kVideo)).redo();
TimelineAddTrackCommand(viewer_output_->track_list(Track::kAudio)).redo();
command->add_child(new TimelineAddTrackCommand(track_list(Track::kVideo)));
command->add_child(new TimelineAddTrackCommand(track_list(Track::kAudio)));
}
Item::Type Sequence::type() const
{
return kSequence;
}
QIcon Sequence::icon()
QIcon Sequence::icon() const
{
return icon::Sequence;
}
QString Sequence::duration()
{
rational timeline_length = viewer_output_->GetLength();
rational timeline_length = GetLength();
int64_t timestamp = Timecode::time_to_timestamp(timeline_length, video_params().time_base());
@@ -232,27 +106,7 @@ QString Sequence::duration()
QString Sequence::rate()
{
return QCoreApplication::translate("Sequence", "%1 FPS").arg(video_params().time_base().flipped().toDouble());
}
const VideoParams &Sequence::video_params() const
{
return viewer_output_->video_params();
}
void Sequence::set_video_params(const VideoParams &vparam)
{
viewer_output_->set_video_params(vparam);
}
const AudioParams &Sequence::audio_params() const
{
return viewer_output_->audio_params();
}
void Sequence::set_audio_params(const AudioParams &params)
{
viewer_output_->set_audio_params(params);
return tr("%1 FPS").arg(video_params().time_base().flipped().toDouble());
}
void Sequence::set_default_parameters()
@@ -279,45 +133,48 @@ void Sequence::set_parameters_from_footage(const QVector<Footage *> footage)
bool found_audio_params = false;
foreach (Footage* f, footage) {
foreach (Stream* s, f->streams()) {
if (!s->enabled()) {
for (int i=0; i<f->GetStreamCount(); i++) {
if (!f->IsStreamEnabled(i)) {
continue;
}
switch (s->type()) {
Stream s = f->GetStreamAt(i);
if (!s.IsValid()) {
continue;
}
switch (s.type()) {
case Stream::kVideo:
{
VideoStream* vs = static_cast<VideoStream*>(s);
// If this is a video stream, use these parameters
if (!found_video_params) {
rational using_timebase;
if (vs->video_type() == VideoStream::kVideoTypeStill) {
if (s.video_type() == Stream::kVideoTypeStill) {
// If this is a still image, we'll use it's resolution but won't set
// `found_video_params` in case something with a frame rate comes along which we'll
// prioritize
using_timebase = video_params().time_base();
} else {
using_timebase = vs->frame_rate().flipped();
using_timebase = s.frame_rate().flipped();
found_video_params = true;
}
set_video_params(VideoParams(vs->width(),
vs->height(),
set_video_params(VideoParams(s.width(),
s.height(),
using_timebase,
static_cast<VideoParams::Format>(Config::Current()["OfflinePixelFormat"].toInt()),
static_cast<VideoParams::Format>(Config::Current()[QStringLiteral("OfflinePixelFormat")].toInt()),
VideoParams::kInternalChannelCount,
vs->pixel_aspect_ratio(),
vs->interlacing(),
VideoParams::generate_auto_divider(vs->width(), vs->height())));
s.pixel_aspect_ratio(),
s.interlacing(),
VideoParams::generate_auto_divider(s.width(), s.height())));
}
break;
}
case Stream::kAudio:
if (!found_audio_params) {
AudioStream* as = static_cast<AudioStream*>(s);
set_audio_params(AudioParams(as->sample_rate(), as->channel_layout(), AudioParams::kInternalFormat));
set_audio_params(AudioParams(s.sample_rate(), s.channel_layout(), AudioParams::kInternalFormat));
found_audio_params = true;
}
break;
@@ -336,14 +193,296 @@ void Sequence::set_parameters_from_footage(const QVector<Footage *> footage)
}
}
ViewerOutput *Sequence::viewer_output() const
QVector<Track *> Sequence::GetUnlockedTracks() const
{
return viewer_output_;
QVector<Track*> tracks = GetTracks();
for (int i=0;i<tracks.size();i++) {
if (tracks.at(i)->IsLocked()) {
tracks.removeAt(i);
i--;
}
}
return tracks;
}
void Sequence::NameChangedEvent(const QString &name)
void Sequence::Retranslate()
{
viewer_output_->SetLabel(name);
super::Retranslate();
SetInputName(kTextureInput, tr("Texture"));
SetInputName(kSamplesInput, tr("Samples"));
for (int i=0;i<Track::kCount;i++) {
QString input_name;
switch (static_cast<Track::Type>(i)) {
case Track::kVideo:
input_name = tr("Video Tracks");
break;
case Track::kAudio:
input_name = tr("Audio Tracks");
break;
case Track::kSubtitle:
input_name = tr("Subtitle Tracks");
break;
case Track::kNone:
case Track::kCount:
break;
}
if (!input_name.isEmpty()) {
SetInputName(kTrackInputFormat.arg(i), input_name);
}
}
}
rational Sequence::GetCustomLength(Track::Type type) const
{
switch (type) {
case Track::kVideo:
return track_lists_.at(Track::kVideo)->GetTotalLength();
case Track::kAudio:
return track_lists_.at(Track::kAudio)->GetTotalLength();
case Track::kSubtitle:
return track_lists_.at(Track::kSubtitle)->GetTotalLength();
case Track::kNone:
case Track::kCount:
break;
}
return rational();
}
void Sequence::InputConnectedEvent(const QString &input, int element, const NodeOutput &output)
{
if (input == kTextureInput) {
emit TextureInputChanged();
} else {
foreach (TrackList* list, track_lists_) {
if (list->track_input() == input) {
// Return because we found our input
list->TrackConnected(output.node(), element);
return;
}
}
}
super::InputConnectedEvent(input, element, output);
}
void Sequence::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output)
{
if (input == kTextureInput) {
emit TextureInputChanged();
} else {
foreach (TrackList* list, track_lists_) {
if (list->track_input() == input) {
// Return because we found our input
list->TrackDisconnected(output.node(), element);
return;
}
}
}
super::InputDisconnectedEvent(input, element, output);
}
void Sequence::ShiftAudioEvent(const rational &from, const rational &to)
{
foreach (Track* track, track_lists_.at(Track::kAudio)->GetTracks()) {
track->waveform().Shift(from, to);
}
}
void Sequence::UpdateTrackCache()
{
track_cache_.clear();
foreach (TrackList* list, track_lists_) {
foreach (Track* track, list->GetTracks()) {
track_cache_.append(track);
}
}
}
void Sequence::ShiftVideoCache(const rational &from, const rational &to)
{
video_frame_cache_.Shift(from, to);
ShiftVideoEvent(from, to);
}
void Sequence::ShiftAudioCache(const rational &from, const rational &to)
{
audio_playback_cache_.Shift(from, to);
ShiftAudioEvent(from, to);
}
void Sequence::ShiftCache(const rational &from, const rational &to)
{
ShiftVideoCache(from, to);
ShiftAudioCache(from, to);
}
void Sequence::InvalidateCache(const TimeRange& range, const QString& from, int element)
{
Q_UNUSED(element)
if (operation_stack_ == 0) {
if (from == kTextureInput || from == kSamplesInput) {
TimeRange invalidated_range(qMax(rational(), range.in()),
qMin(GetLength(), range.out()));
if (invalidated_range.in() != invalidated_range.out()) {
if (from == kTextureInput) {
video_frame_cache_.Invalidate(invalidated_range);
} else {
audio_playback_cache_.Invalidate(invalidated_range);
}
}
}
VerifyLength();
}
super::InvalidateCache(range, from);
}
void Sequence::set_video_params(const VideoParams &video)
{
bool size_changed = video_params_.width() != video.width() || video_params_.height() != video.height();
bool timebase_changed = video_params_.time_base() != video.time_base();
bool pixel_aspect_changed = video_params_.pixel_aspect_ratio() != video.pixel_aspect_ratio();
bool interlacing_changed = video_params_.interlacing() != video.interlacing();
video_params_ = video;
if (size_changed) {
emit SizeChanged(video_params_.width(), video_params_.height());
}
if (pixel_aspect_changed) {
emit PixelAspectChanged(video_params_.pixel_aspect_ratio());
}
if (interlacing_changed) {
emit InterlacingChanged(video_params_.interlacing());
}
if (timebase_changed) {
video_frame_cache_.SetTimebase(video_params_.time_base());
emit TimebaseChanged(video_params_.time_base());
}
emit VideoParamsChanged();
video_frame_cache_.InvalidateAll();
}
void Sequence::set_audio_params(const AudioParams &audio)
{
audio_params_ = audio;
emit AudioParamsChanged();
// This will automatically InvalidateAll
audio_playback_cache_.SetParameters(audio_params());
}
rational Sequence::GetLength()
{
return last_length_;
}
void Sequence::VerifyLength()
{
if (operation_stack_ != 0) {
return;
}
NodeTraverser traverser;
rational video_length, audio_length, subtitle_length;
{
video_length = GetCustomLength(Track::kVideo);
if (video_length.isNull() && IsInputConnected(kTextureInput)) {
NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0));
video_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();
}
video_frame_cache_.SetLength(video_length);
}
{
audio_length = GetCustomLength(Track::kAudio);
if (audio_length.isNull() && IsInputConnected(kSamplesInput)) {
NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0));
audio_length = t.Get(NodeValue::kRational, QStringLiteral("length")).value<rational>();
}
audio_playback_cache_.SetLength(audio_length);
}
{
subtitle_length = GetCustomLength(Track::kSubtitle);
}
rational real_length = qMax(subtitle_length, qMax(video_length, audio_length));
if (real_length != last_length_) {
last_length_ = real_length;
emit LengthChanged(last_length_);
}
}
void Sequence::BeginOperation()
{
operation_stack_++;
super::BeginOperation();
}
void Sequence::EndOperation()
{
operation_stack_--;
super::EndOperation();
}
void Sequence::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled)
{
Q_UNUSED(xml_node_data)
Q_UNUSED(version)
Q_UNUSED(cancelled)
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("points")) {
timeline_points_.Load(reader);
} else {
reader->skipCurrentElement();
}
}
}
void Sequence::SaveInternal(QXmlStreamWriter *writer) const
{
// Write TimelinePoints
writer->writeStartElement(QStringLiteral("points"));
timeline_points_.Save(writer);
writer->writeEndElement(); // points
}
void Sequence::ShiftVideoEvent(const rational &from, const rational &to)
{
Q_UNUSED(from)
Q_UNUSED(to)
}
}
+169 -28
View File
@@ -21,12 +21,24 @@
#ifndef SEQUENCE_H
#define SEQUENCE_H
#include <QUuid>
#include "common/rational.h"
#include "node/block/block.h"
#include "node/graph.h"
#include "node/output/viewer/viewer.h"
#include "render/videoparams.h"
#include "project/item/footage/stream.h"
#include "node/node.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "node/traverser.h"
#include "project/item/footage/footage.h"
#include "project/item/item.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
#include "render/framehashcache.h"
#include "render/videoparams.h"
#include "render/videoparams.h"
#include "timeline/timelinecommon.h"
#include "timeline/timelinepoints.h"
#include "timeline/timelinepoints.h"
namespace olive {
@@ -34,51 +46,180 @@ namespace olive {
/**
* @brief The main timeline object, an graph of edited clips that forms a complete edit
*/
class Sequence : public NodeGraph, public TimelinePoints
class Sequence : public Item, public TimelinePoints
{
Q_OBJECT
public:
Sequence();
Sequence(bool viewer_only_mode = false);
/**
* @brief Load function
*/
virtual void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) override;
virtual ~Sequence() override;
/**
* @brief Save function
*/
virtual void Save(QXmlStreamWriter *writer) const override;
virtual Node* copy() const override
{
return new Sequence();
}
void add_default_nodes();
virtual QString Name() const override
{
return tr("Sequence");
}
/**
* @brief Item::Type() override
*/
virtual Type type() const override;
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.sequence");
}
virtual QIcon icon() override;
virtual QVector<CategoryID> Category() const override
{
return {kCategoryProject};
}
virtual QString Description() const override
{
return tr("A series of cuts that result in an edited video. Also called a timeline.");
}
void add_default_nodes(MultiUndoCommand *command);
virtual QIcon icon() const override;
virtual QString duration() override;
virtual QString rate() override;
const VideoParams &video_params() const;
void set_video_params(const VideoParams &vparam);
const AudioParams& audio_params() const;
void set_audio_params(const AudioParams& params);
void set_default_parameters();
void set_parameters_from_footage(const QVector<Footage *> footage);
ViewerOutput* viewer_output() const;
const QVector<Track *> &GetTracks() const
{
return track_cache_;
}
Track* GetTrackFromReference(const Track::Reference& track_ref) const
{
return track_lists_.at(track_ref.type())->GetTrackAt(track_ref.index());
}
/**
* @brief Same as GetTracks() but omits tracks that are locked.
*/
QVector<Track *> GetUnlockedTracks() const;
TrackList* track_list(Track::Type type) const
{
return track_lists_.at(type);
}
void ShiftVideoCache(const rational& from, const rational& to);
void ShiftAudioCache(const rational& from, const rational& to);
void ShiftCache(const rational& from, const rational& to);
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1) override;
const VideoParams& video_params() const
{
return video_params_;
}
const AudioParams& audio_params() const
{
return audio_params_;
}
void set_video_params(const VideoParams &video);
void set_audio_params(const AudioParams &audio);
rational GetLength();
virtual void Retranslate() override;
FrameHashCache* video_frame_cache()
{
return &video_frame_cache_;
}
AudioPlaybackCache* audio_playback_cache()
{
return &audio_playback_cache_;
}
virtual void BeginOperation() override;
virtual void EndOperation() override;
static const QString kTextureInput;
static const QString kSamplesInput;
static const QString kTrackInputFormat;
TimelinePoints* timeline_points()
{
return &timeline_points_;
}
const QUuid& uuid() const
{
return uuid_;
}
signals:
void TimebaseChanged(const rational&);
void LengthChanged(const rational& length);
void SizeChanged(int width, int height);
void PixelAspectChanged(const rational& pixel_aspect);
void InterlacingChanged(VideoParams::Interlacing mode);
void VideoParamsChanged();
void AudioParamsChanged();
void TrackAdded(Track* track);
void TrackRemoved(Track* track);
void TextureInputChanged();
public slots:
void VerifyLength();
protected:
virtual void NameChangedEvent(const QString& name) override;
virtual void InputConnectedEvent(const QString &input, int element, const NodeOutput &output) override;
virtual void InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output) override;
virtual rational GetCustomLength(Track::Type type) const;
virtual void ShiftVideoEvent(const rational &from, const rational &to);
virtual void ShiftAudioEvent(const rational &from, const rational &to);
virtual void LoadInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) override;
virtual void SaveInternal(QXmlStreamWriter *writer) const override;
private:
ViewerOutput* viewer_output_;
QVector<TrackList*> track_lists_;
QVector<Track*> track_cache_;
QUuid uuid_;
rational last_length_;
FrameHashCache video_frame_cache_;
AudioPlaybackCache audio_playback_cache_;
int operation_stack_;
VideoParams video_params_;
AudioParams audio_params_;
TimelinePoints timeline_points_;
private slots:
void UpdateTrackCache();
};
+47 -20
View File
@@ -26,6 +26,7 @@
#include "common/xmlutils.h"
#include "core.h"
#include "dialog/progress/progress.h"
#include "node/factory.h"
#include "render/diskmanager.h"
#include "window/mainwindow/mainwindow.h"
@@ -36,7 +37,6 @@ Project::Project() :
autorecovery_saved_(true)
{
root_.setParent(this);
root_.set_project(this);
connect(&color_manager_, &ColorManager::ConfigChanged,
this, &Project::ColorConfigChanged);
@@ -79,6 +79,38 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint
*layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data);
} else if (reader->name() == QStringLiteral("nodes")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
QString id;
{
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
id = attr.value().toString();
break;
}
}
}
if (id.isEmpty()) {
qWarning() << "Failed to load node with empty ID";
} else {
Node* node = NodeFactory::CreateFromID(id);
if (!node) {
qWarning() << "Failed to find node with ID" << id;
} else {
node->Load(reader, xml_node_data, version, cancelled);
node->setParent(this);
}
}
} else {
reader->skipCurrentElement();
}
}
} else {
// Skip this
@@ -86,6 +118,12 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint
}
}
// Make connections
XMLConnectNodes(xml_node_data);
// Link blocks
XMLLinkBlocks(xml_node_data);
}
void Project::Save(QXmlStreamWriter *writer) const
@@ -156,11 +194,6 @@ ColorManager *Project::color_manager()
return &color_manager_;
}
QVector<Item *> Project::get_items_of_type(Item::Type type) const
{
return root_.get_children_of_type(type, true);
}
bool Project::is_modified() const
{
return is_modified_;
@@ -199,27 +232,21 @@ const QString &Project::cache_path(bool default_if_empty) const
void Project::ColorConfigChanged()
{
QVector<Item*> footage = this->get_items_of_type(Item::kFootage);
QVector<Footage*> footage = root()->ListOutputsOfType<Footage>();
foreach (Item* item, footage) {
foreach (Stream* s, static_cast<Footage*>(item)->streams()) {
if (s->type() == Stream::kVideo) {
static_cast<VideoStream*>(s)->ColorConfigChanged();
}
}
foreach (Footage* item, footage) {
item->InvalidateAll(QString());
//static_cast<VideoStream*>(s)->ColorConfigChanged();
}
}
void Project::DefaultColorSpaceChanged()
{
QVector<Item*> footage = this->get_items_of_type(Item::kFootage);
QVector<Footage*> footage = root_.ListOutputsOfType<Footage>();
foreach (Item* item, footage) {
foreach (Stream* s, static_cast<Footage*>(item)->streams()) {
if (s->type() == Stream::kVideo) {
static_cast<VideoStream*>(s)->DefaultColorSpaceChanged();
}
}
foreach (Footage* item, footage) {
item->InvalidateAll(QString());
//static_cast<VideoStream*>(s)->DefaultColorSpaceChanged();
}
}
+1 -3
View File
@@ -41,7 +41,7 @@ namespace olive {
* * Project Settings
* * Window Layout
*/
class Project : public QObject
class Project : public NodeGraph
{
Q_OBJECT
public:
@@ -61,8 +61,6 @@ public:
ColorManager* color_manager();
QVector<Item*> get_items_of_type(Item::Type type) const;
bool is_modified() const;
void set_modified(bool e);
+120 -216
View File
@@ -25,7 +25,7 @@
#include <QUrl>
#include "core.h"
#include "node/input/media/media.h"
#include "widget/nodeview/nodeviewundo.h"
namespace olive {
@@ -48,8 +48,16 @@ void ProjectViewModel::set_project(Project *p)
{
beginResetModel();
if (project_) {
DisconnectItem(project_->root());
}
project_ = p;
if (project_) {
ConnectItem(project_->root());
}
endResetModel();
}
@@ -60,8 +68,8 @@ QModelIndex ProjectViewModel::index(int row, int column, const QModelIndex &pare
return QModelIndex();
}
// Get the parent object (project root if the index is invalid)
Item* item_parent = GetItemObjectFromIndex(parent);
// Get the parent object, we assume it's a folder since only folders can have children
Folder* item_parent = static_cast<Folder*>(GetItemObjectFromIndex(parent));
// Return an index to this object
return createIndex(row, column, item_parent->item_child(row));
@@ -103,7 +111,7 @@ int ProjectViewModel::rowCount(const QModelIndex &parent) const
}
// Otherwise, the index must contain a valid pointer, so we just return its child count
return GetItemObjectFromIndex(parent)->item_child_count();
return static_cast<Folder*>(GetItemObjectFromIndex(parent))->item_child_count();
}
int ProjectViewModel::columnCount(const QModelIndex &parent) const
@@ -131,7 +139,7 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
switch (column_type) {
case kName:
return internal_item->name();
return internal_item->GetLabel();
case kDuration:
return internal_item->duration();
case kRate:
@@ -175,20 +183,11 @@ QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation,
bool ProjectViewModel::hasChildren(const QModelIndex &parent) const
{
// Check if this is a valid index
if (parent.isValid()) {
Item* item = GetItemObjectFromIndex(parent);
// If it's a folder, we always return TRUE in order to always show the "expand triangle" icon,
// even when there are no "physical" children
Item* item = GetItemObjectFromIndex(parent);
// Check if this item is a kFolder type
// If it's a folder, we always return TRUE in order to always show the "expand triangle" icon,
// even when there are no "physical" children
if (item->CanHaveChildren()) {
return true;
}
}
// Otherwise, return default behavior
return QAbstractItemModel::hasChildren(parent);
return dynamic_cast<Folder*>(item);
}
bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, int role)
@@ -197,9 +196,11 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
if (index.isValid() && columns_.at(index.column()) == kName && role == Qt::EditRole) {
Item* item = GetItemObjectFromIndex(index);
RenameItemCommand* ric = new RenameItemCommand(this, item, value.toString());
NodeRenameCommand* nrc = new NodeRenameCommand();
Core::instance()->undo_stack()->push(ric);
nrc->AddNode(item, value.toString());
Core::instance()->undo_stack()->push(nrc);
return true;
}
@@ -209,20 +210,8 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
bool ProjectViewModel::canFetchMore(const QModelIndex &parent) const
{
// Check if this is a valid index
if (parent.isValid()) {
Item* item = GetItemObjectFromIndex(parent);
// Check if this item is a kFolder type
// If it's a folder, we always return TRUE in order to always show the "expand triangle" icon,
// even when there are no "physical" children
if (item->CanHaveChildren()) {
return true;
}
}
// Otherwise, return default behavior
return QAbstractItemModel::canFetchMore(parent);
// Use the same hack that always returns true with folders so the expand triangle is always visible
return hasChildren(parent);
}
Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const
@@ -234,7 +223,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const
Qt::ItemFlags f = Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index);
if (GetItemObjectFromIndex(index)->CanHaveChildren()) {
if (dynamic_cast<Folder*>(GetItemObjectFromIndex(index))) {
f |= Qt::ItemIsDropEnabled;
}
@@ -277,7 +266,7 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
// If not, add it to the stream (and also keep track of it in the vector)
quint64 stream_flags;
if (static_cast<Item*>(index.internalPointer())->type() == Item::kFootage) {
if (dynamic_cast<Folder*>(static_cast<Item*>(index.internalPointer()))) {
stream_flags = static_cast<Footage*>(index.internalPointer())->get_enabled_stream_flags();
} else {
stream_flags = UINT64_MAX;
@@ -320,7 +309,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
Item* drop_location = GetItemObjectFromIndex(drop);
// If this is not a folder, we cannot drop these items here
if (!drop_location->CanHaveChildren()) {
if (!dynamic_cast<Folder*>(drop_location)) {
return false;
}
@@ -342,8 +331,11 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
// Check if Item is already the drop location or if its parent is the drop location, in which case this is a
// no-op
if (item != drop_location && item->parent() != drop_location && !ItemIsParentOfChild(item, drop_location)) {
move_command->add_child(new MoveItemCommand(this, item, static_cast<Folder*>(drop_location)));
if (item != drop_location && item->item_parent() != drop_location && !ItemIsParentOfChild(item, drop_location)) {
NodeInput child_input(item, Item::kParentInput);
move_command->add_child(new NodeEdgeRemoveCommand(item->item_parent(), child_input));
move_command->add_child(new NodeEdgeAddCommand(static_cast<Folder*>(drop_location), child_input));
}
}
@@ -372,7 +364,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
Item* drop_item = GetItemObjectFromIndex(drop);
// If we didn't drop onto an item, find the nearest parent folder (should eventually terminate at root either way)
while (!drop_item->CanHaveChildren()) {
while (!dynamic_cast<Folder*>(drop_item)) {
drop_item = drop_item->item_parent();
}
@@ -383,83 +375,25 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
return false;
}
void ProjectViewModel::AddChild(Item *parent, Item *child)
{
QModelIndex parent_index;
if (parent != project_->root()) {
parent_index = CreateIndexFromItem(parent);
}
beginInsertRows(parent_index, parent->item_child_count(), parent->item_child_count());
child->setParent(parent);
endInsertRows();
}
void ProjectViewModel::RemoveChild(Item *parent, Item *child, QObject *new_parent)
{
QModelIndex parent_index;
if (parent != project_->root()) {
parent_index = CreateIndexFromItem(parent);
}
int child_row = IndexOfChild(child);
beginRemoveRows(parent_index, child_row, child_row);
child->setParent(new_parent);
endRemoveRows();
}
void ProjectViewModel::RenameChild(Item *item, const QString &name)
{
item->set_name(name);
QModelIndex index = CreateIndexFromItem(item, columns_.indexOf(kName));
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
}
int ProjectViewModel::IndexOfChild(Item *item) const
{
// Find parent's index within its own parent
// (FIXME: this model should handle sorting, which means it'll have to "know" the indices)
Folder* parent = item->item_parent();
if (item == project_->root()) {
return -1;
}
Item* parent = item->item_parent();
if (parent != nullptr) {
for (int i=0;i<parent->item_child_count();i++) {
if (parent->item_child(i) == item) {
return i;
}
}
if (parent) {
return parent->index_of_child(item);
}
return -1;
}
int ProjectViewModel::ChildCount(const QModelIndex &index)
{
Item* item = GetItemObjectFromIndex(index);
return item->item_child_count();
}
Item *ProjectViewModel::GetItemObjectFromIndex(const QModelIndex &index) const
{
if (index.isValid()) {
return static_cast<Item*>(index.internalPointer());
}
return project_->root();
return project_ ? project_->root() : nullptr;
}
bool ProjectViewModel::ItemIsParentOfChild(Item *parent, Item *child) const
@@ -476,18 +410,95 @@ bool ProjectViewModel::ItemIsParentOfChild(Item *parent, Item *child) const
return false;
}
void ProjectViewModel::MoveItemInternal(Item *item, Item *destination)
void ProjectViewModel::ConnectItem(Item *n)
{
QModelIndex item_index = CreateIndexFromItem(item);
connect(n, &Item::LabelChanged, this, &ProjectViewModel::ItemRenamed);
QModelIndex destination_index = CreateIndexFromItem(destination);
Folder* f = dynamic_cast<Folder*>(n);
if (f) {
connect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::FolderBeginInsertItem);
connect(f, &Folder::EndInsertItem, this, &ProjectViewModel::FolderEndInsertItem);
connect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem);
connect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem);
beginMoveRows(item_index.parent(), item_index.row(), item_index.row(),
destination_index, destination->item_child_count());
foreach (const Node::OutputConnection& c, f->output_connections()) {
Item* item = dynamic_cast<Item*>(c.second.node());
item->setParent(destination);
if (item) {
ConnectItem(item);
}
}
}
}
endMoveRows();
void ProjectViewModel::DisconnectItem(Item *n)
{
disconnect(n, &Item::LabelChanged, this, &ProjectViewModel::ItemRenamed);
Folder* f = dynamic_cast<Folder*>(n);
if (f) {
disconnect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::FolderBeginInsertItem);
disconnect(f, &Folder::EndInsertItem, this, &ProjectViewModel::FolderEndInsertItem);
disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::FolderBeginRemoveItem);
disconnect(f, &Folder::EndRemoveItem, this, &ProjectViewModel::FolderEndRemoveItem);
foreach (const Node::OutputConnection& c, f->output_connections()) {
Item* item = dynamic_cast<Item*>(c.second.node());
if (item) {
DisconnectItem(item);
}
}
}
}
void ProjectViewModel::FolderBeginInsertItem(Item *n, int insert_index)
{
Folder* folder = static_cast<Folder*>(sender());
ConnectItem(n);
QModelIndex index;
if (folder != project_->root()) {
index = CreateIndexFromItem(folder);
}
beginInsertRows(index, insert_index, insert_index);
}
void ProjectViewModel::FolderEndInsertItem()
{
endInsertRows();
}
void ProjectViewModel::FolderBeginRemoveItem(Item *n, int child_index)
{
Folder* folder = static_cast<Folder*>(sender());
DisconnectItem(n);
QModelIndex index;
if (folder != project_->root()) {
index = CreateIndexFromItem(folder);
}
beginRemoveRows(index, child_index, child_index);
}
void ProjectViewModel::FolderEndRemoveItem()
{
endRemoveRows();
}
void ProjectViewModel::ItemRenamed()
{
Item* item = static_cast<Item*>(sender());
QModelIndex index = CreateIndexFromItem(item);
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
}
QModelIndex ProjectViewModel::CreateIndexFromItem(Item *item, int column)
@@ -495,111 +506,4 @@ QModelIndex ProjectViewModel::CreateIndexFromItem(Item *item, int column)
return createIndex(IndexOfChild(item), column, item);
}
ProjectViewModel::MoveItemCommand::MoveItemCommand(ProjectViewModel *model,
Item *item,
Folder *destination) :
model_(model),
item_(item),
destination_(destination)
{
source_ = static_cast<Folder*>(item->parent());
set_name(QCoreApplication::translate("MoveItemCommand", "Move Item"));
}
Project *ProjectViewModel::MoveItemCommand::GetRelevantProject() const
{
return model_->project();
}
void ProjectViewModel::MoveItemCommand::redo()
{
model_->MoveItemInternal(item_, destination_);
}
void ProjectViewModel::MoveItemCommand::undo()
{
model_->MoveItemInternal(item_, source_);
}
ProjectViewModel::RenameItemCommand::RenameItemCommand(ProjectViewModel* model, Item *item, const QString &name) :
model_(model),
item_(item),
new_name_(name)
{
old_name_ = item->name();
set_name(QCoreApplication::translate("RenameItemCommand", "Rename Item"));
}
Project *ProjectViewModel::RenameItemCommand::GetRelevantProject() const
{
return model_->project();
}
void ProjectViewModel::RenameItemCommand::redo()
{
model_->RenameChild(item_, new_name_);
}
void ProjectViewModel::RenameItemCommand::undo()
{
model_->RenameChild(item_, old_name_);
}
ProjectViewModel::AddItemCommand::AddItemCommand(ProjectViewModel* model, Item* folder, Item* child) :
model_(model),
parent_(folder),
child_(child)
{
// Ensure all operations are done in folder's thread
if (memory_manager_.thread() != parent_->thread()) {
memory_manager_.moveToThread(parent_->thread());
}
child_->setParent(&memory_manager_);
}
Project *ProjectViewModel::AddItemCommand::GetRelevantProject() const
{
return model_->project();
}
void ProjectViewModel::AddItemCommand::redo()
{
model_->AddChild(parent_, child_);
}
void ProjectViewModel::AddItemCommand::undo()
{
model_->RemoveChild(parent_, child_, &memory_manager_);
}
ProjectViewModel::RemoveItemCommand::RemoveItemCommand(ProjectViewModel *model, Item *item) :
model_(model),
item_(item)
{
// Ensure all operations are done in folder's thread
parent_ = item_->item_parent();
if (memory_manager_.thread() != item_->thread()) {
memory_manager_.moveToThread(item_->thread());
}
}
Project *ProjectViewModel::RemoveItemCommand::GetRelevantProject() const
{
return model_->project();
}
void ProjectViewModel::RemoveItemCommand::redo()
{
model_->RemoveChild(parent_, item_, &memory_manager_);
}
void ProjectViewModel::RemoveItemCommand::undo()
{
model_->AddChild(parent_, item_);
}
}
+15 -110
View File
@@ -99,99 +99,11 @@ public:
virtual QMimeData * mimeData(const QModelIndexList &indexes) const override;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
/** Other model functions */
void AddChild(Item* parent, Item* child);
void RemoveChild(Item* parent, Item* child, QObject* new_parent);
void RenameChild(Item* item, const QString& name);
/**
* @brief Convenience function for creating QModelIndexes from an Item object
*/
QModelIndex CreateIndexFromItem(Item* item, int column = 0);
/**
* @brief An UndoCommand for moving an item from one folder to another folder
*/
class MoveItemCommand : public UndoCommand {
public:
MoveItemCommand(ProjectViewModel* model, Item* item, Folder* destination);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
ProjectViewModel* model_;
Item* item_;
Folder* source_;
Folder* destination_;
};
/**
* @brief An UndoCommand for renaming an item
*/
class RenameItemCommand : public UndoCommand {
public:
RenameItemCommand(ProjectViewModel* model, Item* item, const QString& name);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
ProjectViewModel* model_;
Item* item_;
QString old_name_;
QString new_name_;
};
/**
* @brief An UndoCommand for adding an item
*/
class AddItemCommand : public UndoCommand {
public:
AddItemCommand(ProjectViewModel* model, Item* folder, Item *child);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
ProjectViewModel* model_;
Item* parent_;
Item* child_;
QObject memory_manager_;
};
/**
* @brief An undo command for removing an item
*/
class RemoveItemCommand : public UndoCommand {
public:
RemoveItemCommand(ProjectViewModel* model, Item* item);
virtual Project* GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
ProjectViewModel* model_;
Item* item_;
Item* parent_;
QObject memory_manager_;
};
private:
/**
* @brief Retrieve the index of `item` in its parent
@@ -205,17 +117,6 @@ private:
*/
int IndexOfChild(Item* item) const;
/**
* @brief Get the child count of an index
*
* @param index
*
* @return
*
* Return number of children (immediate children only)
*/
int ChildCount(const QModelIndex& index);
/**
* @brief Retrieves the Item object from a given index
*
@@ -230,21 +131,25 @@ private:
*/
bool ItemIsParentOfChild(Item* parent, Item* child) const;
/**
* @brief Moves an item to a new destination updating all views in the process
*
* This function will emit a signal indicating that rows are moving, set `destination` as the new parent of `item`,
* and then emit a signal that the row has finished moving.
*
* It's not recommended to use this function directly in most cases since it does not create an UndoCommand allowing
* the user to undo the move. Instead this function should primarily be called from UndoCommands belonging to this
* class (e.g. MoveItemCommand).
*/
void MoveItemInternal(Item* item, Item* destination);
void ConnectItem(Item* n);
void DisconnectItem(Item* n);
Project* project_;
QVector<ColumnType> columns_;
private slots:
void FolderBeginInsertItem(Item* n, int insert_index);
void FolderEndInsertItem();
void FolderBeginRemoveItem(Item* n, int child_index);
void FolderEndRemoveItem();
void ItemRenamed();
};
}