otio: implemented some basic import/export functionality
Certainly not complete, but mild import/export capability.
This commit is contained in:
@@ -20,8 +20,14 @@
|
|||||||
|
|
||||||
#include "otiodecoder.h"
|
#include "otiodecoder.h"
|
||||||
|
|
||||||
|
#include <opentimelineio/clip.h>
|
||||||
|
#include <opentimelineio/externalReference.h>
|
||||||
#include <opentimelineio/timeline.h>
|
#include <opentimelineio/timeline.h>
|
||||||
|
|
||||||
|
#include "node/block/clip/clip.h"
|
||||||
|
#include "node/block/gap/gap.h"
|
||||||
|
#include "project/item/sequence/sequence.h"
|
||||||
|
|
||||||
OLIVE_NAMESPACE_ENTER
|
OLIVE_NAMESPACE_ENTER
|
||||||
|
|
||||||
OTIODecoder::OTIODecoder()
|
OTIODecoder::OTIODecoder()
|
||||||
@@ -45,7 +51,70 @@ ItemPtr OTIODecoder::Probe(const QString& filename, const QAtomicInt* cancelled)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Found" << timeline->video_tracks().size() << "video tracks";
|
SequencePtr sequence = std::make_shared<Sequence>();
|
||||||
|
|
||||||
|
// FIXME: As far as I know, OTIO doesn't store video/audio parameters?
|
||||||
|
sequence->set_default_parameters();
|
||||||
|
|
||||||
|
sequence->set_name(QString::fromStdString(timeline->name()));
|
||||||
|
|
||||||
|
for (auto c : timeline->tracks()->children()) {
|
||||||
|
auto otio_track = static_cast<opentimelineio::v1_0::Track*>(c.value);
|
||||||
|
|
||||||
|
// Create a new track
|
||||||
|
TrackOutput* track = nullptr;
|
||||||
|
|
||||||
|
// Determine what kind of track it is
|
||||||
|
if (otio_track->kind() == "Video") {
|
||||||
|
track = sequence->viewer_output()->track_list(Timeline::kTrackTypeVideo)->AddTrack();
|
||||||
|
} else if (otio_track->kind() == "Audio") {
|
||||||
|
track = sequence->viewer_output()->track_list(Timeline::kTrackTypeAudio)->AddTrack();
|
||||||
|
} else {
|
||||||
|
qWarning() << "Found unknown track type:" << otio_track->kind().c_str();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get clips from track
|
||||||
|
std::map<opentimelineio::v1_0::Composable*, opentimelineio::v1_0::TimeRange> clip_map = otio_track->range_of_all_children(&es);
|
||||||
|
if (es != opentimelineio::v1_0::ErrorStatus::OK) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it=clip_map.cbegin(); it!=clip_map.cend(); it++) {
|
||||||
|
|
||||||
|
Block* block = nullptr;
|
||||||
|
|
||||||
|
if (it->first->schema_name() == "Clip") {
|
||||||
|
|
||||||
|
block = new ClipBlock();
|
||||||
|
|
||||||
|
auto otio_clip = static_cast<opentimelineio::v1_0::Clip*>(it->first);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (otio_clip->media_reference()->schema_name() == "ExternalReference") {
|
||||||
|
QString footage_url = QString::fromStdString(static_cast<opentimelineio::v1_0::ExternalReference*>(otio_clip->media_reference())->target_url());
|
||||||
|
ItemPtr footage = Decoder::ProbeMedia(footage_url, cancelled);
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
} else if (it->first->schema_name() == "Gap") {
|
||||||
|
|
||||||
|
block = new GapBlock();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
qWarning() << "Found unknown block type:" << it->first->schema_name().c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
block->SetLabel(QString::fromStdString(it->first->name()));
|
||||||
|
block->set_length_and_media_out(rational::fromDouble(it->second.duration().to_seconds()));
|
||||||
|
sequence->AddNode(block);
|
||||||
|
track->AppendBlock(block);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
+52
-12
@@ -276,11 +276,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
QList<TimeRange> TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction);
|
QList<TimeRange> TransformTimeTo(const TimeRange& time, Node* target, NodeParam::Type direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find nodes of a certain type that this Node takes inputs from
|
||||||
|
*/
|
||||||
|
template<class T>
|
||||||
|
QList<T*> FindInputNodes() const;
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
/**
|
/**
|
||||||
* @brief Find a node of a certain type that this Node outputs to
|
* @brief Find a node of a certain type that this Node outputs to
|
||||||
*/
|
*/
|
||||||
T* FindOutputNode();
|
QList<T *> FindOutputNode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert a pointer to a value that can be sent between NodeParams
|
* @brief Convert a pointer to a value that can be sent between NodeParams
|
||||||
@@ -471,6 +477,12 @@ private:
|
|||||||
|
|
||||||
void DisconnectInput(NodeInput* input);
|
void DisconnectInput(NodeInput* input);
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
static void FindInputNodeInternal(const Node* n, QList<T *>& list);
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
static void FindOutputNodeInternal(const Node* n, QList<T *>& list);
|
||||||
|
|
||||||
QList<Node *> GetDependenciesInternal(bool traverse, bool exclusive_only) const;
|
QList<Node *> GetDependenciesInternal(bool traverse, bool exclusive_only) const;
|
||||||
|
|
||||||
QList<NodeParam *> params_;
|
QList<NodeParam *> params_;
|
||||||
@@ -497,6 +509,35 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void Node::FindInputNodeInternal(const Node* n, QList<T *>& list)
|
||||||
|
{
|
||||||
|
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
|
||||||
|
|
||||||
|
foreach (NodeInput* input, inputs) {
|
||||||
|
if (input->is_connected()) {
|
||||||
|
Node* connected = input->get_connected_node();
|
||||||
|
T* cast_test = dynamic_cast<T*>(connected);
|
||||||
|
|
||||||
|
if (cast_test) {
|
||||||
|
list.append(cast_test);
|
||||||
|
}
|
||||||
|
|
||||||
|
FindInputNodeInternal<T>(connected, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
QList<T *> Node::FindInputNodes() const
|
||||||
|
{
|
||||||
|
QList<T *> list;
|
||||||
|
|
||||||
|
FindInputNodeInternal<T>(this, list);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
T* Node::ValueToPtr(const QVariant &ptr)
|
T* Node::ValueToPtr(const QVariant &ptr)
|
||||||
{
|
{
|
||||||
@@ -504,28 +545,27 @@ T* Node::ValueToPtr(const QVariant &ptr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
Node* FindOutputNodeInternal(Node* n) {
|
void Node::FindOutputNodeInternal(const Node* n, QList<T *>& list) {
|
||||||
foreach (NodeEdgePtr edge, n->output()->edges()) {
|
foreach (NodeEdgePtr edge, n->output()->edges()) {
|
||||||
Node* connected = edge->input()->parentNode();
|
Node* connected = edge->input()->parentNode();
|
||||||
T* cast_test = dynamic_cast<T*>(connected);
|
T* cast_test = dynamic_cast<T*>(connected);
|
||||||
|
|
||||||
if (cast_test) {
|
if (cast_test) {
|
||||||
return cast_test;
|
list.append(cast_test);
|
||||||
} else {
|
|
||||||
Node* drill_test = FindOutputNodeInternal<T>(connected);
|
|
||||||
if (drill_test) {
|
|
||||||
return drill_test;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
FindOutputNodeInternal<T>(connected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
T* Node::FindOutputNode()
|
QList<T *> Node::FindOutputNode()
|
||||||
{
|
{
|
||||||
return static_cast<T*>(FindOutputNodeInternal<T>(this));
|
QList<T *> list;
|
||||||
|
|
||||||
|
FindOutputNodeInternal<T>(this, list);
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
OLIVE_NAMESPACE_EXIT
|
OLIVE_NAMESPACE_EXIT
|
||||||
|
|||||||
@@ -21,11 +21,12 @@
|
|||||||
#include "exportotiotask.h"
|
#include "exportotiotask.h"
|
||||||
|
|
||||||
#include <opentimelineio/clip.h>
|
#include <opentimelineio/clip.h>
|
||||||
|
#include <opentimelineio/externalReference.h>
|
||||||
#include <opentimelineio/gap.h>
|
#include <opentimelineio/gap.h>
|
||||||
#include <opentimelineio/timeline.h>
|
|
||||||
#include <opentimelineio/transition.h>
|
#include <opentimelineio/transition.h>
|
||||||
|
|
||||||
#include "node/block/transition/transition.h"
|
#include "node/block/transition/transition.h"
|
||||||
|
#include "node/input/media/media.h"
|
||||||
|
|
||||||
OLIVE_NAMESPACE_ENTER
|
OLIVE_NAMESPACE_ENTER
|
||||||
|
|
||||||
@@ -42,59 +43,107 @@ bool ExportOTIOTask::Run()
|
|||||||
|
|
||||||
opentimelineio::v1_0::ErrorStatus es;
|
opentimelineio::v1_0::ErrorStatus es;
|
||||||
|
|
||||||
foreach (TrackOutput* track, sequence_->track_list(Timeline::kTrackTypeVideo)->GetTracks()) {
|
if (!SerializeTrackList(sequence_->track_list(Timeline::kTrackTypeVideo), otio_timeline)
|
||||||
auto otio_track = new opentimelineio::v1_0::Track();
|
|| !SerializeTrackList(sequence_->track_list(Timeline::kTrackTypeAudio), otio_timeline)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
otio_timeline->to_json_file(filename_.toStdString(), &es);
|
||||||
|
|
||||||
|
return (es == opentimelineio::v1_0::ErrorStatus::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
opentimelineio::v1_0::Track *ExportOTIOTask::SerializeTrack(TrackOutput *track)
|
||||||
|
{
|
||||||
|
auto otio_track = new opentimelineio::v1_0::Track();
|
||||||
|
|
||||||
|
opentimelineio::v1_0::ErrorStatus es;
|
||||||
|
|
||||||
|
switch (track->track_type()) {
|
||||||
|
case Timeline::kTrackTypeVideo:
|
||||||
otio_track->set_kind("Video");
|
otio_track->set_kind("Video");
|
||||||
|
break;
|
||||||
|
case Timeline::kTrackTypeAudio:
|
||||||
|
otio_track->set_kind("Audio");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qWarning() << "Don't know OTIO track kind for native type" << track->track_type();
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Block* block, track->Blocks()) {
|
foreach (Block* block, track->Blocks()) {
|
||||||
opentimelineio::v1_0::Composable* otio_block = nullptr;
|
opentimelineio::v1_0::Composable* otio_block = nullptr;
|
||||||
|
|
||||||
switch (block->type()) {
|
switch (block->type()) {
|
||||||
case Block::kClip:
|
case Block::kClip:
|
||||||
{
|
{
|
||||||
auto otio_clip = new opentimelineio::v1_0::Clip();
|
auto otio_clip = new opentimelineio::v1_0::Clip();
|
||||||
|
|
||||||
otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
|
otio_clip->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
|
||||||
block->length().toRationalTime()));
|
block->length().toRationalTime()));
|
||||||
|
|
||||||
otio_block = otio_clip;
|
QList<MediaInput*> media_nodes = block->FindInputNodes<MediaInput>();
|
||||||
break;
|
if (!media_nodes.isEmpty()) {
|
||||||
}
|
auto media_ref = new opentimelineio::v1_0::ExternalReference(media_nodes.first()->footage()->footage()->filename().toStdString());
|
||||||
case Block::kGap:
|
otio_clip->set_media_reference(media_ref);
|
||||||
{
|
|
||||||
auto otio_gap = new opentimelineio::v1_0::Gap();
|
|
||||||
|
|
||||||
otio_gap->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
|
|
||||||
block->length().toRationalTime()));
|
|
||||||
|
|
||||||
otio_block = otio_gap;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Block::kTransition:
|
|
||||||
{
|
|
||||||
auto otio_transition = new opentimelineio::v1_0::Transition();
|
|
||||||
|
|
||||||
TransitionBlock* our_transition = static_cast<TransitionBlock*>(block);
|
|
||||||
|
|
||||||
otio_transition->set_in_offset(our_transition->in_offset().toRationalTime());
|
|
||||||
otio_transition->set_out_offset(our_transition->out_offset().toRationalTime());
|
|
||||||
|
|
||||||
otio_block = new opentimelineio::v1_0::Transition();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!otio_block) {
|
otio_block = otio_clip;
|
||||||
// We shouldn't ever get here, but catch without crashing if we ever do
|
break;
|
||||||
return false;
|
}
|
||||||
}
|
case Block::kGap:
|
||||||
|
{
|
||||||
|
auto otio_gap = new opentimelineio::v1_0::Gap();
|
||||||
|
|
||||||
otio_track->append_child(otio_block, &es);
|
otio_gap->set_source_range(opentimelineio::v1_0::TimeRange(block->in().toRationalTime(),
|
||||||
|
block->length().toRationalTime()));
|
||||||
|
|
||||||
if (es != opentimelineio::v1_0::ErrorStatus::OK) {
|
otio_block = otio_gap;
|
||||||
return false;
|
break;
|
||||||
}
|
}
|
||||||
|
case Block::kTransition:
|
||||||
|
{
|
||||||
|
auto otio_transition = new opentimelineio::v1_0::Transition();
|
||||||
|
|
||||||
|
TransitionBlock* our_transition = static_cast<TransitionBlock*>(block);
|
||||||
|
|
||||||
|
otio_transition->set_in_offset(our_transition->in_offset().toRationalTime());
|
||||||
|
otio_transition->set_out_offset(our_transition->out_offset().toRationalTime());
|
||||||
|
|
||||||
|
otio_block = new opentimelineio::v1_0::Transition();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!otio_block) {
|
||||||
|
// We shouldn't ever get here, but catch without crashing if we ever do
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
otio_track->append_child(otio_block, &es);
|
||||||
|
|
||||||
|
if (es != opentimelineio::v1_0::ErrorStatus::OK) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return otio_track;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
otio_track->possibly_delete();
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExportOTIOTask::SerializeTrackList(TrackList *list, opentimelineio::v1_0::Timeline* otio_timeline)
|
||||||
|
{
|
||||||
|
opentimelineio::v1_0::ErrorStatus es;
|
||||||
|
|
||||||
|
foreach (TrackOutput* track, list->GetTracks()) {
|
||||||
|
auto otio_track = SerializeTrack(track);
|
||||||
|
|
||||||
|
if (!otio_track) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
otio_timeline->tracks()->append_child(otio_track, &es);
|
otio_timeline->tracks()->append_child(otio_track, &es);
|
||||||
@@ -104,9 +153,7 @@ bool ExportOTIOTask::Run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
otio_timeline->to_json_file(filename_.toStdString(), &es);
|
return true;
|
||||||
|
|
||||||
return (es == opentimelineio::v1_0::ErrorStatus::OK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OLIVE_NAMESPACE_EXIT
|
OLIVE_NAMESPACE_EXIT
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
#ifndef PROJECTSAVEASOTIOTASK_H
|
#ifndef PROJECTSAVEASOTIOTASK_H
|
||||||
#define PROJECTSAVEASOTIOTASK_H
|
#define PROJECTSAVEASOTIOTASK_H
|
||||||
|
|
||||||
|
#include <opentimelineio/timeline.h>
|
||||||
|
#include <opentimelineio/track.h>
|
||||||
|
|
||||||
#include "project/item/sequence/sequence.h"
|
#include "project/item/sequence/sequence.h"
|
||||||
#include "task/task.h"
|
#include "task/task.h"
|
||||||
|
|
||||||
@@ -36,6 +39,10 @@ protected:
|
|||||||
virtual bool Run() override;
|
virtual bool Run() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
opentimelineio::v1_0::Track* SerializeTrack(TrackOutput* track);
|
||||||
|
|
||||||
|
bool SerializeTrackList(TrackList* list, opentimelineio::v1_0::Timeline *otio_timeline);
|
||||||
|
|
||||||
ViewerOutput* sequence_;
|
ViewerOutput* sequence_;
|
||||||
|
|
||||||
QString filename_;
|
QString filename_;
|
||||||
|
|||||||
Reference in New Issue
Block a user