project: improved loading and footage importing
This commit is contained in:
@@ -811,7 +811,7 @@ uint64_t FFmpegDecoder::ValidateChannelLayout(AVStream* stream)
|
||||
bool FFmpegDecoder::StreamUsesMultipleInstances(StreamPtr stream)
|
||||
{
|
||||
return stream->type() == Stream::kVideo
|
||||
&& !std::static_pointer_cast<VideoStream>(stream)->is_image_sequence();
|
||||
&& std::static_pointer_cast<VideoStream>(stream)->video_type() != VideoStream::kVideoTypeStill;
|
||||
}
|
||||
|
||||
FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height, const rational& ts, uint8_t** input_data, int* input_linesize)
|
||||
|
||||
+2
-50
@@ -26,49 +26,6 @@
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
Node* XMLLoadNode(QXmlStreamReader* reader)
|
||||
{
|
||||
QString node_id;
|
||||
quintptr node_ptr = 0;
|
||||
QPointF node_pos;
|
||||
QString node_label;
|
||||
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
node_id = attr.value().toString();
|
||||
} else if (attr.name() == QStringLiteral("ptr")) {
|
||||
node_ptr = attr.value().toULongLong();
|
||||
} else if (attr.name() == QStringLiteral("pos")) {
|
||||
QStringList pos = attr.value().toString().split(':');
|
||||
|
||||
// Protection in case this file has been messed with
|
||||
if (pos.size() == 2) {
|
||||
node_pos.setX(pos.at(0).toDouble());
|
||||
node_pos.setY(pos.at(1).toDouble());
|
||||
}
|
||||
} else if (attr.name() == QStringLiteral("label")) {
|
||||
node_label = attr.value().toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (node_id.isEmpty()) {
|
||||
qWarning() << "Found node with no ID";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Node* node = NodeFactory::CreateFromID(node_id);
|
||||
|
||||
if (node) {
|
||||
node->setProperty("xml_ptr", node_ptr);
|
||||
node->SetPosition(node_pos);
|
||||
node->SetLabel(node_label);
|
||||
} else {
|
||||
qWarning() << "Failed to load" << node_id << "- no node with that ID is installed";
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData &xml_node_data, QUndoCommand *command)
|
||||
{
|
||||
foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) {
|
||||
@@ -102,13 +59,8 @@ bool XMLReadNextStartElement(QXmlStreamReader *reader)
|
||||
|
||||
void XMLLinkBlocks(const XMLNodeData &xml_node_data)
|
||||
{
|
||||
foreach (const XMLNodeData::BlockLink& l1, xml_node_data.block_links) {
|
||||
foreach (const XMLNodeData::BlockLink& l2, xml_node_data.block_links) {
|
||||
if (l1.link == l2.block->property("xml_ptr")) {
|
||||
Block::Link(l1.block, l2.block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) {
|
||||
Block::Link(l.block, static_cast<Block*>(xml_node_data.node_ptrs.value(l.link)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ class Item;
|
||||
QXmlStreamAttributes __attributes = reader->attributes(); \
|
||||
foreach (const QXmlStreamAttribute& item, __attributes)
|
||||
|
||||
Node *XMLLoadNode(QXmlStreamReader* reader);
|
||||
|
||||
struct XMLNodeData {
|
||||
struct SerializedConnection {
|
||||
NodeInput* input;
|
||||
@@ -57,6 +55,7 @@ struct XMLNodeData {
|
||||
quintptr link;
|
||||
};
|
||||
|
||||
QHash<quintptr, Node*> node_ptrs;
|
||||
QHash<quintptr, NodeOutput*> output_ptrs;
|
||||
QList<SerializedConnection> desired_connections;
|
||||
QHash<quintptr, StreamPtr> footage_ptrs;
|
||||
@@ -64,9 +63,6 @@ struct XMLNodeData {
|
||||
QList<BlockLink> block_links;
|
||||
QHash<quintptr, Item*> item_ptrs;
|
||||
|
||||
QString real_project_url;
|
||||
QString saved_project_url;
|
||||
|
||||
};
|
||||
|
||||
void XMLConnectNodes(const XMLNodeData& xml_node_data, QUndoCommand* command = nullptr);
|
||||
|
||||
+56
-6
@@ -40,6 +40,7 @@
|
||||
#include "config/config.h"
|
||||
#include "dialog/about/about.h"
|
||||
#include "dialog/export/export.h"
|
||||
#include "dialog/footagerelink/footagerelinkdialog.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "dialog/task/task.h"
|
||||
#include "dialog/preferences/preferences.h"
|
||||
@@ -482,12 +483,14 @@ void Core::AddOpenProject(ProjectPtr p)
|
||||
|
||||
void Core::AddOpenProjectFromTask(Task *task)
|
||||
{
|
||||
QList<ProjectPtr> projects = static_cast<ProjectLoadTask*>(task)->GetLoadedProjects();
|
||||
QList<MainWindowLayoutInfo> layouts = static_cast<ProjectLoadTask*>(task)->GetLoadedLayouts();
|
||||
ProjectLoadTask* load_task = static_cast<ProjectLoadTask*>(task);
|
||||
|
||||
for (int i=0; i<projects.size(); i++) {
|
||||
AddOpenProject(projects.at(i));
|
||||
main_window_->LoadLayout(layouts.at(i));
|
||||
ProjectPtr project = load_task->GetLoadedProject();
|
||||
MainWindowLayoutInfo layout = load_task->GetLoadedLayout();
|
||||
|
||||
if (ValidateFootageInLoadedProject(project, load_task->GetFilenameProjectWasSavedAs())) {
|
||||
AddOpenProject(project);
|
||||
main_window_->LoadLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +563,7 @@ bool Core::StartHeadlessExport()
|
||||
CLITaskDialog task_dialog(&plm);
|
||||
|
||||
if (task_dialog.Run()) {
|
||||
ProjectPtr p = plm.GetLoadedProjects().first();
|
||||
ProjectPtr p = plm.GetLoadedProject();
|
||||
QList<ItemPtr> items = p->get_items_of_type(Item::kSequence);
|
||||
|
||||
// Check if this project contains sequences
|
||||
@@ -1222,6 +1225,53 @@ void Core::CacheActiveSequence(bool in_out_only)
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::ValidateFootageInLoadedProject(ProjectPtr project, const QString& project_saved_url)
|
||||
{
|
||||
QList<FootagePtr> footage_we_couldnt_validate;
|
||||
|
||||
QList<ItemPtr> project_footage = project->get_items_of_type(Item::kFootage);
|
||||
|
||||
foreach (ItemPtr item, project_footage) {
|
||||
FootagePtr footage = std::static_pointer_cast<Footage>(item);
|
||||
|
||||
if (!QFileInfo::exists(footage->filename())) {
|
||||
// If the footage doesn't exist, it might have moved with the project
|
||||
const QString& project_current_url = project->filename();
|
||||
|
||||
if (project_current_url != project_saved_url) {
|
||||
// Project has definitely moved, try to resolve relative paths
|
||||
QDir saved_dir(QFileInfo(project_saved_url).dir());
|
||||
QDir true_dir(QFileInfo(project_current_url).dir());
|
||||
|
||||
QString relative_filename = saved_dir.relativeFilePath(footage->filename());
|
||||
QString transformed_abs_filename = true_dir.filePath(relative_filename);
|
||||
|
||||
if (QFileInfo::exists(transformed_abs_filename)) {
|
||||
// Use this file instead
|
||||
qInfo() << "Resolved" << footage->filename() << "relatively to" << transformed_abs_filename;
|
||||
footage->set_filename(transformed_abs_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Heuristically compare footage to file
|
||||
if (Footage::CompareFootageToItsFilename(footage)) {
|
||||
footage->SetValid();
|
||||
} else {
|
||||
footage_we_couldnt_validate.append(footage);
|
||||
}
|
||||
}
|
||||
|
||||
if (!footage_we_couldnt_validate.isEmpty()) {
|
||||
FootageRelinkDialog frd(footage_we_couldnt_validate, main_window_);
|
||||
if (frd.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Core::CloseAllProjects()
|
||||
{
|
||||
return CloseAllProjects(true);
|
||||
|
||||
@@ -268,6 +268,11 @@ public:
|
||||
*/
|
||||
void CacheActiveSequence(bool in_out_only);
|
||||
|
||||
/**
|
||||
* @brief Check each footage object for whether it still exists or has changed
|
||||
*/
|
||||
bool ValidateFootageInLoadedProject(ProjectPtr project, const QString &project_saved_url);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Starts an open file dialog to load a project from file
|
||||
|
||||
@@ -20,6 +20,7 @@ add_subdirectory(color)
|
||||
add_subdirectory(diskcache)
|
||||
add_subdirectory(export)
|
||||
add_subdirectory(footageproperties)
|
||||
add_subdirectory(footagerelink)
|
||||
add_subdirectory(keyframeproperties)
|
||||
add_subdirectory(preferences)
|
||||
add_subdirectory(progress)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/footagerelink/footagerelinkdialog.h
|
||||
dialog/footagerelink/footagerelinkdialog.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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 "footagerelinkdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
FootageRelinkDialog::FootageRelinkDialog(const QList<FootagePtr>& footage, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
footage_(footage)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(new QLabel("The following files couldn't be found. Clips using them will be "
|
||||
"unplayable until they're relinked."));
|
||||
|
||||
table_ = new QTreeWidget();
|
||||
|
||||
table_->setColumnCount(3);
|
||||
table_->setHeaderLabels({tr("Footage"), tr("Filename"), tr("Actions")});
|
||||
table_->setRootIsDecorated(false);
|
||||
|
||||
for (int i=0; i<footage.size(); i++) {
|
||||
FootagePtr f = footage.at(i);
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem();
|
||||
|
||||
QWidget* item_actions = new QWidget();
|
||||
QHBoxLayout* item_actions_layout = new QHBoxLayout(item_actions);
|
||||
QPushButton* item_browse_btn = new QPushButton(tr("Browse"));
|
||||
item_browse_btn->setProperty("index", i);
|
||||
connect(item_browse_btn, &QPushButton::clicked, this, &FootageRelinkDialog::BrowseForFootage);
|
||||
item_actions_layout->addWidget(item_browse_btn);
|
||||
|
||||
item->setIcon(0, f->icon());
|
||||
item->setText(0, f->name());
|
||||
item->setText(1, f->filename());
|
||||
|
||||
table_->addTopLevelItem(item);
|
||||
|
||||
table_->setItemWidget(item, 2, item_actions);
|
||||
}
|
||||
|
||||
layout->addWidget(table_);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &FootageRelinkDialog::accept);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &FootageRelinkDialog::reject);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
setWindowTitle(tr("Relink Footage"));
|
||||
}
|
||||
|
||||
void FootageRelinkDialog::BrowseForFootage()
|
||||
{
|
||||
int index = sender()->property("index").toInt();
|
||||
FootagePtr f = footage_.at(index);
|
||||
|
||||
QFileInfo info(f->filename());
|
||||
|
||||
QString new_fn = QFileDialog::getOpenFileName(this,
|
||||
tr("Relink \"%1\"").arg(f->name()),
|
||||
info.absolutePath(),
|
||||
QStringLiteral("%1;;%2 (**)").arg(info.fileName(), tr("All Files")));
|
||||
|
||||
if (!new_fn.isEmpty()) {
|
||||
f->set_filename(new_fn);
|
||||
|
||||
if (Footage::CompareFootageToItsFilename(f)) {
|
||||
// Set footage to valid and update icon
|
||||
f->SetValid();
|
||||
|
||||
QTreeWidgetItem* item = table_->topLevelItem(index);
|
||||
item->setIcon(0, f->icon());
|
||||
item->setText(1, f->filename());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,49 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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 FOOTAGERELINKDIALOG_H
|
||||
#define FOOTAGERELINKDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include "project/item/footage/footage.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class FootageRelinkDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FootageRelinkDialog(const QList<FootagePtr>& footage, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QTreeWidget* table_;
|
||||
|
||||
QList<FootagePtr> footage_;
|
||||
|
||||
private slots:
|
||||
void BrowseForFootage();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // FOOTAGERELINKDIALOG_H
|
||||
@@ -220,10 +220,12 @@ rational Block::MediaToSequenceTime(const rational &media_time) const
|
||||
|
||||
void Block::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append({this, reader->readElementText().toULongLong()});
|
||||
} else {
|
||||
Node::LoadInternal(reader, xml_node_data);
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("link")) {
|
||||
xml_node_data.block_links.append({this, reader->readElementText().toULongLong()});
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -88,7 +88,7 @@ void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const
|
||||
}
|
||||
|
||||
if (attr.name() == QStringLiteral("keyframing")) {
|
||||
set_is_keyframing(attr.value() == QStringLiteral("1"));
|
||||
set_is_keyframing(attr.value().toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,16 +199,16 @@ void NodeInput::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const
|
||||
set_property(QStringLiteral("col_view"), reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("cslook")) {
|
||||
set_property(QStringLiteral("col_look"), reader->readElementText());
|
||||
} else {
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
LoadInternal(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeInput::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement("input");
|
||||
|
||||
writer->writeAttribute("id", id());
|
||||
|
||||
writer->writeAttribute("keyframing", QString::number(keyframing_));
|
||||
@@ -258,9 +258,9 @@ void NodeInput::Save(QXmlStreamWriter *writer) const
|
||||
|
||||
SaveConnections(writer);
|
||||
|
||||
writer->writeStartElement(QStringLiteral("custom"));
|
||||
SaveInternal(writer);
|
||||
|
||||
writer->writeEndElement(); // input
|
||||
writer->writeEndElement(); // custom
|
||||
}
|
||||
|
||||
void NodeInput::SaveConnections(QXmlStreamWriter *writer) const
|
||||
|
||||
+14
-10
@@ -187,17 +187,19 @@ void NodeInputArray::RemoveAt(int index)
|
||||
|
||||
void NodeInputArray::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("subparameters")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
Append();
|
||||
At(GetSize() - 1)->Load(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("subparameters")) {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("input")) {
|
||||
Append();
|
||||
At(GetSize() - 1)->Load(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
NodeInput::Load(reader, xml_node_data, cancelled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +208,9 @@ void NodeInputArray::SaveInternal(QXmlStreamWriter *writer) const
|
||||
writer->writeStartElement("subparameters");
|
||||
|
||||
foreach (NodeInput* sub, sub_params_) {
|
||||
sub->Save(writer);
|
||||
writer->writeStartElement(QStringLiteral("input"));
|
||||
sub->Save(writer);
|
||||
writer->writeEndElement();
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // subparameters
|
||||
|
||||
+42
-15
@@ -95,34 +95,61 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, const QAto
|
||||
}
|
||||
|
||||
param->Load(reader, xml_node_data, cancelled);
|
||||
} else {
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
|
||||
} else if (reader->name() == QStringLiteral("pos")) {
|
||||
QPointF p;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
p.setX(reader->readElementText().toDouble());
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
p.setY(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
SetPosition(p);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
SetLabel(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("custom")) {
|
||||
LoadInternal(reader, xml_node_data);
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::Save(QXmlStreamWriter *writer, const QString &custom_name) const
|
||||
void Node::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement(custom_name.isEmpty() ? QStringLiteral("node") : custom_name);
|
||||
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("id"), id());
|
||||
writer->writeStartElement(QStringLiteral("pos"));
|
||||
writer->writeTextElement(QStringLiteral("x"), QString::number(GetPosition().x()));
|
||||
writer->writeTextElement(QStringLiteral("y"), QString::number(GetPosition().y()));
|
||||
writer->writeEndElement(); // pos
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("pos"),
|
||||
QStringLiteral("%1:%2").arg(QString::number(GetPosition().x()),
|
||||
QString::number(GetPosition().y())));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("label"),
|
||||
GetLabel());
|
||||
writer->writeTextElement(QStringLiteral("label"), GetLabel());
|
||||
|
||||
foreach (NodeParam* param, parameters()) {
|
||||
switch (param->type()) {
|
||||
case NodeParam::kInput:
|
||||
writer->writeStartElement(QStringLiteral("input"));
|
||||
break;
|
||||
case NodeParam::kOutput:
|
||||
writer->writeStartElement(QStringLiteral("output"));
|
||||
break;
|
||||
}
|
||||
|
||||
param->Save(writer);
|
||||
|
||||
writer->writeEndElement(); // input/output
|
||||
}
|
||||
|
||||
writer->writeStartElement(QStringLiteral("custom"));
|
||||
SaveInternal(writer);
|
||||
|
||||
writer->writeEndElement(); // node
|
||||
writer->writeEndElement(); // custom
|
||||
}
|
||||
|
||||
QString Node::ShortName() const
|
||||
@@ -361,7 +388,7 @@ void Node::Hash(QCryptographicHash &hash, const rational& time) const
|
||||
hash.addData(stream->footage()->filename().toUtf8());
|
||||
|
||||
// Footage last modified date
|
||||
hash.addData(stream->footage()->timestamp().toString().toUtf8());
|
||||
hash.addData(QString::number(stream->footage()->timestamp()).toUtf8());
|
||||
|
||||
// Footage stream
|
||||
hash.addData(QString::number(stream->index()).toUtf8());
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ public:
|
||||
/**
|
||||
* @brief Save this node into a text/XML format
|
||||
*/
|
||||
void Save(QXmlStreamWriter* writer, const QString& custom_name = QString()) const;
|
||||
void Save(QXmlStreamWriter* writer) const;
|
||||
|
||||
/**
|
||||
* @brief Return the name of the node
|
||||
|
||||
@@ -63,13 +63,9 @@ void NodeOutput::Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, cons
|
||||
|
||||
void NodeOutput::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement("output");
|
||||
|
||||
writer->writeAttribute("id", id());
|
||||
|
||||
writer->writeAttribute("ptr", QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
writer->writeEndElement(); // output
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -111,12 +111,14 @@ void TrackOutput::SetTrackHeight(const double &height)
|
||||
emit TrackHeightChangedInPixels(GetTrackHeightInPixels());
|
||||
}
|
||||
|
||||
void TrackOutput::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data)
|
||||
void TrackOutput::LoadInternal(QXmlStreamReader *reader, XMLNodeData &)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("height")) {
|
||||
SetTrackHeight(reader->readElementText().toDouble());
|
||||
} else {
|
||||
Node::LoadInternal(reader, xml_node_data);
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("height")) {
|
||||
SetTrackHeight(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -212,10 +212,14 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N
|
||||
if (!got_cached_frame) {
|
||||
// Retrieve video frames
|
||||
foreach (const NodeValue& v, video_footage_to_retrieve) {
|
||||
QVariant value = ProcessVideoFootage(v.data().value<StreamPtr>(), range.in());
|
||||
StreamPtr stream = v.data().value<StreamPtr>();
|
||||
|
||||
if (!value.isNull()) {
|
||||
output_params.Push(NodeParam::kTexture, value, node);
|
||||
if (stream->footage()->IsValid()) {
|
||||
QVariant value = ProcessVideoFootage(stream, range.in());
|
||||
|
||||
if (!value.isNull()) {
|
||||
output_params.Push(NodeParam::kTexture, value, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,10 +244,14 @@ void NodeTraverser::PostProcessTable(const Node *node, const TimeRange &range, N
|
||||
|
||||
// Retrieve audio samples
|
||||
foreach (const NodeValue& v, audio_footage_to_retrieve) {
|
||||
QVariant value = ProcessAudioFootage(v.data().value<StreamPtr>(), range);
|
||||
StreamPtr stream = v.data().value<StreamPtr>();
|
||||
|
||||
if (!value.isNull()) {
|
||||
output_params.Push(NodeParam::kSamples, value, node);
|
||||
if (stream->footage()->IsValid()) {
|
||||
QVariant value = ProcessAudioFootage(v.data().value<StreamPtr>(), range);
|
||||
|
||||
if (!value.isNull()) {
|
||||
output_params.Push(NodeParam::kSamples, value, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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 ×tamp() 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -124,7 +124,7 @@ void ProjectImportTask::Import(Folder *folder, QFileInfoList import, int &counte
|
||||
FootagePtr footage = std::static_pointer_cast<Footage>(item);
|
||||
|
||||
footage->set_filename(file_path);
|
||||
footage->set_timestamp(file_info.lastModified());
|
||||
footage->set_timestamp(file_info.lastModified().toMSecsSinceEpoch());
|
||||
|
||||
// See if this footage is an image sequence
|
||||
ValidateImageSequence(footage, import, i);
|
||||
@@ -223,7 +223,6 @@ void ProjectImportTask::ValidateImageSequence(ItemPtr item, QFileInfoList& info_
|
||||
rational default_timebase = Config::Current()["DefaultSequenceFrameRate"].value<rational>();
|
||||
video_stream->set_timebase(default_timebase);
|
||||
video_stream->set_frame_rate(default_timebase.flipped());
|
||||
video_stream->set_image_sequence(true);
|
||||
|
||||
video_stream->set_start_time(start_index);
|
||||
video_stream->set_duration(end_index - start_index + 1);
|
||||
|
||||
@@ -46,22 +46,18 @@ bool ProjectLoadTask::Run()
|
||||
while(XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("version")) {
|
||||
qDebug() << "Project version:" << reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("url")) {
|
||||
project_saved_url_ = reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("project")) {
|
||||
ProjectPtr project = std::make_shared<Project>();
|
||||
project_ = std::make_shared<Project>();
|
||||
|
||||
project->set_filename(filename_);
|
||||
project_->set_filename(filename_);
|
||||
|
||||
MainWindowLayoutInfo layout;
|
||||
|
||||
project->Load(&reader, &layout, &IsCancelled());
|
||||
project_->Load(&reader, &layout_info_, &IsCancelled());
|
||||
|
||||
// Ensure project is in main thread
|
||||
project->moveToThread(qApp->thread());
|
||||
|
||||
if (!IsCancelled()) {
|
||||
projects_.append(project);
|
||||
layout_info_.append(layout);
|
||||
}
|
||||
project_->moveToThread(qApp->thread());
|
||||
break;
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
@@ -33,23 +33,35 @@ class ProjectLoadTask : public Task
|
||||
public:
|
||||
ProjectLoadTask(const QString& filename);
|
||||
|
||||
const QList<ProjectPtr>& GetLoadedProjects() const
|
||||
ProjectPtr GetLoadedProject() const
|
||||
{
|
||||
return projects_;
|
||||
return project_;
|
||||
}
|
||||
|
||||
const QList<MainWindowLayoutInfo>& GetLoadedLayouts() const
|
||||
MainWindowLayoutInfo GetLoadedLayout() const
|
||||
{
|
||||
return layout_info_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the filename the project was saved as, but not necessarily where it is now
|
||||
*
|
||||
* May help for resolving relative paths.
|
||||
*/
|
||||
const QString& GetFilenameProjectWasSavedAs() const
|
||||
{
|
||||
return project_saved_url_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
QList<ProjectPtr> projects_;
|
||||
ProjectPtr project_;
|
||||
|
||||
QList<MainWindowLayoutInfo> layout_info_;
|
||||
MainWindowLayoutInfo layout_info_;
|
||||
|
||||
QString project_saved_url_;
|
||||
|
||||
QString filename_;
|
||||
|
||||
|
||||
@@ -51,8 +51,14 @@ bool ProjectSaveTask::Run()
|
||||
|
||||
writer.writeTextElement("version", "0.2.0");
|
||||
|
||||
writer.writeTextElement("url", project_->filename());
|
||||
|
||||
writer.writeStartElement(QStringLiteral("project"));
|
||||
|
||||
project_->Save(&writer);
|
||||
|
||||
writer.writeEndElement(); // project
|
||||
|
||||
writer.writeEndElement(); // olive
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
@@ -55,8 +55,6 @@ void TimelineMarker::set_name(const QString &name)
|
||||
|
||||
void TimelineMarkerList::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("markers"));
|
||||
|
||||
foreach (TimelineMarker* marker, markers_) {
|
||||
writer->writeStartElement(QStringLiteral("marker"));
|
||||
|
||||
@@ -67,8 +65,6 @@ void TimelineMarkerList::Save(QXmlStreamWriter *writer) const
|
||||
|
||||
writer->writeEndElement(); // marker
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // markers
|
||||
}
|
||||
|
||||
TimelineMarkerList::~TimelineMarkerList()
|
||||
|
||||
@@ -54,13 +54,13 @@ void TimelinePoints::Load(QXmlStreamReader *reader)
|
||||
|
||||
void TimelinePoints::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("points"));
|
||||
writer->writeStartElement(QStringLiteral("workarea"));
|
||||
workarea_.Save(writer);
|
||||
writer->writeEndElement(); // workarea
|
||||
|
||||
workarea_.Save(writer);
|
||||
|
||||
markers_.Save(writer);
|
||||
|
||||
writer->writeEndElement(); // points
|
||||
writer->writeStartElement(QStringLiteral("markers"));
|
||||
markers_.Save(writer);
|
||||
writer->writeEndElement(); // markers
|
||||
}
|
||||
|
||||
TimelineWorkArea *TimelinePoints::workarea()
|
||||
|
||||
@@ -81,13 +81,9 @@ void TimelineWorkArea::Load(QXmlStreamReader *reader)
|
||||
|
||||
void TimelineWorkArea::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("workarea"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea_enabled_));
|
||||
writer->writeAttribute(QStringLiteral("in"), workarea_range_.in().toString());
|
||||
writer->writeAttribute(QStringLiteral("out"), workarea_range_.out().toString());
|
||||
|
||||
writer->writeEndElement(); // workarea
|
||||
}
|
||||
|
||||
const rational &TimelineWorkArea::in() const
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/factory.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
@@ -39,10 +40,15 @@ void NodeCopyPasteWidget::CopyNodesToClipboard(const QList<Node *> &nodes, void
|
||||
writer.writeStartElement(QStringLiteral("olive"));
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
writer.writeStartElement(QStringLiteral("node"));
|
||||
writer.writeAttribute(QStringLiteral("id"), n->id());
|
||||
n->Save(&writer);
|
||||
writer.writeEndElement(); // node
|
||||
}
|
||||
|
||||
writer.writeStartElement(QStringLiteral("custom"));
|
||||
CopyNodesToClipboardInternal(&writer, userdata);
|
||||
writer.writeEndElement(); // custom
|
||||
|
||||
writer.writeEndElement(); // olive
|
||||
writer.writeEndDocument();
|
||||
@@ -67,15 +73,24 @@ QList<Node *> NodeCopyPasteWidget::PasteNodesFromClipboard(Sequence *graph, QUnd
|
||||
if (reader.name() == QStringLiteral("olive")) {
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("node")) {
|
||||
Node* node = XMLLoadNode(&reader);
|
||||
Node* node = nullptr;
|
||||
|
||||
XMLAttributeLoop((&reader), attr) {
|
||||
if (attr.name() == QStringLiteral("id")) {
|
||||
node = NodeFactory::CreateFromID(attr.value().toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node->Load(&reader, xml_node_data, nullptr);
|
||||
|
||||
pasted_nodes.append(node);
|
||||
}
|
||||
} else if (reader.name() == QStringLiteral("custom")) {
|
||||
PasteNodesFromClipboardInternal(&reader, xml_node_data, userdata);
|
||||
} else {
|
||||
PasteNodesFromClipboardInternal(&reader, userdata);
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -157,7 +172,7 @@ void NodeCopyPasteWidget::CopyNodesToClipboardInternal(QXmlStreamWriter*, void*)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeCopyPasteWidget::PasteNodesFromClipboardInternal(QXmlStreamReader* reader, void*)
|
||||
void NodeCopyPasteWidget::PasteNodesFromClipboardInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, void*)
|
||||
{
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ protected:
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata);
|
||||
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void* userdata);
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -285,8 +285,6 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n)
|
||||
|
||||
void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata)
|
||||
{
|
||||
writer->writeStartElement(QStringLiteral("timeline"));
|
||||
|
||||
// Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere
|
||||
QList<TimelineViewBlockItem*>& selected = *static_cast<QList<TimelineViewBlockItem*>*>(userdata);
|
||||
rational earliest_in = RATIONAL_MAX;
|
||||
@@ -314,38 +312,32 @@ void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void
|
||||
|
||||
writer->writeEndElement();
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // timeline
|
||||
}
|
||||
|
||||
void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void *userdata)
|
||||
void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData& xml_node_data, void *userdata)
|
||||
{
|
||||
if (reader->name() == QStringLiteral("timeline")) {
|
||||
QList<BlockPasteData>& paste_data = *static_cast<QList<BlockPasteData>*>(userdata);
|
||||
QList<BlockPasteData>& paste_data = *static_cast<QList<BlockPasteData>*>(userdata);
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("block")) {
|
||||
BlockPasteData bpd;
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("block")) {
|
||||
BlockPasteData bpd;
|
||||
|
||||
foreach (QXmlStreamAttribute attr, reader->attributes()) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
bpd.ptr = attr.value().toULongLong();
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
bpd.in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("tracktype")) {
|
||||
bpd.track_type = static_cast<Timeline::TrackType>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("trackindex")) {
|
||||
bpd.track_index = attr.value().toInt();
|
||||
}
|
||||
foreach (QXmlStreamAttribute attr, reader->attributes()) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
bpd.block = static_cast<Block*>(xml_node_data.node_ptrs.value(attr.value().toULongLong()));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
bpd.in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("tracktype")) {
|
||||
bpd.track_type = static_cast<Timeline::TrackType>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("trackindex")) {
|
||||
bpd.track_index = attr.value().toInt();
|
||||
}
|
||||
|
||||
paste_data.append(bpd);
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
|
||||
paste_data.append(bpd);
|
||||
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
} else {
|
||||
NodeCopyPasteWidget::PasteNodesFromClipboardInternal(reader, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,12 +673,7 @@ void TimelineWidget::Paste(bool insert)
|
||||
rational paste_end = GetTime();
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
foreach (Node* n, pasted) {
|
||||
if (n->property("xml_ptr") == bpd.ptr) {
|
||||
paste_end = qMax(paste_end, paste_start + bpd.in + static_cast<Block*>(n)->length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
paste_end = qMax(paste_end, paste_start + bpd.in + bpd.block->length());
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
@@ -695,17 +682,12 @@ void TimelineWidget::Paste(bool insert)
|
||||
}
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
foreach (Node* n, pasted) {
|
||||
if (n->property("xml_ptr") == bpd.ptr) {
|
||||
qDebug() << "Placing" << n;
|
||||
new TrackPlaceBlockCommand(GetConnectedNode()->track_list(bpd.track_type),
|
||||
bpd.track_index,
|
||||
static_cast<Block*>(n),
|
||||
paste_start + bpd.in,
|
||||
command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "Placing" << bpd.block;
|
||||
new TrackPlaceBlockCommand(GetConnectedNode()->track_list(bpd.track_type),
|
||||
bpd.track_index,
|
||||
bpd.block,
|
||||
paste_start + bpd.in,
|
||||
command);
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
@@ -119,10 +119,10 @@ protected:
|
||||
virtual void DisconnectNodeInternal(ViewerOutput* n) override;
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override;
|
||||
|
||||
struct BlockPasteData {
|
||||
quintptr ptr;
|
||||
Block* block;
|
||||
rational in;
|
||||
Timeline::TrackType track_type;
|
||||
int track_index;
|
||||
|
||||
Reference in New Issue
Block a user