project: improved loading and footage importing

This commit is contained in:
itsmattkc
2020-10-19 14:41:01 +11:00
parent 30e5a442a8
commit db8d321fa7
39 changed files with 630 additions and 329 deletions
+15 -5
View File
@@ -81,17 +81,27 @@ void Folder::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QA
void Folder::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement(QStringLiteral("folder"));
writer->writeAttribute(QStringLiteral("name"), name());
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
foreach (ItemPtr child, children()) {
child->Save(writer);
}
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;
}
writer->writeEndElement(); // folder
child->Save(writer);
writer->writeEndElement(); // footage/folder/sequence
}
}
OLIVE_NAMESPACE_EXIT
+24
View File
@@ -20,6 +20,8 @@
#include "audiostream.h"
#include "common/xmlutils.h"
OLIVE_NAMESPACE_ENTER
AudioStream::AudioStream()
@@ -101,4 +103,26 @@ 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_));
}
OLIVE_NAMESPACE_EXIT
+5
View File
@@ -55,6 +55,11 @@ public:
virtual QIcon icon() const override;
protected:
virtual void LoadCustomParameters(QXmlStreamReader *reader) override;
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override;
signals:
void ConformAppended(OLIVE_NAMESPACE::AudioParams params);
+49 -68
View File
@@ -24,6 +24,7 @@
#include <QDir>
#include "codec/decoder.h"
#include "common/filefunctions.h"
#include "common/xmlutils.h"
#include "config/config.h"
#include "core.h"
@@ -43,91 +44,45 @@ Footage::~Footage()
void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
{
/*
QXmlStreamAttributes attributes = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attributes) {
if (attr.name() == QStringLiteral("name")) {
set_name(attr.value().toString());
} else if (attr.name() == QStringLiteral("filename")) {
set_filename(attr.value().toString());
}
}
// Validate filename
if (!QFileInfo::exists(filename_)) {
// Absolute filename does not exist, use some heuristics to try relocating the file
if (xml_node_data.real_project_url != xml_node_data.saved_project_url) {
// Project path has changed, check if the file we're looking for is the same relative to the
// new project path
QDir saved_dir(QFileInfo(xml_node_data.saved_project_url).dir());
QDir true_dir(QFileInfo(xml_node_data.real_project_url).dir());
QString relative_filename = saved_dir.relativeFilePath(filename_);
QString transformed_abs_filename = true_dir.filePath(relative_filename);
if (QFileInfo::exists(transformed_abs_filename)) {
// Use this file instead
qInfo() << "Footage" << filename_ << "doesn't exist, using relative file" << transformed_abs_filename;
set_filename(transformed_abs_filename);
}
}
}
Decoder::ProbeMedia(this, cancelled);
while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
if (reader->name() == QStringLiteral("stream")) {
int stream_index = -1;
quintptr stream_ptr = 0;
XMLAttributeLoop(reader, attr) {
if (cancelled && *cancelled) {
return;
}
if (attr.name() == QStringLiteral("index")) {
stream_index = attr.value().toInt();
} else if (attr.name() == QStringLiteral("ptr")) {
stream_ptr = attr.value().toULongLong();
}
}
if (stream_index > -1 && stream_ptr > 0) {
xml_node_data.footage_ptrs.insert(stream_ptr, stream(stream_index));
stream(stream_index)->Load(reader);
} else {
qWarning() << "Invalid stream found in project file";
}
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")) {
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 {
reader->skipCurrentElement();
}
}
*/
}
void Footage::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement("footage");
writer->writeTextElement(QStringLiteral("name"), name());
writer->writeTextElement(QStringLiteral("filename"), filename());
writer->writeTextElement(QStringLiteral("timestamp"), QString::number(timestamp_));
writer->writeTextElement(QStringLiteral("decoder"), decoder_);
writer->writeAttribute("name", name());
writer->writeAttribute("filename", filename());
TimelinePoints::Save(writer);
writer->writeStartElement(QStringLiteral("points"));
TimelinePoints::Save(writer);
writer->writeEndElement(); // points
foreach (StreamPtr stream, streams_) {
stream->Save(writer);
writer->writeStartElement(QStringLiteral("stream"));
stream->Save(writer);
writer->writeEndElement(); // stream
}
writer->writeEndElement(); // footage
}
void Footage::Clear()
@@ -154,12 +109,12 @@ void Footage::set_filename(const QString &s)
filename_ = s;
}
const QDateTime &Footage::timestamp() const
const qint64 &Footage::timestamp() const
{
return timestamp_;
}
void Footage::set_timestamp(const QDateTime &t)
void Footage::set_timestamp(const qint64 &t)
{
timestamp_ = t;
}
@@ -341,6 +296,32 @@ StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const
return nullptr;
}
bool Footage::CompareFootageToItsFilename(FootagePtr footage)
{
// Heuristic to determine if file has changed
QFileInfo info(footage->filename());
if (info.exists()) {
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.
ItemPtr item = Decoder::ProbeMedia(footage->filename(), nullptr);
if (item && item->type() == footage->type()) {
// Item is the same type, that's a good sign. Let's look for any differences.
// FIXME: Implement this
return true;
}
}
}
// Footage file couldn't be found or resolved to something we didn't expect
return false;
}
void Footage::UpdateTooltip()
{
if (valid_) {
+8 -5
View File
@@ -32,6 +32,9 @@
OLIVE_NAMESPACE_ENTER
class Footage;
using FootagePtr = std::shared_ptr<Footage>;
/**
* @brief A reference to an external media file with metadata in a project structure
*
@@ -109,7 +112,7 @@ public:
* The file's last modified timestamp is stored for potential organization in the ProjectExplorer. It can be
* retrieved here.
*/
const QDateTime& timestamp() const;
const qint64 &timestamp() const;
/**
* @brief Set the last modified time/date
@@ -120,7 +123,7 @@ public:
*
* New last modified time/date
*/
void set_timestamp(const QDateTime& t);
void set_timestamp(const qint64 &t);
/**
* @brief Add a stream metadata object to this footage
@@ -199,6 +202,8 @@ public:
StreamPtr get_first_stream_of_type(const Stream::Type& type) const;
static bool CompareFootageToItsFilename(FootagePtr footage);
private:
/**
* @brief Internal function to delete all Stream children and empty the array
@@ -229,7 +234,7 @@ private:
/**
* @brief Internal timestamp object
*/
QDateTime timestamp_;
qint64 timestamp_;
/**
* @brief Internal streams array
@@ -245,8 +250,6 @@ private:
};
using FootagePtr = std::shared_ptr<Footage>;
OLIVE_NAMESPACE_EXIT
#endif // FOOTAGE_H
+55 -6
View File
@@ -37,22 +37,71 @@ Stream::~Stream()
{
}
void Stream::Load(QXmlStreamReader *reader)
StreamPtr Stream::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
{
LoadCustomParameters(reader);
StreamPtr stream;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("type")) {
Stream::Type type = static_cast<Stream::Type>(attr.value().toInt());
switch (type) {
case Stream::kVideo:
stream = std::make_shared<VideoStream>();
break;
case Stream::kAudio:
stream = std::make_shared<AudioStream>();
break;
default:
stream = std::make_shared<Stream>();
stream->set_type(type);
break;
}
// This is the only attribute we need
break;
}
}
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);
} else {
reader->skipCurrentElement();
}
}
return stream;
}
void Stream::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement("stream");
writer->writeAttribute(QStringLiteral("type"), QString::number(type_));
writer->writeAttribute("ptr", QString::number(reinterpret_cast<quintptr>(this)));
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
writer->writeAttribute("index", QString::number(index_));
writer->writeTextElement(QStringLiteral("index"), QString::number(index_));
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(); // stream
writer->writeEndElement();
}
QString Stream::description() const
+5 -4
View File
@@ -33,6 +33,9 @@
OLIVE_NAMESPACE_ENTER
class Footage;
class Stream;
using StreamPtr = std::shared_ptr<Stream>;
struct XMLNodeData;
/**
* @brief A base class for keeping metadata about a media stream.
@@ -64,9 +67,9 @@ public:
/**
* @brief Required virtual destructor, serves no purpose
*/
virtual ~Stream();
virtual ~Stream() override;
void Load(QXmlStreamReader* reader);
static StreamPtr Load(QXmlStreamReader* reader, XMLNodeData& xml_node_data, const QAtomicInt* cancelled);
void Save(QXmlStreamWriter *writer) const;
@@ -119,8 +122,6 @@ private:
};
using StreamPtr = std::shared_ptr<Stream>;
OLIVE_NAMESPACE_EXIT
#include <QMetaType>
+30 -14
View File
@@ -35,8 +35,7 @@ VideoStream::VideoStream() :
interlacing_(VideoParams::kInterlaceNone),
video_type_(VideoStream::kVideoTypeVideo),
pixel_aspect_ratio_(1),
start_time_(0),
is_image_sequence_(false)
start_time_(0)
{
set_type(Stream::kVideo);
}
@@ -75,16 +74,6 @@ void VideoStream::set_start_time(const int64_t &start_time)
emit ParametersChanged();
}
bool VideoStream::is_image_sequence() const
{
return is_image_sequence_;
}
void VideoStream::set_image_sequence(bool e)
{
is_image_sequence_ = e;
}
int64_t VideoStream::get_time_in_timebase_units(const rational &time) const
{
return Timecode::time_to_timestamp(time, timebase()) + start_time();
@@ -102,8 +91,26 @@ QIcon VideoStream::icon() const
void VideoStream::LoadCustomParameters(QXmlStreamReader *reader)
{
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("colorspace")) {
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<PixelFormat::Format>(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();
}
@@ -112,7 +119,16 @@ void VideoStream::LoadCustomParameters(QXmlStreamReader *reader)
void VideoStream::SaveCustomParameters(QXmlStreamWriter *writer) const
{
writer->writeTextElement("colorspace", colorspace_);
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("pixelaspect"), pixel_aspect_ratio_.toString());
writer->writeTextElement(QStringLiteral("framerate"), frame_rate_.toString());
writer->writeTextElement(QStringLiteral("starttime"), QString::number(start_time_));
}
bool VideoStream::premultiplied_alpha() const
-5
View File
@@ -132,9 +132,6 @@ public:
const int64_t& start_time() const;
void set_start_time(const int64_t& start_time);
bool is_image_sequence() const;
void set_image_sequence(bool e);
int64_t get_time_in_timebase_units(const rational& time) const;
virtual QIcon icon() const override;
@@ -166,8 +163,6 @@ private:
int64_t start_time_;
bool is_image_sequence_;
};
using VideoStreamPtr = std::shared_ptr<VideoStream>;
+19 -8
View File
@@ -121,7 +121,14 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const
Node* node;
if (reader->name() == QStringLiteral("node")) {
node = XMLLoadNode(reader);
node = nullptr;
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
node = NodeFactory::CreateFromID(attr.value().toString());
break;
}
}
} else {
node = viewer_output_;
}
@@ -143,7 +150,7 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const
XMLLinkBlocks(xml_node_data);
// Ensure this and all children are in the main thread
// (FIXME: Weird place for this? This should probably be in ProjectLoadManager somehow)
// NOTE: It might be good to move the Item system to QObjects so they inherit their thread
if (thread() != qApp->thread()) {
moveToThread(qApp->thread());
}
@@ -151,8 +158,6 @@ void Sequence::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const
void Sequence::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement(QStringLiteral("sequence"));
writer->writeAttribute(QStringLiteral("name"), name());
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
@@ -178,17 +183,23 @@ void Sequence::Save(QXmlStreamWriter *writer) const
writer->writeEndElement(); // audio
// Write TimelinePoints
TimelinePoints::Save(writer);
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;
}
}
viewer_output_->Save(writer, QStringLiteral("viewer"));
writer->writeEndElement(); // sequence
writer->writeStartElement(QStringLiteral("viewer"));
writer->writeAttribute(QStringLiteral("id"), viewer_output_->id());
viewer_output_->Save(writer);
writer->writeEndElement(); // viewer;
}
void Sequence::add_default_nodes()
+7 -20
View File
@@ -47,13 +47,9 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, const
{
XMLNodeData xml_node_data;
// Set project filename (hacky)
xml_node_data.real_project_url = static_cast<QFile*>(reader->device())->fileName();
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("folder")) {
if (reader->name() == QStringLiteral("root")) {
// Assume this folder is our root
root_.Load(reader, xml_node_data, cancelled);
} else if (reader->name() == QStringLiteral("colormanagement")) {
@@ -82,11 +78,6 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, const
*layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data);
} else if (reader->name() == QStringLiteral("url")) {
// This should be read in before most other elements
xml_node_data.saved_project_url = reader->readElementText();
} else {
// Skip this
@@ -104,27 +95,23 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, const
void Project::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement("project");
writer->writeTextElement("url", filename_);
writer->writeTextElement("cachepath", cache_path(false));
writer->writeTextElement(QStringLiteral("cachepath"), cache_path(false));
writer->writeStartElement(QStringLiteral("root"));
root_.Save(writer);
writer->writeEndElement();
writer->writeStartElement("colormanagement");
writer->writeStartElement(QStringLiteral("colormanagement"));
writer->writeTextElement("config", color_manager_.GetConfigFilename());
writer->writeTextElement(QStringLiteral("config"), color_manager_.GetConfigFilename());
writer->writeTextElement("default", color_manager_.GetDefaultInputColorSpace());
writer->writeTextElement(QStringLiteral("default"), color_manager_.GetDefaultInputColorSpace());
writer->writeEndElement(); // colormanagement
// Save main window project layout
MainWindowLayoutInfo main_window_info = Core::instance()->main_window()->SaveLayout();
main_window_info.toXml(writer);
writer->writeEndElement(); // project
}
Folder *Project::root()