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
use crate::{Generator, MediaFingerprint, ProviderSource};
use alpha2_media::{ColorMetadata, SourceId};
use serde::{Deserialize, Serialize};
/// A host-resolved media resource.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct MediaSource {
/// Stable source identity.
pub id: SourceId,
/// Opaque URI or host locator. The engine does not open it directly.
/// Required, except that a [`MediaKind::Generated`] or
/// [`MediaKind::Composition`] source has none.
#[serde(default)]
pub locator: String,
/// Media payload domain.
pub kind: MediaKind,
/// The color a [`MediaKind::Video`] source's pictures are in, which wins
/// over what its frames and its stream are tagged with on every host: a
/// host records it at import, or a document declares it to correct
/// missing or wrong tags. `None` leaves the tags in charge. A still is
/// read as sRGB code values whatever it declares.
#[serde(default)]
pub color_metadata: Option<ColorMetadata>,
/// Whether this source's media has an audio stream: a fact of the file,
/// so every host that imports it records the same, whether or not that
/// host can decode the stream. A host that cannot reports so at run time
/// and plays the source silent; the document keeps the stream for the
/// hosts that can.
///
/// [`MediaKind::Audio`] always does. A [`MediaKind::Video`] source declares
/// it so that clips on a visual track contribute to the mix; leaving it
/// unset keeps a video clip silent.
#[serde(default)]
pub has_audio: bool,
/// What a [`MediaKind::Generated`] source draws; present exactly for
/// that kind.
#[serde(default)]
pub generator: Option<Generator>,
/// The composition a [`MediaKind::Composition`] source shows; present
/// exactly for that kind.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub composition: Option<crate::CompositionId>,
/// What the host found at the locator when the project recorded it, so
/// another host can tell whether media it finds is the same. A generated
/// source has none.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<MediaFingerprint>,
/// The extension provider that supplies this source's media, and its
/// settings, when the host's own decoders do not read it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider: Option<ProviderSource>,
}
impl MediaSource {
/// Creates a silent-by-default source without explicit color metadata.
#[must_use]
pub fn new(id: SourceId, locator: impl Into<String>, kind: MediaKind) -> Self {
Self {
id,
locator: locator.into(),
kind,
color_metadata: None,
has_audio: false,
generator: None,
composition: None,
fingerprint: None,
provider: None,
}
}
/// Creates a source the engine draws itself.
#[must_use]
pub const fn generated(id: SourceId, generator: Generator) -> Self {
Self {
id,
locator: String::new(),
kind: MediaKind::Generated,
color_metadata: None,
has_audio: false,
generator: Some(generator),
composition: None,
fingerprint: None,
provider: None,
}
}
/// Creates a source that shows composition `composition`.
#[must_use]
pub const fn composition(id: SourceId, composition: crate::CompositionId) -> Self {
Self {
id,
locator: String::new(),
kind: MediaKind::Composition,
color_metadata: None,
has_audio: false,
generator: None,
composition: Some(composition),
fingerprint: None,
provider: None,
}
}
/// Returns whether a host supplies this source's media; the engine draws
/// a generated or composition source itself.
#[must_use]
pub const fn is_host_supplied(&self) -> bool {
!matches!(self.kind, MediaKind::Generated | MediaKind::Composition)
}
/// Returns whether this source can contribute samples to the audio mix.
#[must_use]
pub const fn is_audible(&self) -> bool {
matches!(self.kind, MediaKind::Audio) || self.has_audio
}
/// Returns declared color metadata or the SDR BT.709 limited-range fallback.
#[must_use]
pub fn effective_color_metadata(&self) -> ColorMetadata {
self.color_metadata.unwrap_or_default()
}
}
/// The payload domain of a source.
#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MediaKind {
/// A video stream, potentially with an audio stream.
Video,
/// An audio stream.
Audio,
/// A still visual image.
Image,
/// A picture the engine draws from the source's [`Generator`]. The host
/// supplies nothing for it.
Generated,
/// A composition of the project, drawn at the source time as its own
/// time. The host supplies nothing for it; the media its nodes read, it
/// supplies as for any composition.
Composition,
}