diff --git a/README.md b/README.md
index 188acf2..3916052 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,12 @@ Color space conversion:
![Color Space](images/ColorSpace.png)
+Multiple effects on clips, tracks, and stacks:
+
+![Track Effects](images/MultipleEffects.png)
+
+![Multiple Effects Graph](images/MultipleEffectsGraph.svg)
+
Building
========
diff --git a/data/Generator.otio b/data/Generator.otio
index 06b9457..20a7954 100644
--- a/data/Generator.otio
+++ b/data/Generator.otio
@@ -46,6 +46,82 @@
},
"name": "Fill"
},
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "GeneratorReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "generator_kind": "Fill",
+ "parameters": {
+ "size": [ 1280, 720 ],
+ "color": [ 0.0, 1.0, 0.0, 1.0 ]
+ }
+ },
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Fill"
+ },
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "GeneratorReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "generator_kind": "Fill",
+ "parameters": {
+ "size": [ 1280, 720 ],
+ "color": [ 0.0, 0.0, 1.0, 1.0 ]
+ }
+ },
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Fill"
+ },
{
"OTIO_SCHEMA": "Clip.1",
"media_reference": {
@@ -103,6 +179,86 @@
"value": 0
}
},
+ "generator_kind": "Checkers",
+ "parameters": {
+ "size": [ 1280, 720 ],
+ "checkerSize": [ 200, 200 ],
+ "color1": [ 1.0, 1.0, 1.0, 1.0 ],
+ "color2": [ 0.0, 0.0, 0.0, 1.0 ]
+ }
+ },
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Checkers"
+ },
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "GeneratorReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "generator_kind": "Checkers",
+ "parameters": {
+ "size": [ 1280, 720 ],
+ "checkerSize": [ 300, 300 ],
+ "color1": [ 1.0, 1.0, 1.0, 1.0 ],
+ "color2": [ 0.0, 0.0, 0.0, 1.0 ]
+ }
+ },
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 1
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Checkers"
+ },
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "GeneratorReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 3
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
"generator_kind": "Noise",
"parameters": {
"size": [ 1280, 720 ],
@@ -118,7 +274,7 @@
"duration": {
"OTIO_SCHEMA": "RationalTime.1",
"rate": 24,
- "value": 1
+ "value": 3
},
"start_time": {
"OTIO_SCHEMA": "RationalTime.1",
diff --git a/data/MultipleEffects.otio b/data/MultipleEffects.otio
new file mode 100644
index 0000000..565599a
--- /dev/null
+++ b/data/MultipleEffects.otio
@@ -0,0 +1,122 @@
+{
+ "OTIO_SCHEMA": "Timeline.1",
+ "metadata": {},
+ "name": "MultipleEffects",
+ "tracks": {
+ "OTIO_SCHEMA": "Stack.1",
+ "effects": [
+ {
+ "OTIO_SCHEMA": "PowEffect.1",
+ "effect_name": "",
+ "value": 2
+ }
+ ],
+ "children": [
+ {
+ "OTIO_SCHEMA": "Track.1",
+ "effects": [
+ {
+ "OTIO_SCHEMA": "InvertEffect.1",
+ "effect_name": "",
+ "value": 0.0
+ }
+ ],
+ "children": [
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "ExternalReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 5
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "target_url": "Charlie.jpg"
+ },
+ "effects": [
+ {
+ "OTIO_SCHEMA": "FlipEffect.1",
+ "effect_name": ""
+ },
+ {
+ "OTIO_SCHEMA": "BlurEffect.1",
+ "effect_name": "",
+ "radius": 30
+ }
+ ],
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 5
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Charlie"
+ },
+ {
+ "OTIO_SCHEMA": "Clip.1",
+ "media_reference": {
+ "OTIO_SCHEMA": "ExternalReference.1",
+ "available_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 5
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "target_url": "Charlie.jpg"
+ },
+ "effects": [
+ {
+ "OTIO_SCHEMA": "FlopEffect.1",
+ "effect_name": ""
+ },
+ {
+ "OTIO_SCHEMA": "ColorMapEffect.1",
+ "effect_name": "",
+ "map_name": "plasma"
+ }
+ ],
+ "source_range": {
+ "OTIO_SCHEMA": "TimeRange.1",
+ "duration": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 5
+ },
+ "start_time": {
+ "OTIO_SCHEMA": "RationalTime.1",
+ "rate": 24,
+ "value": 0
+ }
+ },
+ "name": "Charlie"
+ }
+ ],
+ "kind": "Video",
+ "name": "Video"
+ }
+ ],
+ "name": "Stack"
+ }
+}
diff --git a/images/Generator.png b/images/Generator.png
index 2046116..61f3809 100644
Binary files a/images/Generator.png and b/images/Generator.png differ
diff --git a/images/MultipleEffects.png b/images/MultipleEffects.png
new file mode 100644
index 0000000..317ca13
Binary files /dev/null and b/images/MultipleEffects.png differ
diff --git a/images/MultipleEffectsGraph.dot b/images/MultipleEffectsGraph.dot
new file mode 100644
index 0000000..d882637
--- /dev/null
+++ b/images/MultipleEffectsGraph.dot
@@ -0,0 +1,16 @@
+digraph MultipleEffects {
+ node [shape=box, fontsize=12, margin=0.05, width=0, height=0];
+ Pow_2560849241088 [label="Pow"]
+ Comp_2560849246288 -> Pow_2560849241088
+ Comp_2560849246288 [label="Comp"]
+ Invert_2560932117488 -> Comp_2560849246288
+ Invert_2560932117488 [label="Invert"]
+ Blur_2560849246080 -> Invert_2560932117488
+ Blur_2560849246080 [label="Blur"]
+ Flip_2560932119216 -> Blur_2560849246080
+ Flip_2560932119216 [label="Flip"]
+ Read_2560840968656 -> Flip_2560932119216
+ Read_2560840968656 [label="Read: Charlie.jpg"]
+ Fill_2560840970288 -> Comp_2560849246288
+ Fill_2560840970288 [label="Fill: 0, 0, 0, 1"]
+}
\ No newline at end of file
diff --git a/images/MultipleEffectsGraph.svg b/images/MultipleEffectsGraph.svg
new file mode 100644
index 0000000..5f0026c
--- /dev/null
+++ b/images/MultipleEffectsGraph.svg
@@ -0,0 +1,91 @@
+
+
+
+
+
diff --git a/lib/toucan/TimelineGraph.cpp b/lib/toucan/TimelineGraph.cpp
index f571580..b4bdf25 100644
--- a/lib/toucan/TimelineGraph.cpp
+++ b/lib/toucan/TimelineGraph.cpp
@@ -95,13 +95,21 @@ namespace toucan
FillData{ _imageSize, IMATH_NAMESPACE::V4f(0.F, 0.F, 0.F, 1.F )});
// Loop over the tracks.
- for (const auto& i : _timeline->tracks()->children())
+ auto stack = _timeline->tracks();
+ for (const auto& i : stack->children())
{
if (auto track = OTIO_NS::dynamic_retainer_cast(i))
{
// Process this track.
auto trackNode = _track(time, track);
+ // Get the track effects.
+ const auto& effects = track->effects();
+ if (!effects.empty())
+ {
+ trackNode = _effects(effects, trackNode);
+ }
+
// Composite this track over the previous track.
std::vector > nodes;
if (trackNode)
@@ -118,6 +126,13 @@ namespace toucan
}
}
+ // Get the stack effects.
+ const auto& effects = stack->effects();
+ if (!effects.empty())
+ {
+ node = _effects(effects, node);
+ }
+
return node;
}
@@ -326,20 +341,10 @@ namespace toucan
}
// Get the effects.
- for (const auto& effect : item->effects())
+ const auto& effects = item->effects();
+ if (!effects.empty())
{
- if (auto iEffect = dynamic_cast(effect.value))
- {
- auto effectNode = iEffect->createNode({ out });
- out = effectNode;
- }
- else if (auto linearTimeWarp = dynamic_cast(effect.value))
- {
- auto linearTimeWarpNode = std::make_shared(
- static_cast(linearTimeWarp->time_scalar()),
- std::vector >{ out });
- out = linearTimeWarpNode;
- }
+ out = _effects(effects, out);
}
if (out)
{
@@ -375,4 +380,27 @@ namespace toucan
}
return out;
}
+
+ std::shared_ptr TimelineGraph::_effects(
+ const std::vector >& effects,
+ const std::shared_ptr& input) const
+ {
+ std::shared_ptr out = input;
+ for (const auto& effect : effects)
+ {
+ if (auto iEffect = dynamic_cast(effect.value))
+ {
+ auto effectNode = iEffect->createNode({ out });
+ out = effectNode;
+ }
+ else if (auto linearTimeWarp = dynamic_cast(effect.value))
+ {
+ auto linearTimeWarpNode = std::make_shared(
+ static_cast(linearTimeWarp->time_scalar()),
+ std::vector >{ out });
+ out = linearTimeWarpNode;
+ }
+ }
+ return out;
+ }
}
diff --git a/lib/toucan/TimelineGraph.h b/lib/toucan/TimelineGraph.h
index c1bed8e..37c7d22 100644
--- a/lib/toucan/TimelineGraph.h
+++ b/lib/toucan/TimelineGraph.h
@@ -51,6 +51,9 @@ namespace toucan
const OTIO_NS::SerializableObject::Retainer&,
const OTIO_NS::TimeRange& trimmedRangeInParent,
const std::vector >&) const;
+ std::shared_ptr _effects(
+ const std::vector >&,
+ const std::shared_ptr&) const;
std::filesystem::path _path;
OTIO_NS::SerializableObject::Retainer _timeline;
diff --git a/tests/TimelineGraphTest.cpp b/tests/TimelineGraphTest.cpp
index 3fe5e4b..f14e8e3 100644
--- a/tests/TimelineGraphTest.cpp
+++ b/tests/TimelineGraphTest.cpp
@@ -25,6 +25,7 @@ namespace toucan
"Gap",
"Generator",
"LinearTimeWarp",
+ "MultipleEffects",
"Transform",
"Transition",
"Transition2"