-
Notifications
You must be signed in to change notification settings - Fork 3
/
track_media.py
171 lines (129 loc) · 5.88 KB
/
track_media.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
from camtasia.effects import EffectSchema
from .marker import Marker
class TrackMedia:
"""Individual media elements on a track on the timeline.
The relationship between the underlying media and that visible on the timeline on the timeline is a bit involved:
v--media-start--v
-------------------------------------------------------
| underlying media |
-------------------------------------------------------
|---- visible part of media ----|
| |
v--start------------v v
-------------------------------------------------------------------------------
| timeline |
-------------------------------------------------------------------------------
So `media-start` is the offset into the full, underlying media where the visble part starts.
`start` is the offset into the *timeline* where the visible part starts.
Media marker timestamps are calculated from the start of the underlying media. So in order to calculate the
timeline-relative timestamp for a media marker you need to take `start`, `media-start`, and the marker's timestamp
into account::
start + (marker_time - media_start)
"""
def __init__(self, media_data):
self._data = media_data
self._markers = _Markers(self)
@property
def id(self):
"""ID of the media entry on the track."""
return self._data['id']
@property
def markers(self):
return self._markers
@property
def start(self):
"The offset (in frames) on the timeline at which the visible media starts."
return self._data['start']
@property
def media_start(self):
"The offset (in frames) into the underlying media at which the visible media starts."
return self._data['mediaStart']
@property
def duration(self):
"The duration (in frames) of the media on the timeline."
return self._data['duration']
@property
def source(self):
"""ID of the media-bin source for this media.
If media does not have a presence in the media-bin (e.g. if it's an annotation), this
will be None.
"""
return self._data.get('src', None)
def __repr__(self):
return f'Media(start={self.start}, duration={self.duration})'
@property
def effects(self):
return TrackMediaEffects(self._data)
class TrackMediaEffects():
"""Individual effects objects are immutable, but they can be added, removed, and replaced.
"""
# Effects objects are immutable, but they can be added, removed, and replaced
def __init__(self, track_media_data):
self._track_media_data = track_media_data
self._effects = self._track_media_data["effects"]
self._metadata = self._track_media_data["metadata"]
def __getitem__(self, index):
effect_data = self._effects[index]
effect_schema = EffectSchema()
effect = effect_schema.load(effect_data)
return effect
def __delitem__(self, index):
effect = self[index]
for key in effect.metadata:
del self._metadata[key]
del self._effects[index]
def __setitem__(self, index, effect):
effect_schema = EffectSchema()
effect_data = effect_schema.dump(effect)
self._effects.insert(index, effect_data)
for key in effect.metadata:
del self._metadata[key]
self._metadata.update(effect.metadata)
del self._effects[index + 1]
def __len__(self):
return len(self._effects)
def add_effect(self, effect):
effect_schema = EffectSchema()
effect_data = effect_schema.dump(effect)
self._effects.append(effect_data)
self._metadata.update(effect.metadata)
class _Markers:
"Collection of markers in a TrackMedia."
def __init__(self, track_media: TrackMedia):
self._track_media = track_media
def __iter__(self):
"Iterate over Markers in a TrackMedia."
# Keyframes may not exist when e.g. the media has no markers
keyframes = self._track_media._data.get(
'parameters', {}).get('toc', {}).get('keyframes', ())
for m in keyframes:
marker_offset = m['time']
yield Marker(name=m['value'],
time=self._track_media.start + (marker_offset - self._track_media.media_start))
def add(self, name, offset, duplicates_okay=False):
"""Add a Marker to a TrackMedia.
Note that `offset` is interpreted as relative to the start of the timeline, not the
track media. This is symmetrical with how you interpret `Marker` objects in general. So if
you want to add a marker relative to the start of the `TrackMedia`, you need to add its
`start` value. For example, here's how to add a marker to the start of a `TrackMedia`:
>>> track = ...
>>> media = next(iter(track.medias))
>>> media.markers.add('marker-name', media.start)
Args:
name: The name of the marker.
offset: The offset of the marker (relative to the start of the timeline).
Returns: The new Marker object.
Raises:
ValueError: If a marker at `offset` already exists.
"""
if any(m.time == offset for m in self):
raise ValueError(f'A marker already exists at offset {offset}')
marker_offset = offset + self._track_media.media_start - self._track_media.start
keyframes = self._track_media._data.setdefault(
'parameters', {}).setdefault('toc', {}).setdefault('keyframes', [])
keyframes.append(
{'value': name,
'time': marker_offset,
'endTime': marker_offset,
'duration': 0
})