moved several files around
Mostly moved classes that became node derivatives into the node/ folder.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
add_subdirectory(audio)
|
||||
add_subdirectory(block)
|
||||
add_subdirectory(color)
|
||||
add_subdirectory(distort)
|
||||
add_subdirectory(filter)
|
||||
add_subdirectory(generator)
|
||||
|
||||
@@ -31,6 +31,8 @@ class PanNode : public Node
|
||||
public:
|
||||
PanNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(PanNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -31,6 +31,8 @@ class VolumeNode : public MathNodeBase
|
||||
public:
|
||||
VolumeNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(VolumeNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -37,6 +37,8 @@ class Block : public Node
|
||||
public:
|
||||
Block();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(Block)
|
||||
|
||||
enum Type {
|
||||
kClip,
|
||||
kGap,
|
||||
|
||||
@@ -34,6 +34,8 @@ class ClipBlock : public Block
|
||||
public:
|
||||
ClipBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(ClipBlock)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual Type type() const override;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,6 +31,8 @@ class TransitionBlock : public Block
|
||||
public:
|
||||
TransitionBlock();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TransitionBlock)
|
||||
|
||||
virtual Type type() const override;
|
||||
|
||||
virtual void Retranslate() override;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -31,6 +31,8 @@ class StrokeFilterNode : public Node
|
||||
public:
|
||||
StrokeFilterNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(StrokeFilterNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -34,6 +34,8 @@ class MatrixGenerator : public Node
|
||||
public:
|
||||
MatrixGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(MatrixGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -32,6 +32,8 @@ class PolygonGenerator : public Node
|
||||
public:
|
||||
PolygonGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(PolygonGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -31,6 +31,8 @@ class SolidGenerator : public Node
|
||||
public:
|
||||
SolidGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(SolidGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -31,6 +31,8 @@ class TextGenerator : public Node
|
||||
public:
|
||||
TextGenerator();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TextGenerator)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -31,6 +31,8 @@ class TimeInput : public Node
|
||||
public:
|
||||
TimeInput();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(TimeInput)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -31,6 +31,8 @@ class MathNode : public MathNodeBase
|
||||
public:
|
||||
MathNode();
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(MathNode)
|
||||
|
||||
virtual Node* copy() const override;
|
||||
|
||||
virtual QString Name() const override;
|
||||
|
||||
@@ -30,6 +30,8 @@ class MathNodeBase : public Node
|
||||
public:
|
||||
MathNodeBase() = default;
|
||||
|
||||
NODE_DEFAULT_DESTRUCTOR(MathNodeBase)
|
||||
|
||||
enum Operation {
|
||||
kOpAdd,
|
||||
kOpSubtract,
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define NODE_DEFAULT_DESTRUCTOR(x) virtual ~x() override {DisconnectAll();}
|
||||
|
||||
class NodeGraph;
|
||||
class Folder;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "project/item/sequence/sequence.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -55,11 +55,6 @@ Track::Track() :
|
||||
track_height_ = kTrackHeightDefault;
|
||||
}
|
||||
|
||||
Track::~Track()
|
||||
{
|
||||
DisconnectAll();
|
||||
}
|
||||
|
||||
void Track::set_type(const Type &track_type)
|
||||
{
|
||||
track_type_ = track_type;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 ¶ms) 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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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*>(¶ms.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();
|
||||
}*/
|
||||
|
||||
}
|
||||
@@ -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 ×tamp() 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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user