moved several files around

Mostly moved classes that became node derivatives into the node/ folder.
This commit is contained in:
itsmattkc
2021-04-08 12:15:59 +10:00
parent 2791e8d738
commit cb75e37e77
115 changed files with 159 additions and 128 deletions
+1
View File
@@ -16,6 +16,7 @@
add_subdirectory(audio)
add_subdirectory(block)
add_subdirectory(color)
add_subdirectory(distort)
add_subdirectory(filter)
add_subdirectory(generator)
+2
View File
@@ -31,6 +31,8 @@ class PanNode : public Node
public:
PanNode();
NODE_DEFAULT_DESTRUCTOR(PanNode)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class VolumeNode : public MathNodeBase
public:
VolumeNode();
NODE_DEFAULT_DESTRUCTOR(VolumeNode)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -37,6 +37,8 @@ class Block : public Node
public:
Block();
NODE_DEFAULT_DESTRUCTOR(Block)
enum Type {
kClip,
kGap,
+2
View File
@@ -34,6 +34,8 @@ class ClipBlock : public Block
public:
ClipBlock();
NODE_DEFAULT_DESTRUCTOR(ClipBlock)
virtual Node* copy() const override;
virtual Type type() const override;
+2
View File
@@ -34,6 +34,8 @@ class GapBlock : public Block
public:
GapBlock();
NODE_DEFAULT_DESTRUCTOR(GapBlock)
virtual Node * copy() const override;
virtual Type type() const override;
@@ -31,6 +31,8 @@ class CrossDissolveTransition : public TransitionBlock
public:
CrossDissolveTransition();
NODE_DEFAULT_DESTRUCTOR(CrossDissolveTransition)
virtual Node* copy() const override;
virtual QString Name() const override;
@@ -31,6 +31,8 @@ class DipToColorTransition : public TransitionBlock
public:
DipToColorTransition();
NODE_DEFAULT_DESTRUCTOR(DipToColorTransition)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class TransitionBlock : public Block
public:
TransitionBlock();
NODE_DEFAULT_DESTRUCTOR(TransitionBlock)
virtual Type type() const override;
virtual void Retranslate() override;
+22
View File
@@ -0,0 +1,22 @@
# 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/>.
add_subdirectory(colormanager)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
PARENT_SCOPE
)
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2020 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/color/colormanager/colormanager.cpp
node/color/colormanager/colormanager.h
PARENT_SCOPE
)
@@ -0,0 +1,307 @@
/***
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 "colormanager.h"
#include <QDir>
#include <QFloat16>
#include <QStandardPaths>
#include "common/define.h"
#include "common/filefunctions.h"
#include "config/config.h"
#include "core.h"
namespace olive {
const QString ColorManager::kConfigFilenameIn = QStringLiteral("config");
const QString ColorManager::kDefaultColorspaceIn = QStringLiteral("default_input");
const QString ColorManager::kReferenceSpaceIn = QStringLiteral("reference_space");
OCIO::ConstConfigRcPtr ColorManager::default_config_;
ColorManager::ColorManager() :
config_(nullptr)
{
// Filename input
AddInput(kConfigFilenameIn, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
// Colorspace input
AddInput(kDefaultColorspaceIn, NodeValue::kCombo, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
// Default reference space is scene linear
AddInput(kReferenceSpaceIn, NodeValue::kCombo, 0, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
// Set config to our built-in default
SetConfig(GetDefaultConfig());
SetDefaultInputColorSpace(QStringLiteral("sRGB OETF"));
}
OCIO::ConstConfigRcPtr ColorManager::GetConfig() const
{
return config_;
}
OCIO::ConstConfigRcPtr ColorManager::CreateConfigFromFile(const QString &filename)
{
OCIO_SET_C_LOCALE_FOR_SCOPE;
return OCIO::Config::CreateFromFile(filename.toUtf8());
}
QString ColorManager::GetConfigFilename() const
{
return GetStandardValue(kConfigFilenameIn).toString();
}
OCIO::ConstConfigRcPtr ColorManager::GetDefaultConfig()
{
return default_config_;
}
void ColorManager::SetUpDefaultConfig()
{
if (!qgetenv("OCIO").isEmpty()) {
// Attempt to set config from "OCIO" environment variable
try {
OCIO_SET_C_LOCALE_FOR_SCOPE;
default_config_ = OCIO::Config::CreateFromEnv();
return;
} catch (OCIO::Exception& e) {
qWarning() << "Failed to load config from OCIO environment variable config:" << e.what();
}
}
// Extract OCIO config - kind of hacky, but it'll work
QString dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(QStringLiteral("ocioconf"));
FileFunctions::CopyDirectory(QStringLiteral(":/ocioconf"),
dir,
true);
qDebug() << "Extracting default OCIO config to" << dir;
{
OCIO_SET_C_LOCALE_FOR_SCOPE;
default_config_ = CreateConfigFromFile(QDir(dir).filePath(QStringLiteral("config.ocio")));
}
}
void ColorManager::SetConfigFilename(const QString &filename)
{
SetStandardValue(kConfigFilenameIn, filename);
}
QStringList ColorManager::ListAvailableDisplays()
{
QStringList displays;
int number_of_displays = config_->getNumDisplays();
for (int i=0;i<number_of_displays;i++) {
displays.append(config_->getDisplay(i));
}
return displays;
}
QString ColorManager::GetDefaultDisplay()
{
return config_->getDefaultDisplay();
}
QStringList ColorManager::ListAvailableViews(QString display)
{
QStringList views;
int number_of_views = config_->getNumViews(display.toUtf8());
for (int i=0;i<number_of_views;i++) {
views.append(config_->getView(display.toUtf8(), i));
}
return views;
}
QString ColorManager::GetDefaultView(const QString &display)
{
return config_->getDefaultView(display.toUtf8());
}
QStringList ColorManager::ListAvailableLooks()
{
QStringList looks;
int number_of_looks = config_->getNumLooks();
for (int i=0;i<number_of_looks;i++) {
looks.append(config_->getLookNameByIndex(i));
}
return looks;
}
QStringList ColorManager::ListAvailableColorspaces() const
{
return ListAvailableColorspaces(config_);
}
QString ColorManager::GetDefaultInputColorSpace() const
{
return ListAvailableColorspaces().at(GetStandardValue(kDefaultColorspaceIn).toInt());
}
void ColorManager::SetDefaultInputColorSpace(const QString &s)
{
SetStandardValue(kDefaultColorspaceIn, ListAvailableColorspaces().indexOf(s));
}
QString ColorManager::GetReferenceColorSpace() const
{
ReferenceSpace ref_space = static_cast<ReferenceSpace>(GetStandardValue(kReferenceSpaceIn).toInt());
if (ref_space == kCompositingLog) {
return OCIO::ROLE_COMPOSITING_LOG;
} else {
return OCIO::ROLE_SCENE_LINEAR;
}
}
QString ColorManager::GetCompliantColorSpace(const QString &s)
{
if (ListAvailableColorspaces().contains(s)) {
return s;
} else {
return GetDefaultInputColorSpace();
}
}
ColorTransform ColorManager::GetCompliantColorSpace(const ColorTransform &transform, bool force_display)
{
if (transform.is_display() || force_display) {
// Get display information
QString display = transform.display();
QString view = transform.view();
QString look = transform.look();
// Check if display still exists in config
if (!ListAvailableDisplays().contains(display)) {
display = GetDefaultDisplay();
}
// Check if view still exists in display
if (!ListAvailableViews(display).contains(view)) {
view = GetDefaultView(display);
}
// Check if looks still exists
if (!ListAvailableLooks().contains(look)) {
look.clear();
}
return ColorTransform(display, view, look);
} else {
QString output = transform.output();
if (!ListAvailableColorspaces().contains(output)) {
output = GetDefaultInputColorSpace();
}
return ColorTransform(output);
}
}
QStringList ColorManager::ListAvailableColorspaces(OCIO::ConstConfigRcPtr config)
{
QStringList spaces;
int number_of_colorspaces = config->getNumColorSpaces();
for (int i=0;i<number_of_colorspaces;i++) {
spaces.append(config->getColorSpaceNameByIndex(i));
}
return spaces;
}
void ColorManager::GetDefaultLumaCoefs(double *rgb) const
{
config_->getDefaultLumaCoefs(rgb);
}
Color ColorManager::GetDefaultLumaCoefs() const
{
Color c;
// Just a default value, shouldn't be significant
c.set_alpha(1.0f);
// The float data in Color lines up with the "rgb" param of this function
GetDefaultLumaCoefs(c.data());
return c;
}
void ColorManager::Retranslate()
{
SetInputName(kConfigFilenameIn, tr("Configuration"));
SetInputName(kDefaultColorspaceIn, tr("Default Input"));
SetInputName(kReferenceSpaceIn, tr("Reference Space"));
SetComboBoxStrings(kReferenceSpaceIn, {tr("Scene Linear"), tr("Compositing Log")});
SetInputProperty(kConfigFilenameIn, QStringLiteral("placeholder"), tr("(built-in)"));
}
void ColorManager::InputValueChangedEvent(const QString &input, int element)
{
Q_UNUSED(element)
if (input == kConfigFilenameIn) {
try {
SetConfig(OCIO::Config::CreateFromFile(GetConfigFilename().toUtf8()));
} catch (OCIO::Exception&) {}
}
}
ColorManager::SetLocale::SetLocale(const char* new_locale)
{
old_locale_ = setlocale(LC_NUMERIC, nullptr);
setlocale(LC_NUMERIC, new_locale);
}
ColorManager::SetLocale::~SetLocale()
{
setlocale(LC_NUMERIC, old_locale_.toUtf8());
}
void ColorManager::SetConfig(OCIO::ConstConfigRcPtr config)
{
config_ = config;
SetComboBoxStrings(kDefaultColorspaceIn, ListAvailableColorspaces());
}
}
+149
View File
@@ -0,0 +1,149 @@
/***
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 COLORSERVICE_H
#define COLORSERVICE_H
#include <memory>
#include <QMutex>
#include "codec/frame.h"
#include "node/node.h"
#include "render/colorprocessor.h"
#define OCIO_SET_C_LOCALE_FOR_SCOPE ColorManager::SetLocale d("C")
namespace olive {
class ColorManager : public Node
{
Q_OBJECT
public:
ColorManager();
virtual QString Name() const override
{
return tr("Color Manager");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.colormanager");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryColor};
}
virtual QString Description() const override
{
return tr("Color management configuration for project.");
}
virtual Node* copy() const override
{
return new ColorManager();
}
OCIO::ConstConfigRcPtr GetConfig() const;
static OCIO::ConstConfigRcPtr CreateConfigFromFile(const QString& filename);
QString GetConfigFilename() const;
static OCIO::ConstConfigRcPtr GetDefaultConfig();
static void SetUpDefaultConfig();
void SetConfigFilename(const QString& filename);
QStringList ListAvailableDisplays();
QString GetDefaultDisplay();
QStringList ListAvailableViews(QString display);
QString GetDefaultView(const QString& display);
QStringList ListAvailableLooks();
QStringList ListAvailableColorspaces() const;
QString GetDefaultInputColorSpace() const;
void SetDefaultInputColorSpace(const QString& s);
QString GetReferenceColorSpace() const;
QString GetCompliantColorSpace(const QString& s);
ColorTransform GetCompliantColorSpace(const ColorTransform& transform, bool force_display = false);
static QStringList ListAvailableColorspaces(OCIO::ConstConfigRcPtr config);
void GetDefaultLumaCoefs(double *rgb) const;
Color GetDefaultLumaCoefs() const;
class SetLocale
{
public:
SetLocale(const char* new_locale);
~SetLocale();
private:
QString old_locale_;
};
QMutex* mutex()
{
return &mutex_;
}
static const QString kConfigFilenameIn;
static const QString kDefaultColorspaceIn;
static const QString kReferenceSpaceIn;
virtual void Retranslate() override;
protected:
virtual void InputValueChangedEvent(const QString &input, int element) override;
private:
enum ReferenceSpace {
kSceneLinear,
kCompositingLog
};
void SetConfig(OCIO::ConstConfigRcPtr config);
OCIO::ConstConfigRcPtr config_;
QMutex mutex_;
static OCIO::ConstConfigRcPtr default_config_;
};
}
#endif // COLORSERVICE_H
+2
View File
@@ -33,6 +33,8 @@ class CropDistortNode : public Node
public:
CropDistortNode();
NODE_DEFAULT_DESTRUCTOR(CropDistortNode)
virtual Node* copy() const override
{
return new CropDistortNode();
@@ -31,6 +31,8 @@ class TransformDistortNode : public MatrixGenerator
public:
TransformDistortNode();
NODE_DEFAULT_DESTRUCTOR(TransformDistortNode)
virtual Node* copy() const override
{
return new TransformDistortNode();
+3 -3
View File
@@ -43,9 +43,9 @@
#include "math/trigonometry/trigonometry.h"
#include "output/track/track.h"
#include "output/viewer/viewer.h"
#include "project/item/folder/folder.h"
#include "project/item/footage/footage.h"
#include "project/item/sequence/sequence.h"
#include "project/folder/folder.h"
#include "project/footage/footage.h"
#include "project/sequence/sequence.h"
namespace olive {
QList<Node*> NodeFactory::library_;
+2
View File
@@ -31,6 +31,8 @@ class BlurFilterNode : public Node
public:
BlurFilterNode();
NODE_DEFAULT_DESTRUCTOR(BlurFilterNode)
virtual Node* copy() const override;
virtual QString Name() const override;
@@ -31,6 +31,8 @@ class MosaicFilterNode : public Node
public:
MosaicFilterNode();
NODE_DEFAULT_DESTRUCTOR(MosaicFilterNode)
virtual Node* copy() const override
{
return new MosaicFilterNode();
+2
View File
@@ -31,6 +31,8 @@ class StrokeFilterNode : public Node
public:
StrokeFilterNode();
NODE_DEFAULT_DESTRUCTOR(StrokeFilterNode)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -34,6 +34,8 @@ class MatrixGenerator : public Node
public:
MatrixGenerator();
NODE_DEFAULT_DESTRUCTOR(MatrixGenerator)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -32,6 +32,8 @@ class PolygonGenerator : public Node
public:
PolygonGenerator();
NODE_DEFAULT_DESTRUCTOR(PolygonGenerator)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class SolidGenerator : public Node
public:
SolidGenerator();
NODE_DEFAULT_DESTRUCTOR(SolidGenerator)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class TextGenerator : public Node
public:
TextGenerator();
NODE_DEFAULT_DESTRUCTOR(TextGenerator)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class TimeInput : public Node
public:
TimeInput();
NODE_DEFAULT_DESTRUCTOR(TimeInput)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -31,6 +31,8 @@ class MathNode : public MathNodeBase
public:
MathNode();
NODE_DEFAULT_DESTRUCTOR(MathNode)
virtual Node* copy() const override;
virtual QString Name() const override;
+2
View File
@@ -30,6 +30,8 @@ class MathNodeBase : public Node
public:
MathNodeBase() = default;
NODE_DEFAULT_DESTRUCTOR(MathNodeBase)
enum Operation {
kOpAdd,
kOpSubtract,
+2
View File
@@ -31,6 +31,8 @@ class MergeNode : public Node
public:
MergeNode();
NODE_DEFAULT_DESTRUCTOR(MergeNode)
virtual Node* copy() const override;
virtual QString Name() const override;
@@ -31,6 +31,8 @@ class TrigonometryNode : public Node
public:
TrigonometryNode();
NODE_DEFAULT_DESTRUCTOR(TrigonometryNode)
virtual Node* copy() const override;
virtual QString Name() const override;
+1 -1
View File
@@ -31,8 +31,8 @@
#include "common/xmlutils.h"
#include "core.h"
#include "config/config.h"
#include "node/project/footage/footage.h"
#include "project/project.h"
#include "project/item/footage/footage.h"
#include "ui/colorcoding.h"
#include "ui/icons/icons.h"
#include "widget/nodeview/nodeviewundo.h"
+2
View File
@@ -45,6 +45,8 @@
namespace olive {
#define NODE_DEFAULT_DESTRUCTOR(x) virtual ~x() override {DisconnectAll();}
class NodeGraph;
class Folder;
+1 -1
View File
@@ -25,7 +25,7 @@
#include <QUndoCommand>
#include "node/node.h"
#include "project/item/sequence/sequence.h"
#include "node/project/sequence/sequence.h"
namespace olive {
-5
View File
@@ -55,11 +55,6 @@ Track::Track() :
track_height_ = kTrackHeightDefault;
}
Track::~Track()
{
DisconnectAll();
}
void Track::set_type(const Type &track_type)
{
track_type_ = track_type;
+1 -1
View File
@@ -44,7 +44,7 @@ public:
Track();
virtual ~Track() override;
NODE_DEFAULT_DESTRUCTOR(Track)
const Track::Type& type() const;
void set_type(const Track::Type& track_type);
+1 -1
View File
@@ -24,7 +24,7 @@
#include "node/math/math/math.h"
#include "node/math/merge/merge.h"
#include "node/output/viewer/viewer.h"
#include "project/item/sequence/sequence.h"
#include "node/project/sequence/sequence.h"
namespace olive {
-8
View File
@@ -57,14 +57,6 @@ ViewerOutput::ViewerOutput(bool create_default_streams) :
}
}
ViewerOutput::~ViewerOutput()
{
// Should prevent traversing graph unnecessarily
BeginOperation();
DisconnectAll();
EndOperation();
}
Node *ViewerOutput::copy() const
{
return new ViewerOutput();
+2 -1
View File
@@ -45,7 +45,8 @@ class ViewerOutput : public Node
Q_OBJECT
public:
ViewerOutput(bool create_default_streams = true);
virtual ~ViewerOutput() override;
NODE_DEFAULT_DESTRUCTOR(ViewerOutput)
virtual Node* copy() const override;
+7
View File
@@ -14,9 +14,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(folder)
add_subdirectory(footage)
add_subdirectory(projectsettings)
add_subdirectory(sequence)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/project/project.h
node/project/project.cpp
node/project/projectviewmodel.h
node/project/projectviewmodel.cpp
PARENT_SCOPE
)
+23
View File
@@ -0,0 +1,23 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2020 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/project/folder/folder.h
node/project/folder/folder.cpp
PARENT_SCOPE
)
+158
View File
@@ -0,0 +1,158 @@
/***
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 "folder.h"
#include "common/xmlutils.h"
#include "node/project/footage/footage.h"
#include "node/project/sequence/sequence.h"
#include "ui/icons/icons.h"
namespace olive {
#define super Node
const QString Folder::kChildInput = QStringLiteral("child_in");
Folder::Folder()
{
AddInput(kChildInput, NodeValue::kNone, InputFlags(kInputFlagArray | kInputFlagNotKeyframable));
}
QIcon Folder::icon() const
{
return icon::Folder;
}
void Folder::Retranslate()
{
super::Retranslate();
SetInputName(kChildInput, tr("Children"));
}
bool ChildExistsWithNameInternal(const Folder* n, const QString& s)
{
for (int i=0; i<n->item_child_count(); i++) {
Node* child = n->item_child(i);
if (child->GetLabel() == s) {
return true;
} else {
Folder* subfolder = dynamic_cast<Folder*>(child);
if (subfolder && ChildExistsWithNameInternal(subfolder, s)) {
return true;
}
}
}
return false;
}
bool Folder::ChildExistsWithName(const QString &s) const
{
return ChildExistsWithNameInternal(this, s);
}
int Folder::index_of_child_in_array(Node *item) const
{
int index_of_item = item_children_.indexOf(item);
if (index_of_item == -1) {
return -1;
}
return item_element_index_.at(index_of_item);
}
void Folder::InputConnectedEvent(const QString &input, int element, const NodeOutput &output)
{
if (input == kChildInput && element != -1) {
Node* item = output.node();
// 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);
item_element_index_.append(element);
item->SetFolder(this);
emit EndInsertItem();
}
}
void Folder::InputDisconnectedEvent(const QString &input, int element, const NodeOutput &output)
{
if (input == kChildInput && element != -1) {
Node* item = output.node();
int child_index = item_children_.indexOf(item);
emit BeginRemoveItem(item, child_index);
item_children_.removeAt(child_index);
item_element_index_.removeAt(child_index);
item->SetFolder(nullptr);
emit EndRemoveItem();
}
}
FolderAddChild::FolderAddChild(Folder *folder, Node *child, bool autoposition) :
folder_(folder),
child_(child),
autoposition_(autoposition),
position_command_(nullptr)
{
}
FolderAddChild::~FolderAddChild()
{
delete position_command_;
}
Project *FolderAddChild::GetRelevantProject() const
{
return folder_->project();
}
void FolderAddChild::redo()
{
int array_index = folder_->InputArraySize(Folder::kChildInput);
folder_->InputArrayAppend(Folder::kChildInput, false);
Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index));
if (autoposition_) {
old_position_ = child_->GetPosition();
if (!position_command_) {
position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, array_index, array_index+1, true);
}
position_command_->redo();
}
}
void FolderAddChild::undo()
{
if (position_command_) {
position_command_->undo();
}
Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1));
folder_->InputArrayRemoveLast(Folder::kChildInput);
}
}
+191
View File
@@ -0,0 +1,191 @@
/***
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 FOLDER_H
#define FOLDER_H
#include "node/node.h"
namespace olive {
/**
* @brief The Folder class representing a directory in a project structure
*
* The Item base class already has support for children, but this functionality is disabled by default
* (see CanHaveChildren() override). The Folder is a specific type that enables this functionality.
*/
class Folder : public Node
{
Q_OBJECT
public:
Folder();
NODE_DEFAULT_DESTRUCTOR(Folder)
virtual Node* copy() const override
{
return new Folder();
}
virtual QString Name() const override
{
return tr("Folder");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.folder");
}
virtual QVector<CategoryID> Category() const override
{
return {kCategoryProject};
}
virtual QString Description() const override
{
return tr("Organize several items into a single collection.");
}
virtual QIcon icon() const override;
virtual void Retranslate() override;
bool ChildExistsWithName(const QString& s) const;
int item_child_count() const
{
return item_children_.size();
}
Node* item_child(int i) const
{
return item_children_.at(i);
}
const QVector<Node*>& children() const
{
return item_children_;
}
virtual bool IsItem() const override
{
return true;
}
int index_of_child(Node* item) const
{
return item_children_.indexOf(item);
}
int index_of_child_in_array(Node* item) const;
template <typename T>
QVector<T*> ListChildrenOfType() const
{
QVector<T*> list;
foreach (Node* node, item_children_) {
T* cast_test = dynamic_cast<T*>(node);
if (cast_test) {
list.append(cast_test);
}
}
return list;
}
static const QString kChildInput;
signals:
void BeginInsertItem(Node* n, int index);
void EndInsertItem();
void BeginRemoveItem(Node* n, int index);
void EndRemoveItem();
protected:
virtual void InputConnectedEvent(const QString& input, int element, const NodeOutput& output) override;
virtual void InputDisconnectedEvent(const QString& input, int element, const NodeOutput& output) 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<Node*> item_children_;
QVector<int> item_element_index_;
};
class FolderAddChild : public UndoCommand
{
public:
FolderAddChild(Folder* folder, Node* child, bool autoposition = true);
virtual ~FolderAddChild() override;
virtual Project * GetRelevantProject() const override;
virtual void redo() override;
virtual void undo() override;
private:
Folder* folder_;
Node* child_;
bool autoposition_;
QPointF old_position_;
NodeSetPositionAsChildCommand* position_command_;
};
}
#endif // FOLDER_H
+25
View File
@@ -0,0 +1,25 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2020 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/project/footage/footage.cpp
node/project/footage/footage.h
node/project/footage/footagedescription.cpp
node/project/footage/footagedescription.h
PARENT_SCOPE
)
+579
View File
@@ -0,0 +1,579 @@
/***
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 "footage.h"
#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"
#include "widget/videoparamedit/videoparamedit.h"
namespace olive {
const QString Footage::kFilenameInput = QStringLiteral("file_in");
#define super ViewerOutput
Footage::Footage(const QString &filename) :
ViewerOutput(false),
cancelled_(nullptr)
{
AddInput(kFilenameInput, NodeValue::kFile, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
Clear();
set_filename(filename);
}
void Footage::Retranslate()
{
super::Retranslate();
SetInputName(kFilenameInput, tr("Filename"));
}
QVector<QString> Footage::inputs_for_output(const QString &output) const
{
Q_UNUSED(output)
return {kFilenameInput};
}
bool Footage::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled)
{
if (reader->name() == QStringLiteral("timestamp")) {
set_timestamp(reader->readElementText().toLongLong());
return true;
} else {
return super::LoadCustom(reader, xml_node_data, version, cancelled);
}
}
void Footage::SaveCustom(QXmlStreamWriter *writer) const
{
super::SaveCustom(writer);
writer->writeTextElement(QStringLiteral("timestamp"), QString::number(timestamp_));
}
void Footage::InputValueChangedEvent(const QString &input, int 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()));
FootageDescription footage_info;
if (QFileInfo::exists(meta_cache_file)) {
// Load meta cache file
footage_info.Load(meta_cache_file);
} else {
// Probe and create cache
QVector<DecoderPtr> decoder_list = Decoder::ReceiveListOfAllDecoders();
foreach (DecoderPtr decoder, decoder_list) {
footage_info = decoder->Probe(filename(), cancelled_);
if (footage_info.IsValid()) {
break;
}
}
if (!footage_info.Save(meta_cache_file)) {
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
}
}
if (footage_info.IsValid()) {
decoder_ = footage_info.decoder();
for (int i=0; i<footage_info.GetVideoStreams().size(); i++) {
AddStream(Track::kVideo, QVariant::fromValue(footage_info.GetVideoStreams().at(i)));
}
if (!footage_info.GetVideoStreams().isEmpty()) {
// FIXME: This will break on multiple video streams. Currently we don't have
// infrastructure for different properties per element. We'll see if this becomes
// a problem.
VideoParams vp = footage_info.GetVideoStreams().first();
uint64_t video_param_mask = 0;
video_param_mask |= VideoParamEdit::kEnabled;
video_param_mask |= VideoParamEdit::kColorspace;
video_param_mask |= VideoParamEdit::kPixelAspect;
video_param_mask |= VideoParamEdit::kInterlacing;
video_param_mask |= VideoParamEdit::kFrameRateIsArbitrary;
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
// Add premultiplied setting if this footage has an alpha channel
video_param_mask |= VideoParamEdit::kPremultipliedAlpha;
}
if (vp.video_type() == VideoParams::kVideoTypeVideo) {
// This is video, ensure that the frame rate does not overwrite the timebase
video_param_mask |= VideoParamEdit::kFrameRateIsNotTimebase;
} else {
// This is not a video, so it's either a still image or an image sequence
video_param_mask |= VideoParamEdit::kIsImageSequence;
video_param_mask |= VideoParamEdit::kStartTime;
video_param_mask |= VideoParamEdit::kEndTime;
video_param_mask |= VideoParamEdit::kFrameRate;
}
SetInputProperty(kVideoParamsInput, QStringLiteral("mask"), QVariant::fromValue(video_param_mask));
}
for (int i=0; i<footage_info.GetAudioStreams().size(); i++) {
AddStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i)));
}
SetValid();
}
} else {
set_timestamp(0);
}
} else {
super::InputValueChangedEvent(input, element);
}
}
rational Footage::GetCustomLength(Track::Type type) const
{
if (type == Track::kVideo) {
VideoParams first_stream = GetFirstEnabledVideoStream();
if (first_stream.is_valid()) {
return Timecode::timestamp_to_time(first_stream.duration(), first_stream.time_base());
}
} else if (type == Track::kAudio) {
AudioParams first_stream = GetFirstEnabledAudioStream();
if (first_stream.is_valid()) {
return Timecode::timestamp_to_time(first_stream.duration(), first_stream.time_base());
}
}
return super::GetCustomLength(type);
}
QString Footage::GetColorspaceToUse(const VideoParams &params) const
{
if (params.colorspace().isEmpty()) {
return project()->color_manager()->GetDefaultInputColorSpace();
} else {
return params.colorspace();
}
}
void Footage::Clear()
{
// Clear all dynamically created inputs
InputArrayResize(kVideoParamsInput, 0);
InputArrayResize(kAudioParamsInput, 0);
// Clear decoder link
decoder_.clear();
// Reset ready state
valid_ = false;
}
void Footage::SetValid()
{
valid_ = true;
}
QString Footage::filename() const
{
return GetStandardValue(kFilenameInput).toString();
}
void Footage::set_filename(const QString &s)
{
SetStandardValue(kFilenameInput, s);
}
const qint64 &Footage::timestamp() const
{
return timestamp_;
}
void Footage::set_timestamp(const qint64 &t)
{
timestamp_ = t;
}
int Footage::GetStreamIndex(Track::Type type, int index) const
{
if (type == Track::kVideo) {
return GetVideoParams(index).stream_index();
} else if (type == Track::kAudio) {
return GetAudioParams(index).stream_index();
} else {
return -1;
}
}
const QString &Footage::decoder() const
{
return decoder_;
}
QIcon Footage::icon() const
{
if (valid_ && GetTotalStreamCount()) {
// Prioritize video > audio > image
VideoParams s = GetFirstEnabledVideoStream();
if (s.is_valid() && s.video_type() != VideoParams::kVideoTypeStill) {
return icon::Video;
} else if (HasEnabledAudioStreams()) {
return icon::Audio;
} else if (s.is_valid() && s.video_type() == VideoParams::kVideoTypeStill) {
return icon::Image;
}
}
return icon::Error;
}
QString Footage::DescribeVideoStream(const VideoParams &params)
{
if (params.video_type() == VideoParams::kVideoTypeStill) {
return tr("%1: Image - %2x%3").arg(QString::number(params.stream_index()),
QString::number(params.width()),
QString::number(params.height()));
} else {
return tr("%1: Video - %2x%3").arg(QString::number(params.stream_index()),
QString::number(params.width()),
QString::number(params.height()));
}
}
QString Footage::DescribeAudioStream(const AudioParams &params)
{
return tr("%1: Audio - %2 Channel(s), %3Hz")
.arg(QString::number(params.stream_index()),
QString::number(params.channel_count()),
QString::number(params.sample_rate()));
}
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()) {
// 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.
DecoderPtr decoder = Decoder::CreateFromID(footage->decoder());
Streams probed_streams = decoder->Probe(filename, nullptr);
if (probed_streams == footage->streams_) {
return true;
}
}*/
Q_UNUSED(footage)
// 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
return false;
}
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
Track::Reference ref = Track::Reference::FromString(output);
QString fn = filename();
if (!fn.isEmpty()) {
VideoParams params = GetVideoParams(ref.index());
if (params.is_valid()) {
// 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() == Track::kVideo) {
// Current color config and space
hash.addData(project()->color_manager()->GetConfigFilename().toUtf8());
hash.addData(GetColorspaceToUse(params).toUtf8());
// Alpha associated setting
hash.addData(QString::number(params.premultiplied_alpha()).toUtf8());
// Pixel aspect ratio
hash.addData(reinterpret_cast<const char*>(&params.pixel_aspect_ratio()), sizeof(params.pixel_aspect_ratio()));
// Footage timestamp
if (params.video_type() != VideoParams::kVideoTypeStill) {
int64_t video_ts = Timecode::time_to_timestamp(time, params.time_base());
// 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(params.start_time()).toUtf8());
}
}
}
}
}
NodeValueTable Footage::Value(const QString &output, NodeValueDatabase &value) const
{
Track::Reference ref = Track::Reference::FromString(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()) {
FootageJob job(decoder_, filename(), ref.type());
rational length;
if (ref.type() == Track::kVideo) {
VideoParams vp = GetVideoParams(ref.index());
// Ensure the colorspace is valid and not empty
vp.set_colorspace(GetColorspaceToUse(vp));
length = Timecode::timestamp_to_time(vp.duration(), vp.time_base());
job.set_video_params(vp);
} else {
AudioParams ap = GetAudioParams(ref.index());
job.set_audio_params(ap);
job.set_cache_path(project()->cache_path());
length = Timecode::timestamp_to_time(ap.duration(), ap.time_base());
}
table.Push(NodeValue::kRational, QVariant::fromValue(length), this, false, QStringLiteral("length"));
table.Push(NodeValue::kFootageJob, QVariant::fromValue(job), this);
}
return table;
}
QString Footage::GetStreamTypeName(Track::Type type)
{
switch (type) {
case Track::kVideo:
return tr("Video");
case Track::kAudio:
return tr("Audio");
case Track::kSubtitle:
return tr("Subtitle");
case Track::kNone:
case Track::kCount:
break;
}
return tr("Unknown");
}
NodeOutput Footage::GetConnectedTextureOutput()
{
QString output = Track::Reference(Track::kVideo, 0).ToString();
if (HasOutputWithID(output)) {
return NodeOutput(this, output);
} else {
return NodeOutput();
}
}
NodeOutput Footage::GetConnectedSampleOutput()
{
QString output = Track::Reference(Track::kAudio, 0).ToString();
if (HasOutputWithID(output)) {
return NodeOutput(this, output);
} else {
return NodeOutput();
}
}
void Footage::UpdateTooltip()
{
if (valid_) {
QString tip = tr("Filename: %1").arg(filename());
int vp_sz = GetVideoStreamCount();
for (int i=0; i<vp_sz; i++) {
VideoParams p = GetVideoParams(i);
if (p.enabled()) {
tip.append("\n");
tip.append(DescribeVideoStream(p));
}
}
int ap_sz = GetAudioStreamCount();
for (int i=0; i<ap_sz; i++) {
AudioParams p = GetAudioParams(i);
if (p.enabled()) {
tip.append("\n");
tip.append(DescribeAudioStream(p));
}
}
SetToolTip(tip);
} else {
SetToolTip(tr("Invalid"));
}
}
/*void Footage::AddStreamAsInput(Track::Type type, int index, QVariant value)
{
QString input_id = GetInputIDOfIndex(type, index);
Track::Reference ref(type, index);
// Create input for parameters
NodeValue::Type value_type;
uint64_t param_mask = 0;
if (type == Track::kVideo) {
VideoParams vp = value.value<VideoParams>();
value_type = NodeValue::kVideoParams;
// Universal parameters for video/image footage
param_mask |= VideoParamEdit::kEnabled;
param_mask |= VideoParamEdit::kColorspace;
param_mask |= VideoParamEdit::kPixelAspect;
param_mask |= VideoParamEdit::kInterlacing;
if (vp.channel_count() == VideoParams::kRGBAChannelCount) {
// If this has an alpha channel, add a premultiplied optino
param_mask |= VideoParamEdit::kPremultipliedAlpha;
}
if (vp.video_type() != VideoParams::kVideoTypeVideo) {
// This is either a still image or an image sequence, add properties for those
param_mask |= VideoParamEdit::kIsImageSequence;
param_mask |= VideoParamEdit::kStartTime;
param_mask |= VideoParamEdit::kEndTime;
param_mask |= VideoParamEdit::kFrameRate;
} else {
// Ensure timebase isn't overwritten by the frame rate field
param_mask |= VideoParamEdit::kFrameRateIsNotTimebase;
}
} else {
value_type = NodeValue::kAudioParams;
param_mask = 0;
}
AddInput(input_id, value_type,
InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
SetStandardValue(input_id, value);
SetInputProperty(input_id, QStringLiteral("mask"), QVariant::fromValue(param_mask));
inputs_for_stream_properties_.insert(ref, input_id);
// Create output for stream
QString output_id = Track::Reference(type, index).ToString();
AddOutput(output_id);
outputs_for_streams_.insert(ref, output_id);
}*/
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 Track::Reference::video_colorspace(bool default_if_empty) const
{
if (IsValid()) {
VideoParams params = footage_->GetVideoParams(index_);
if (params.is_valid()) {
if (params.colorspace().isEmpty() && default_if_empty) {
} else {
return params.colorspace();
}
}
}
return QString();
}*/
}
+243
View File
@@ -0,0 +1,243 @@
/***
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 FOOTAGE_H
#define FOOTAGE_H
#include <QList>
#include <QDateTime>
#include "common/rational.h"
#include "footagedescription.h"
#include "node/output/viewer/viewer.h"
#include "render/audioparams.h"
#include "render/videoparams.h"
#include "timeline/timelinepoints.h"
namespace olive {
/**
* @brief A reference to an external media file with metadata in a project structure
*
* Footage objects serve two purposes: storing metadata about external media and storing it as a project item.
* Footage objects store a list of Stream objects which store the majority of video/audio metadata. These streams
* are identical to the stream data in the files.
*/
class Footage : public ViewerOutput
{
Q_OBJECT
public:
/**
* @brief Footage Constructor
*/
Footage(const QString& filename = QString());
NODE_DEFAULT_DESTRUCTOR(Footage)
virtual Node* copy() const override
{
return new Footage();
}
virtual QString Name() const override
{
return tr("Footage");
}
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;
virtual QVector<QString> inputs_for_output(const QString &output) const override;
/**
* @brief Reset Footage state ready for running through Probe() again
*
* If a Footage object needs to be re-probed (e.g. source file changes or Footage is linked to a new file), its
* state needs to be reset so the Decoder::Probe() function can accurately mirror the source file. Clear() will
* reset the Footage object's state to being freshly created (keeping the filename).
*
* In most cases, you'll be using olive::ProbeMedia() for re-probing which already runs Clear(), so you won't need
* to worry about this.
*/
void Clear();
bool IsValid() const
{
return valid_;
}
/**
* @brief Sets this footage to valid and ready to use
*/
void SetValid();
/**
* @brief Return the current filename of this Footage object
*/
QString filename() const;
/**
* @brief Set the filename
*
* NOTE: This does not automtaically clear the old streams and re-probe for new ones. If the file link has been
* changed, this will need to be done manually.
*
* @param s
*
* New filename
*/
void set_filename(const QString& s);
/**
* @brief Retrieve the last modified time/date
*
* The file's last modified timestamp is stored for potential organization in the ProjectExplorer. It can be
* retrieved here.
*/
const qint64 &timestamp() const;
/**
* @brief Set the last modified time/date
*
* This should probably only be done on import or replace.
*
* @param t
*
* New last modified time/date
*/
void set_timestamp(const qint64 &t);
void SetCancelPointer(const QAtomicInt* c)
{
cancelled_ = c;
}
int GetStreamIndex(Track::Type type, int index) const;
int GetStreamIndex(const Track::Reference& ref) const
{
return GetStreamIndex(ref.type(), ref.index());
}
Track::Reference GetReferenceFromRealIndex(int real_index) const;
/**
* @brief Get the Decoder ID set when this Footage was probed
*
* @return
*
* A decoder ID
*/
const QString& decoder() const;
virtual QIcon icon() const override;
virtual bool IsItem() const override
{
return true;
}
static QString DescribeVideoStream(const VideoParams& params);
static QString DescribeAudioStream(const AudioParams& params);
static bool CompareFootageToFile(Footage* footage, const QString& filename);
static bool CompareFootageToItsFilename(Footage* footage);
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(Track::Type type);
virtual NodeOutput GetConnectedTextureOutput() override;
virtual NodeOutput GetConnectedSampleOutput() override;
static const QString kFilenameInput;
protected:
/**
* @brief Load function
*/
virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override;
/**
* @brief Save function
*/
virtual void SaveCustom(QXmlStreamWriter *writer) const override;
virtual void InputValueChangedEvent(const QString &input, int element) override;
virtual rational GetCustomLength(Track::Type type) const override;
private:
QString GetColorspaceToUse(const VideoParams& params) const;
/**
* @brief Update the icon based on the Footage status
*
* For kUnprobed and kError an appropriate icon will be shown. For kReady, this function will determine what the
* dominant type of media in this Footage is (video/audio/image) and set the icon accordingly based on that.
*/
void UpdateIcon();
/**
* @brief Update the tooltip based on the Footage status
*
* For kUnprobed and kError, this sets an appropriate generic message. For kReady, this function will set
* basic information about the Footage in the tooltip (based on the results of a previous probe).
*/
void UpdateTooltip();
/**
* @brief Internal timestamp object
*/
qint64 timestamp_;
/**
* @brief Internal attached decoder ID
*/
QString decoder_;
bool valid_;
const QAtomicInt* cancelled_;
private slots:
void CheckFootage();
};
}
#endif // FOOTAGE_H
@@ -0,0 +1,118 @@
/***
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 "footagedescription.h"
#include <QFile>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include "common/xmlutils.h"
namespace olive {
bool FootageDescription::Load(const QString &filename)
{
// Reset self
*this = FootageDescription();
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")) {
decoder_ = reader.readElementText();
} else if (reader.name() == QStringLiteral("streams")) {
while (XMLReadNextStartElement(&reader)) {
if (reader.name() == QStringLiteral("video")) {
VideoParams vp;
vp.Load(&reader);
AddVideoStream(vp);
} else if (reader.name() == QStringLiteral("audio")) {
AudioParams ap;
ap.Load(&reader);
AddAudioStream(ap);
} else {
reader.skipCurrentElement();
}
}
} else {
reader.skipCurrentElement();
}
}
} else {
reader.skipCurrentElement();
}
}
file.close();
return true;
}
return false;
}
bool FootageDescription::Save(const QString &filename) const
{
QFile file(filename);
if (!file.open(QFile::WriteOnly)) {
return false;
}
QXmlStreamWriter writer(&file);
writer.writeStartDocument();
writer.writeStartElement(QStringLiteral("streamcache"));
writer.writeTextElement(QStringLiteral("decoder"), decoder_);
writer.writeStartElement(QStringLiteral("streams"));
foreach (const VideoParams& vp, video_streams_) {
writer.writeStartElement(QStringLiteral("video"));
vp.Save(&writer);
writer.writeEndElement(); // video
}
foreach (const AudioParams& ap, audio_streams_) {
writer.writeStartElement(QStringLiteral("audio"));
ap.Save(&writer);
writer.writeEndElement(); // audio
}
writer.writeEndElement(); // streams
writer.writeEndElement(); // streamcache
writer.writeEndDocument();
file.close();
return true;
}
}
@@ -0,0 +1,125 @@
/***
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 FOOTAGEDESCRIPTION_H
#define FOOTAGEDESCRIPTION_H
#include "node/output/track/track.h"
#include "render/audioparams.h"
#include "render/videoparams.h"
namespace olive {
class FootageDescription
{
public:
FootageDescription(const QString& decoder = QString()) :
decoder_(decoder)
{
}
bool IsValid() const
{
return !decoder_.isEmpty() && (!video_streams_.isEmpty() || !audio_streams_.isEmpty());
}
const QString& decoder() const
{
return decoder_;
}
void AddVideoStream(const VideoParams& video_params)
{
Q_ASSERT(!HasStreamIndex(video_params.stream_index()));
video_streams_.append(video_params);
}
void AddAudioStream(const AudioParams& audio_params)
{
Q_ASSERT(!HasStreamIndex(audio_params.stream_index()));
audio_streams_.append(audio_params);
}
Track::Type GetTypeOfStream(int index)
{
if (StreamIsVideo(index)) {
return Track::kVideo;
} else if (StreamIsAudio(index)) {
return Track::kAudio;
} else {
return Track::kNone;
}
}
bool StreamIsVideo(int index) const
{
foreach (const VideoParams& vp, video_streams_) {
if (vp.stream_index() == index) {
return true;
}
}
return false;
}
bool StreamIsAudio(int index) const
{
foreach (const AudioParams& ap, audio_streams_) {
if (ap.stream_index() == index) {
return true;
}
}
return false;
}
bool HasStreamIndex(int index) const
{
return StreamIsVideo(index) || StreamIsAudio(index);
}
bool Load(const QString& filename);
bool Save(const QString& filename) const;
const QVector<VideoParams>& GetVideoStreams() const
{
return video_streams_;
}
const QVector<AudioParams>& GetAudioStreams() const
{
return audio_streams_;
}
private:
QString decoder_;
QVector<VideoParams> video_streams_;
QVector<AudioParams> audio_streams_;
};
}
#endif // FOOTAGEDESCRIPTION_H
+301
View File
@@ -0,0 +1,301 @@
/***
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 "project.h"
#include <QDir>
#include <QFileInfo>
#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"
namespace olive {
Project::Project() :
is_modified_(false),
autorecovery_saved_(true)
{
// Generate UUID for this project
RegenerateUuid();
// Adds a color manager "node" to this project so that it synchronizes
color_manager_ = new ColorManager();
color_manager_->setParent(this);
color_manager_->SetPosition(QPointF(1, 0));
AddDefaultNode(color_manager_);
// Same with project settings
settings_ = new ProjectSettingsNode();
settings_->setParent(this);
settings_->SetPosition(QPointF(2, 0));
AddDefaultNode(settings_);
// Folder root for project
root_ = new Folder();
root_->setParent(this);
root_->SetLabel(tr("Root"));
connect(color_manager(), &ColorManager::ValueChanged,
this, &Project::ColorManagerValueChanged);
}
void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint version, const QAtomicInt* cancelled)
{
XMLNodeData xml_node_data;
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("root")) {
root_->Load(reader, xml_node_data, version, cancelled);
} else if (reader->name() == QStringLiteral("layout")) {
// Since the main window's functions have to occur in the GUI thread (and we're likely
// loading in a secondary thread), we load all necessary data into a separate struct so we
// can continue loading and queue it with the main window so it can handle the data
// appropriately in its own thread.
*layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data);
} else if (reader->name() == QStringLiteral("uuid")) {
uuid_ = QUuid::fromString(reader->readElementText());
} else if (reader->name() == QStringLiteral("nodes")) {
while (XMLReadNextStartElement(reader)) {
if (reader->name() == QStringLiteral("node")) {
bool is_root = false;
bool is_cm = false;
bool is_settings = false;
QString id;
{
XMLAttributeLoop(reader, attr) {
if (attr.name() == QStringLiteral("id")) {
id = attr.value().toString();
} else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) {
is_root = true;
} else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) {
is_cm = true;
} else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) {
is_settings = true;
}
}
}
if (id.isEmpty()) {
qWarning() << "Failed to load node with empty ID";
} else {
Node* node;
if (is_root) {
node = root_;
} else if (is_cm) {
node = color_manager_;
} else if (is_settings) {
node = settings_;
} else {
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
reader->skipCurrentElement();
}
}
// Make connections
XMLConnectNodes(xml_node_data);
// Link blocks
XMLLinkBlocks(xml_node_data);
}
void Project::Save(QXmlStreamWriter *writer) const
{
writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString());
writer->writeStartElement(QStringLiteral("nodes"));
foreach (Node* node, nodes()) {
writer->writeStartElement(QStringLiteral("node"));
if (node == root_) {
writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1"));
} else if (node == color_manager_) {
writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1"));
} else if (node == settings_) {
writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1"));
}
writer->writeAttribute(QStringLiteral("id"), node->id());
node->Save(writer);
writer->writeEndElement(); // node
}
writer->writeEndElement(); // nodes
// Save main window project layout
MainWindowLayoutInfo main_window_info = Core::instance()->main_window()->SaveLayout();
main_window_info.toXml(writer);
}
Folder *Project::root()
{
return root_;
}
QString Project::name() const
{
if (filename_.isEmpty()) {
return tr("(untitled)");
} else {
return QFileInfo(filename_).completeBaseName();
}
}
const QString &Project::filename() const
{
return filename_;
}
QString Project::pretty_filename() const
{
QString fn = filename();
if (fn.isEmpty()) {
return tr("(untitled)");
} else {
return fn;
}
}
void Project::set_filename(const QString &s)
{
filename_ = s;
#ifdef Q_OS_WINDOWS
// Prevents filenames
filename_.replace('/', '\\');
#endif
emit NameChanged();
}
ColorManager *Project::color_manager()
{
return color_manager_;
}
bool Project::is_modified() const
{
return is_modified_;
}
void Project::set_modified(bool e)
{
is_modified_ = e;
set_autorecovery_saved(!e);
emit ModifiedChanged(is_modified_);
}
bool Project::has_autorecovery_been_saved() const
{
return autorecovery_saved_;
}
void Project::set_autorecovery_saved(bool e)
{
autorecovery_saved_ = e;
}
bool Project::is_new() const
{
return !is_modified_ && filename_.isEmpty();
}
QString Project::cache_path() const
{
ProjectSettingsNode::CacheSetting setting = settings_->GetCacheSetting();
switch (setting) {
case ProjectSettingsNode::kCacheUseDefaultLocation:
break;
case ProjectSettingsNode::kCacheCustomPath:
{
QString cache_path = settings_->GetCustomCachePath();
if (cache_path.isEmpty()) {
return cache_path;
}
break;
}
case ProjectSettingsNode::kCacheStoreAlongsideProject:
{
if (!filename_.isEmpty()) {
return QFileInfo(filename_).path();
}
break;
}
}
return DiskManager::instance()->GetDefaultCachePath();
}
void Project::RegenerateUuid()
{
uuid_ = QUuid::createUuid();
}
void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range)
{
Q_UNUSED(input)
Q_UNUSED(range)
QVector<Footage*> footage = root()->ListChildrenOfType<Footage>();
foreach (Footage* item, footage) {
item->InvalidateAll(QString());
}
}
}
+111
View File
@@ -0,0 +1,111 @@
/***
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 PROJECT_H
#define PROJECT_H
#include <memory>
#include <QObject>
#include <QUuid>
#include "node/color/colormanager/colormanager.h"
#include "node/output/viewer/viewer.h"
#include "node/project/footage/footage.h"
#include "node/project/projectsettings/projectsettings.h"
#include "window/mainwindow/mainwindowlayoutinfo.h"
namespace olive {
/**
* @brief A project instance containing all the data pertaining to the user's project
*
* A project instance uses a parent-child hierarchy of Item objects. Projects will usually contain the following:
*
* * Footage
* * Sequences
* * Folders
* * Project Settings
* * Window Layout
*/
class Project : public NodeGraph
{
Q_OBJECT
public:
Project();
void Load(QXmlStreamReader* reader, MainWindowLayoutInfo *layout, uint version, const QAtomicInt* cancelled);
void Save(QXmlStreamWriter* writer) const;
Folder* root();
QString name() const;
const QString& filename() const;
QString pretty_filename() const;
void set_filename(const QString& s);
ColorManager* color_manager();
bool is_modified() const;
void set_modified(bool e);
bool has_autorecovery_been_saved() const;
void set_autorecovery_saved(bool e);
bool is_new() const;
QString cache_path() const;
const QUuid& GetUuid() const
{
return uuid_;
}
void RegenerateUuid();
signals:
void NameChanged();
void ModifiedChanged(bool e);
private:
QUuid uuid_;
Folder* root_;
QString filename_;
ColorManager* color_manager_;
ProjectSettingsNode* settings_;
bool is_modified_;
bool autorecovery_saved_;
private slots:
void ColorManagerValueChanged(const NodeInput& input, const TimeRange& range);
};
}
#endif // PROJECT_H
@@ -31,6 +31,8 @@ class ProjectSettingsNode : public Node
public:
ProjectSettingsNode();
NODE_DEFAULT_DESTRUCTOR(ProjectSettingsNode)
virtual QString Name() const override
{
return tr("Project Settings");
+516
View File
@@ -0,0 +1,516 @@
/***
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 "projectviewmodel.h"
#include <QDebug>
#include <QMimeData>
#include <QUrl>
#include "core.h"
#include "widget/nodeview/nodeviewundo.h"
#include "widget/nodeparamview/nodeparamviewundo.h"
namespace olive {
ProjectViewModel::ProjectViewModel(QObject *parent) :
QAbstractItemModel(parent),
project_(nullptr)
{
// FIXME: make this configurable
columns_.append(kName);
columns_.append(kDuration);
columns_.append(kRate);
}
Project *ProjectViewModel::project() const
{
return project_;
}
void ProjectViewModel::set_project(Project *p)
{
beginResetModel();
if (project_) {
DisconnectItem(project_->root());
}
project_ = p;
if (project_) {
ConnectItem(project_->root());
}
endResetModel();
}
QModelIndex ProjectViewModel::index(int row, int column, const QModelIndex &parent) const
{
// I'm actually not 100% sure what this does, but it seems logical and was in the earlier code
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
// 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));
}
QModelIndex ProjectViewModel::parent(const QModelIndex &child) const
{
// Get the Item object from the index
Node* item = GetItemObjectFromIndex(child);
// Get Item's parent object
Folder* par = item->folder();
// If the parent is the root, return an empty index
if (par == project_->root()) {
return QModelIndex();
}
// Otherwise return a true index to its parent
int parent_index = IndexOfChild(par);
// Make sure the index is valid (there's no reason it shouldn't be)
Q_ASSERT(parent_index > -1);
// Return an index to the parent
return createIndex(parent_index, 0, par);
}
int ProjectViewModel::rowCount(const QModelIndex &parent) const
{
// If there's no project, there are obviously no items to show
if (project_ == nullptr) {
return 0;
}
// If the index is the root, return the root child count
if (parent == QModelIndex()) {
return project_->root()->item_child_count();
}
// Otherwise, the index must contain a valid pointer, so we just return its child count
return static_cast<Folder*>(GetItemObjectFromIndex(parent))->item_child_count();
}
int ProjectViewModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
// Not strictly necessary, but a decent visual cue that there's no project currently active
if (project_ == nullptr) {
return 0;
}
return columns_.size();
}
QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
{
Node* internal_item = GetItemObjectFromIndex(index);
ColumnType column_type = columns_.at(index.column());
switch (role) {
case Qt::DisplayRole:
{
// Standard text role
switch (column_type) {
case kName:
return internal_item->GetLabel();
case kDuration:
return internal_item->duration();
case kRate:
return internal_item->rate();
}
}
break;
case Qt::DecorationRole:
// If this is the first column, return the Item's icon
if (column_type == kName) {
return internal_item->icon();
}
break;
case Qt::ToolTipRole:
return internal_item->ToolTip();
}
return QVariant();
}
QVariant ProjectViewModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// Check if we need text data (DisplayRole) and orientation is horizontal
// FIXME I'm not 100% sure what happens if the orientation is vertical/if that check is necessary
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
ColumnType column_type = columns_.at(section);
// Return the name based on the column's current type
switch (column_type) {
case kName:
return tr("Name");
case kDuration:
return tr("Duration");
case kRate:
return tr("Rate");
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
bool ProjectViewModel::hasChildren(const QModelIndex &parent) const
{
// 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
Node* item = GetItemObjectFromIndex(parent);
return dynamic_cast<Folder*>(item);
}
bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
// The name is editable
if (index.isValid() && columns_.at(index.column()) == kName && role == Qt::EditRole) {
Node* item = GetItemObjectFromIndex(index);
QString new_name = value.toString();
if (!new_name.isEmpty()) {
NodeRenameCommand* nrc = new NodeRenameCommand();
nrc->AddNode(item, value.toString());
Core::instance()->undo_stack()->push(nrc);
return true;
}
}
return false;
}
bool ProjectViewModel::canFetchMore(const QModelIndex &parent) const
{
// 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
{
if (!index.isValid()) {
// Allow dropping files from external sources
return Qt::ItemIsDropEnabled;
}
Qt::ItemFlags f = Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index);
if (dynamic_cast<Folder*>(GetItemObjectFromIndex(index))) {
f |= Qt::ItemIsDropEnabled;
}
// If the column is the kName column, that means it's editable
if (columns_.at(index.column()) == kName) {
f |= Qt::ItemIsEditable;
}
return f;
}
QStringList ProjectViewModel::mimeTypes() const
{
// Allow data from this model and a file list from external sources
return {QStringLiteral("application/x-oliveprojectitemdata"), QStringLiteral("text/uri-list")};
}
QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
{
// Compliance with Qt standard
if (indexes.isEmpty()) {
return nullptr;
}
// Encode mime data for the rows/items that were dragged
QMimeData* data = new QMimeData();
// Use QDataStream to stream the item data into a byte array
QByteArray encoded_data;
QDataStream stream(&encoded_data, QIODevice::WriteOnly);
// The indexes list includes indexes for each column which we don't use. To make sure each row only gets sent *once*,
// we keep a list of dragged items
QVector<void*> dragged_items;
foreach (QModelIndex index, indexes) {
if (index.isValid()) {
// Check if we've dragged this item before
if (!dragged_items.contains(index.internalPointer())) {
// If not, add it to the stream (and also keep track of it in the vector)
ViewerOutput* footage = dynamic_cast<ViewerOutput*>(static_cast<Node*>(index.internalPointer()));
if (footage) {
QVector<Track::Reference> streams = footage->GetEnabledStreamsAsReferences();
stream << streams << reinterpret_cast<quintptr>(footage);
dragged_items.append(footage);
}
}
}
}
// Set byte array as the mime data and return the mime data
data->setData(QStringLiteral("application/x-oliveprojectitemdata"), encoded_data);
return data;
}
bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &drop)
{
// Default recommended checks from https://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
if (!canDropMimeData(data, action, row, column, drop)) {
return false;
}
if (action == Qt::IgnoreAction) {
return true;
}
// Probe mime data for its format
QStringList mime_formats = data->formats();
if (mime_formats.contains(QStringLiteral("application/x-oliveprojectitemdata"))) {
// Data is drag/drop data from this model
QByteArray model_data = data->data(QStringLiteral("application/x-oliveprojectitemdata"));
// Use QDataStream to deserialize the data
QDataStream stream(&model_data, QIODevice::ReadOnly);
// Get the Item object that the items were dropped on
Folder* drop_location = dynamic_cast<Folder*>(GetItemObjectFromIndex(drop));
// If this is not a folder, we cannot drop these items here
if (!drop_location) {
return false;
}
// Variables to deserialize into
quintptr item_ptr;
QList<Track::Reference> streams;
// Loop through all data
MultiUndoCommand* move_command = new MultiUndoCommand();
move_command->set_name(tr("Move Items"));
while (!stream.atEnd()) {
stream >> streams >> item_ptr;
Node* item = reinterpret_cast<Node*>(item_ptr);
// 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->folder() != drop_location
&& (!dynamic_cast<Folder*>(item) || !ItemIsParentOfChild(static_cast<Folder*>(item), drop_location))) {
move_command->add_child(new NodeEdgeRemoveCommand(item, NodeInput(item->folder(), Folder::kChildInput, item->folder()->index_of_child_in_array(item))));
move_command->add_child(new FolderAddChild(drop_location, item));
}
}
Core::instance()->undo_stack()->pushIfHasChildren(move_command);
return true;
} else if (mime_formats.contains(QStringLiteral("text/uri-list"))) {
// We received a list of files
QByteArray file_data = data->data(QStringLiteral("text/uri-list"));
// Use text stream to parse (just an easy way of sifting through line breaks
QTextStream stream(&file_data);
// Convert QByteArray to QStringList (which Core takes for importing)
QStringList urls;
while (!stream.atEnd()) {
QUrl url = stream.readLine();
if (!url.isEmpty()) {
urls.append(url.toLocalFile());
}
}
// Get folder dropped onto
Node* drop_item = GetItemObjectFromIndex(drop);
// If we didn't drop onto an item, find the nearest parent folder (should eventually terminate at root either way)
if (!dynamic_cast<Folder*>(drop_item)) {
drop_item = drop_item->folder();
if (!drop_item) {
// Failed to find folder to place this in
return false;
}
}
// Trigger an import
Core::instance()->ImportFiles(urls, this, static_cast<Folder*>(drop_item));
return true;
}
return false;
}
int ProjectViewModel::IndexOfChild(Node *item) const
{
// Find parent's index within its own parent
Folder* parent = item->folder();
if (parent) {
return parent->index_of_child(item);
}
return -1;
}
Node *ProjectViewModel::GetItemObjectFromIndex(const QModelIndex &index) const
{
if (index.isValid()) {
return static_cast<Node*>(index.internalPointer());
}
return project_ ? project_->root() : nullptr;
}
bool ProjectViewModel::ItemIsParentOfChild(Folder *parent, Node *child) const
{
// Loop through parent hierarchy checking if `parent` is one of its parents
do {
child = child->folder();
if (parent == child) {
return true;
}
} while (child != nullptr);
return false;
}
void ProjectViewModel::ConnectItem(Node *n)
{
connect(n, &Node::LabelChanged, this, &ProjectViewModel::ItemRenamed);
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);
connect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::ItemAdded);
connect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::ItemRemoved);
foreach (Node* c, f->children()) {
ConnectItem(c);
}
}
}
void ProjectViewModel::DisconnectItem(Node *n)
{
disconnect(n, &Node::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);
disconnect(f, &Folder::BeginInsertItem, this, &ProjectViewModel::ItemAdded);
disconnect(f, &Folder::BeginRemoveItem, this, &ProjectViewModel::ItemRemoved);
foreach (Node* c, f->children()) {
ConnectItem(c);
}
}
}
void ProjectViewModel::FolderBeginInsertItem(Node *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(Node *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()
{
Node* item = static_cast<Node*>(sender());
QModelIndex index = CreateIndexFromItem(item);
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
}
QModelIndex ProjectViewModel::CreateIndexFromItem(Node *item, int column)
{
return createIndex(IndexOfChild(item), column, item);
}
}
+162
View File
@@ -0,0 +1,162 @@
/***
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 VIEWMODEL_H
#define VIEWMODEL_H
#include <QAbstractItemModel>
#include "project.h"
#include "undo/undocommand.h"
#include "node/block/block.h"
namespace olive {
/**
* @brief An adapter that interprets the data in a Project into a Qt item model for usage in ViewModel Views.
*
* Assuming a Project is currently "open" (i.e. the Project is connected to a ProjectExplorer/ProjectPanel through
* a ProjectViewModel), it may be better to make modifications (e.g. additions/removals/renames) through the
* ProjectViewModel so that the views can be efficiently and correctly updated. ProjectViewModel contains several
* "wrapper" functions for Project and Item functions that also signal any connected views to update accordingly.
*/
class ProjectViewModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum ColumnType {
/// Media name
kName,
/// Media duration
kDuration,
/// Media rate (frame rate for video, sample rate for audio)
kRate
};
/**
* @brief ProjectViewModel Constructor
*
* @param parent
* Parent object for memory handling
*/
ProjectViewModel(QObject* parent);
/**
* @brief Get currently active project
*
* @return
*
* Currently active project or nullptr if there is none
*/
Project* project() const;
/**
* @brief Set the project to adapt
*
* Any views attached to this model will get updated by this function.
*
* @param p
*
* Project to adapt, can be set to nullptr to "close" the project (will show an empty model that cannot be modified)
*/
void set_project(Project* p);
/** Compulsory Qt QAbstractItemModel overrides */
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
virtual QModelIndex parent(const QModelIndex &child) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/** Optional Qt QAbstractItemModel overrides */
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
virtual bool canFetchMore(const QModelIndex &parent) const override;
/** Drag and drop support */
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
virtual QStringList mimeTypes() const override;
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;
/**
* @brief Convenience function for creating QModelIndexes from an Item object
*/
QModelIndex CreateIndexFromItem(Node *item, int column = 0);
signals:
void ItemAdded(Node* node);
void ItemRemoved(Node* node);
private:
/**
* @brief Retrieve the index of `item` in its parent
*
* This function will return the index of a specified item in its parent according to whichever sorting algorithm
* is currently active.
*
* @return
*
* Index of the specified item, or -1 if the item is root (in which case it has no parent).
*/
int IndexOfChild(Node* item) const;
/**
* @brief Retrieves the Item object from a given index
*
* A convenience function for retrieving Item objects. If the index is not valid, this returns the root Item.
*/
Node* GetItemObjectFromIndex(const QModelIndex& index) const;
/**
* @brief Check if an Item is a parent of a Child
*
* Checks entire "parent hierarchy" of `child` to see if `parent` is one of its parents.
*/
bool ItemIsParentOfChild(Folder *parent, Node* child) const;
void ConnectItem(Node* n);
void DisconnectItem(Node *n);
Project* project_;
QVector<ColumnType> columns_;
private slots:
void FolderBeginInsertItem(Node *n, int insert_index);
void FolderEndInsertItem();
void FolderBeginRemoveItem(Node* n, int child_index);
void FolderEndRemoveItem();
void ItemRenamed();
};
}
#endif // VIEWMODEL_H
+23
View File
@@ -0,0 +1,23 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2020 Olive Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/project/sequence/sequence.h
node/project/sequence/sequence.cpp
PARENT_SCOPE
)
+173
View File
@@ -0,0 +1,173 @@
/***
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 "sequence.h"
#include <QThread>
#include "panel/timeline/timeline.h"
#include "ui/icons/icons.h"
namespace olive {
const QString Sequence::kTrackInputFormat = QStringLiteral("track_in_%1");
#define super ViewerOutput
Sequence::Sequence()
{
// 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);
}
}
void Sequence::add_default_nodes(MultiUndoCommand* command)
{
// Create tracks and connect them to the viewer
command->add_child(new TimelineAddTrackCommand(track_list(Track::kVideo)));
command->add_child(new TimelineAddTrackCommand(track_list(Track::kAudio)));
}
QIcon Sequence::icon() const
{
return icon::Sequence;
}
QVector<Track *> Sequence::GetUnlockedTracks() const
{
QVector<Track*> tracks = GetTracks();
for (int i=0;i<tracks.size();i++) {
if (tracks.at(i)->IsLocked()) {
tracks.removeAt(i);
i--;
}
}
return tracks;
}
void Sequence::Retranslate()
{
super::Retranslate();
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
{
if (!track_lists_.isEmpty()) {
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)
{
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)
{
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);
}
}
}
}
+124
View File
@@ -0,0 +1,124 @@
/***
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 SEQUENCE_H
#define SEQUENCE_H
#include "node/output/track/tracklist.h"
#include "node/output/viewer/viewer.h"
#include "timeline/timelinepoints.h"
namespace olive {
/**
* @brief The main timeline object, an graph of edited clips that forms a complete edit
*/
class Sequence : public ViewerOutput
{
Q_OBJECT
public:
Sequence();
NODE_DEFAULT_DESTRUCTOR(Sequence)
virtual Node* copy() const override
{
return new Sequence();
}
virtual QString Name() const override
{
return tr("Sequence");
}
virtual QString id() const override
{
return QStringLiteral("org.olivevideoeditor.Olive.sequence");
}
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;
const QVector<Track *> &GetTracks() const
{
return track_cache_;
}
Track* GetTrackFromReference(const Track::Reference& track_ref) const
{
return track_lists_.at(track_ref.type())->GetTrackAt(track_ref.index());
}
/**
* @brief Same as GetTracks() but omits tracks that are locked.
*/
QVector<Track *> GetUnlockedTracks() const;
TrackList* track_list(Track::Type type) const
{
return track_lists_.at(type);
}
virtual void Retranslate() override;
static const QString kTrackInputFormat;
virtual bool IsItem() const override
{
return true;
}
protected:
virtual void ShiftAudioEvent(const rational &from, const rational &to) 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 override;
signals:
void TrackAdded(Track* track);
void TrackRemoved(Track* track);
private:
QVector<TrackList*> track_lists_;
QVector<Track*> track_cache_;
private slots:
void UpdateTrackCache();
};
}
#endif // SEQUENCE_H