Skip to content

Commit 76fd2c0

Browse files
rogernelsonjminor
authored andcommitted
Media Multi-Reference Feature (#1241)
* Upgraded the Clip API and schema to support multiple media references in a single Clip. * New API is introduced to allow for setting a dictionary of media references indexed by a string key, and API for choosing which of those is active. The old API for getting/setting a single media reference is retained for backwards compatibility, and for use cases where only a single media reference is needed. * Note that the Clip schema version is updated to Clip.2, which is not supported by older versions of OTIO.
1 parent be7cdce commit 76fd2c0

File tree

16 files changed

+505
-36
lines changed

16 files changed

+505
-36
lines changed

docs/tutorials/otio-serialized-schema-only-fields.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,14 @@ parameters:
131131

132132
## Module: opentimelineio.schema
133133

134-
### Clip.1
134+
### Clip.2
135135

136136
parameters:
137+
- *active_media_reference_key*
137138
- *effects*
138139
- *enabled*
139140
- *markers*
140-
- *media_reference*
141+
- *media_references*
141142
- *metadata*
142143
- *name*
143144
- *source_range*

docs/tutorials/otio-serialized-schema.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ parameters:
260260

261261
## Module: opentimelineio.schema
262262

263-
### Clip.1
263+
### Clip.2
264264

265265
*full module path*: `opentimelineio.schema.Clip`
266266

@@ -271,10 +271,11 @@ None
271271
```
272272

273273
parameters:
274+
- *active_media_reference_key*:
274275
- *effects*:
275276
- *enabled*: If true, an Item contributes to compositions. Analogous to Mute in various NLEs.
276277
- *markers*:
277-
- *media_reference*:
278+
- *media_references*:
278279
- *metadata*:
279280
- *name*:
280281
- *source_range*:

src/opentimelineio/clip.cpp

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33

44
namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
55

6+
char constexpr Clip::default_media_key[];
7+
68
Clip::Clip(
79
std::string const& name,
810
MediaReference* media_reference,
911
optional<TimeRange> const& source_range,
10-
AnyDictionary const& metadata)
12+
AnyDictionary const& metadata,
13+
std::string const& active_media_reference_key)
1114
: Parent{ name, source_range, metadata }
15+
, _active_media_reference_key(active_media_reference_key)
1216
{
1317
set_media_reference(media_reference);
1418
}
@@ -19,33 +23,136 @@ Clip::~Clip()
1923
MediaReference*
2024
Clip::media_reference() const noexcept
2125
{
22-
return _media_reference;
26+
auto active = _media_references.find(_active_media_reference_key);
27+
return active == _media_references.end() || !active->second
28+
? nullptr
29+
: active->second;
30+
}
31+
32+
Clip::MediaReferences
33+
Clip::media_references() const noexcept
34+
{
35+
MediaReferences result;
36+
for (auto const& m: _media_references)
37+
{
38+
result.insert(
39+
{ m.first, dynamic_retainer_cast<MediaReference>(m.second) });
40+
}
41+
return result;
42+
}
43+
44+
template <typename MediaRefMap>
45+
bool
46+
Clip::check_for_valid_media_reference_key(
47+
std::string const& caller,
48+
std::string const& key,
49+
MediaRefMap const& media_references,
50+
ErrorStatus* error_status)
51+
{
52+
auto empty_key = media_references.find("");
53+
if (empty_key != media_references.end())
54+
{
55+
if (error_status)
56+
{
57+
*error_status = ErrorStatus(
58+
ErrorStatus::MEDIA_REFERENCES_CONTAIN_EMPTY_KEY,
59+
caller +
60+
" failed because the media references contain an empty string key",
61+
this);
62+
}
63+
return false;
64+
}
65+
66+
auto found = media_references.find(key);
67+
if (found == media_references.end())
68+
{
69+
if (error_status)
70+
{
71+
*error_status = ErrorStatus(
72+
ErrorStatus::MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY,
73+
caller +
74+
" failed because the media references do not contain the active key",
75+
this);
76+
}
77+
return false;
78+
}
79+
return true;
80+
}
81+
82+
void
83+
Clip::set_media_references(
84+
MediaReferences const& media_references,
85+
std::string const& new_active_key,
86+
ErrorStatus* error_status) noexcept
87+
{
88+
if (!check_for_valid_media_reference_key(
89+
"set_media_references",
90+
new_active_key,
91+
media_references,
92+
error_status))
93+
{
94+
return;
95+
}
96+
97+
_media_references.clear();
98+
for (auto const& m: media_references)
99+
{
100+
_media_references[m.first] = m.second ? m.second : new MissingReference;
101+
}
102+
103+
_active_media_reference_key = new_active_key;
104+
}
105+
106+
std::string
107+
Clip::active_media_reference_key() const noexcept
108+
{
109+
return _active_media_reference_key;
110+
}
111+
112+
void
113+
Clip::set_active_media_reference_key(
114+
std::string const& new_active_key, ErrorStatus* error_status) noexcept
115+
{
116+
if (!check_for_valid_media_reference_key(
117+
"set_active_media_reference_key",
118+
new_active_key,
119+
_media_references,
120+
error_status))
121+
{
122+
return;
123+
}
124+
_active_media_reference_key = new_active_key;
23125
}
24126

25127
void
26128
Clip::set_media_reference(MediaReference* media_reference)
27129
{
28-
_media_reference = media_reference ? media_reference : new MissingReference;
130+
_media_references[_active_media_reference_key] =
131+
media_reference ? media_reference : new MissingReference;
29132
}
30133

31134
bool
32135
Clip::read_from(Reader& reader)
33136
{
34-
return reader.read("media_reference", &_media_reference) &&
137+
return reader.read("media_references", &_media_references) &&
138+
reader.read(
139+
"active_media_reference_key", &_active_media_reference_key) &&
35140
Parent::read_from(reader);
36141
}
37142

38143
void
39144
Clip::write_to(Writer& writer) const
40145
{
41146
Parent::write_to(writer);
42-
writer.write("media_reference", _media_reference);
147+
writer.write("media_references", _media_references);
148+
writer.write("active_media_reference_key", _active_media_reference_key);
43149
}
44150

45151
TimeRange
46152
Clip::available_range(ErrorStatus* error_status) const
47153
{
48-
if (!_media_reference)
154+
auto active_media = media_reference();
155+
if (!active_media)
49156
{
50157
if (error_status)
51158
{
@@ -57,7 +164,7 @@ Clip::available_range(ErrorStatus* error_status) const
57164
return TimeRange();
58165
}
59166

60-
if (!_media_reference->available_range())
167+
if (!active_media->available_range())
61168
{
62169
if (error_status)
63170
{
@@ -69,13 +176,14 @@ Clip::available_range(ErrorStatus* error_status) const
69176
return TimeRange();
70177
}
71178

72-
return _media_reference->available_range().value();
179+
return active_media->available_range().value();
73180
}
74181

75182
optional<Imath::Box2d>
76183
Clip::available_image_bounds(ErrorStatus* error_status) const
77184
{
78-
if (!_media_reference)
185+
auto active_media = media_reference();
186+
if (!active_media)
79187
{
80188
*error_status = ErrorStatus(
81189
ErrorStatus::CANNOT_COMPUTE_BOUNDS,
@@ -84,7 +192,7 @@ Clip::available_image_bounds(ErrorStatus* error_status) const
84192
return optional<Imath::Box2d>();
85193
}
86194

87-
if (!_media_reference.value->available_image_bounds())
195+
if (!active_media->available_image_bounds())
88196
{
89197
*error_status = ErrorStatus(
90198
ErrorStatus::CANNOT_COMPUTE_BOUNDS,
@@ -93,7 +201,7 @@ Clip::available_image_bounds(ErrorStatus* error_status) const
93201
return optional<Imath::Box2d>();
94202
}
95203

96-
return _media_reference.value->available_image_bounds();
204+
return active_media->available_image_bounds();
97205
}
98206

99207
}} // namespace opentimelineio::OPENTIMELINEIO_VERSION

src/opentimelineio/clip.h

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,39 @@ namespace opentimelineio { namespace OPENTIMELINEIO_VERSION {
99
class Clip : public Item
1010
{
1111
public:
12+
static char constexpr default_media_key[] = "DEFAULT_MEDIA";
13+
1214
struct Schema
1315
{
1416
static auto constexpr name = "Clip";
15-
static int constexpr version = 1;
17+
static int constexpr version = 2;
1618
};
1719

1820
using Parent = Item;
1921

2022
Clip(
21-
std::string const& name = std::string(),
22-
MediaReference* media_reference = nullptr,
23-
optional<TimeRange> const& source_range = nullopt,
24-
AnyDictionary const& metadata = AnyDictionary());
25-
26-
void set_media_reference(MediaReference* media_reference);
23+
std::string const& name = std::string(),
24+
MediaReference* media_reference = nullptr,
25+
optional<TimeRange> const& source_range = nullopt,
26+
AnyDictionary const& metadata = AnyDictionary(),
27+
std::string const& active_media_reference_key = default_media_key);
2728

29+
void set_media_reference(MediaReference* media_reference);
2830
MediaReference* media_reference() const noexcept;
2931

32+
using MediaReferences = std::map<std::string, MediaReference*>;
33+
34+
MediaReferences media_references() const noexcept;
35+
void set_media_references(
36+
MediaReferences const& media_references,
37+
std::string const& new_active_key,
38+
ErrorStatus* error_status = nullptr) noexcept;
39+
40+
std::string active_media_reference_key() const noexcept;
41+
void set_active_media_reference_key(
42+
std::string const& new_active_key,
43+
ErrorStatus* error_status = nullptr) noexcept;
44+
3045
virtual TimeRange
3146
available_range(ErrorStatus* error_status = nullptr) const;
3247

@@ -40,7 +55,16 @@ class Clip : public Item
4055
virtual void write_to(Writer&) const;
4156

4257
private:
43-
Retainer<MediaReference> _media_reference;
58+
template <typename MediaRefMap>
59+
bool check_for_valid_media_reference_key(
60+
std::string const& caller,
61+
std::string const& key,
62+
MediaRefMap const& media_references,
63+
ErrorStatus* error_status);
64+
65+
private:
66+
std::map<std::string, Retainer<MediaReference>> _media_references;
67+
std::string _active_media_reference_key;
4468
};
4569

4670
}} // namespace opentimelineio::OPENTIMELINEIO_VERSION

src/opentimelineio/errorStatus.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ ErrorStatus::outcome_to_string(Outcome o)
5757
return "cannot trim transition";
5858
case CANNOT_COMPUTE_BOUNDS:
5959
return "cannot compute image bounds";
60+
case MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY:
61+
return "active key not found in media references";
62+
case MEDIA_REFERENCES_CONTAIN_EMPTY_KEY:
63+
return "the media referencess cannot contain an empty key";
6064
default:
6165
return "unknown/illegal ErrorStatus::Outcome code";
6266
};

src/opentimelineio/errorStatus.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ struct ErrorStatus
3636
OBJECT_WITHOUT_DURATION,
3737
CANNOT_TRIM_TRANSITION,
3838
OBJECT_CYCLE,
39-
CANNOT_COMPUTE_BOUNDS
39+
CANNOT_COMPUTE_BOUNDS,
40+
MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY,
41+
MEDIA_REFERENCES_CONTAIN_EMPTY_KEY
4042
};
4143

4244
ErrorStatus()

src/opentimelineio/typeRegistry.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ TypeRegistry::TypeRegistry()
9090
(*d)["marked_range"] = (*d)["range"];
9191
d->erase("range");
9292
});
93+
94+
register_upgrade_function(Clip::Schema::name, 2, [](AnyDictionary* d) {
95+
auto media_ref = (*d)["media_reference"];
96+
97+
// The default ctor of Clip used to set media_reference to
98+
// MissingReference. To preserve the same behaviour, if we don't have a
99+
// valid MediaReference, do it here too.
100+
if (media_ref.type() != typeid(SerializableObject::Retainer<>))
101+
{
102+
media_ref = SerializableObject::Retainer<>(new MissingReference);
103+
}
104+
105+
(*d)["media_references"] =
106+
AnyDictionary{ { Clip::default_media_key, media_ref } };
107+
108+
(*d)["active_media_reference_key"] =
109+
std::string(Clip::default_media_key);
110+
111+
d->erase("media_reference");
112+
});
93113
}
94114

95115
bool

src/py-opentimelineio/opentimelineio-bindings/otio_errorStatusHandler.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ ErrorStatusHandler::~ErrorStatusHandler() noexcept(false) {
6060
throw _CannotComputeAvailableRangeException(full_details());
6161
case ErrorStatus::OBJECT_CYCLE:
6262
throw py::value_error("Detected SerializableObject cycle while copying/serializing: " + details());
63+
case ErrorStatus::MEDIA_REFERENCES_DO_NOT_CONTAIN_ACTIVE_KEY:
64+
throw py::value_error("The media references do not contain the active key");
65+
case ErrorStatus::MEDIA_REFERENCES_CONTAIN_EMPTY_KEY:
66+
throw py::value_error("The media references contain an empty key");
6367
default:
6468
throw py::value_error(full_details());
6569
}

0 commit comments

Comments
 (0)