alpha2_model/source.rs
1use crate::{Generator, MediaFingerprint, ProviderSource};
2use alpha2_media::{ColorMetadata, SourceId};
3use serde::{Deserialize, Serialize};
4
5/// A host-resolved media resource.
6#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
7#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
8#[serde(deny_unknown_fields)]
9#[non_exhaustive]
10pub struct MediaSource {
11 /// Stable source identity.
12 pub id: SourceId,
13 /// Opaque URI or host locator. The engine does not open it directly.
14 /// Required, except that a [`MediaKind::Generated`] or
15 /// [`MediaKind::Composition`] source has none.
16 #[serde(default)]
17 pub locator: String,
18 /// Media payload domain.
19 pub kind: MediaKind,
20 /// The color a [`MediaKind::Video`] source's pictures are in, which wins
21 /// over what its frames and its stream are tagged with on every host: a
22 /// host records it at import, or a document declares it to correct
23 /// missing or wrong tags. `None` leaves the tags in charge. A still is
24 /// read as sRGB code values whatever it declares.
25 #[serde(default)]
26 pub color_metadata: Option<ColorMetadata>,
27 /// Whether this source's media has an audio stream: a fact of the file,
28 /// so every host that imports it records the same, whether or not that
29 /// host can decode the stream. A host that cannot reports so at run time
30 /// and plays the source silent; the document keeps the stream for the
31 /// hosts that can.
32 ///
33 /// [`MediaKind::Audio`] always does. A [`MediaKind::Video`] source declares
34 /// it so that clips on a visual track contribute to the mix; leaving it
35 /// unset keeps a video clip silent.
36 #[serde(default)]
37 pub has_audio: bool,
38 /// What a [`MediaKind::Generated`] source draws; present exactly for
39 /// that kind.
40 #[serde(default)]
41 pub generator: Option<Generator>,
42 /// The composition a [`MediaKind::Composition`] source shows; present
43 /// exactly for that kind.
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 pub composition: Option<crate::CompositionId>,
46 /// What the host found at the locator when the project recorded it, so
47 /// another host can tell whether media it finds is the same. A generated
48 /// source has none.
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub fingerprint: Option<MediaFingerprint>,
51 /// The extension provider that supplies this source's media, and its
52 /// settings, when the host's own decoders do not read it.
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub provider: Option<ProviderSource>,
55}
56
57impl MediaSource {
58 /// Creates a silent-by-default source without explicit color metadata.
59 #[must_use]
60 pub fn new(id: SourceId, locator: impl Into<String>, kind: MediaKind) -> Self {
61 Self {
62 id,
63 locator: locator.into(),
64 kind,
65 color_metadata: None,
66 has_audio: false,
67 generator: None,
68 composition: None,
69 fingerprint: None,
70 provider: None,
71 }
72 }
73
74 /// Creates a source the engine draws itself.
75 #[must_use]
76 pub const fn generated(id: SourceId, generator: Generator) -> Self {
77 Self {
78 id,
79 locator: String::new(),
80 kind: MediaKind::Generated,
81 color_metadata: None,
82 has_audio: false,
83 generator: Some(generator),
84 composition: None,
85 fingerprint: None,
86 provider: None,
87 }
88 }
89
90 /// Creates a source that shows composition `composition`.
91 #[must_use]
92 pub const fn composition(id: SourceId, composition: crate::CompositionId) -> Self {
93 Self {
94 id,
95 locator: String::new(),
96 kind: MediaKind::Composition,
97 color_metadata: None,
98 has_audio: false,
99 generator: None,
100 composition: Some(composition),
101 fingerprint: None,
102 provider: None,
103 }
104 }
105
106 /// Returns whether a host supplies this source's media; the engine draws
107 /// a generated or composition source itself.
108 #[must_use]
109 pub const fn is_host_supplied(&self) -> bool {
110 !matches!(self.kind, MediaKind::Generated | MediaKind::Composition)
111 }
112
113 /// Returns whether this source can contribute samples to the audio mix.
114 #[must_use]
115 pub const fn is_audible(&self) -> bool {
116 matches!(self.kind, MediaKind::Audio) || self.has_audio
117 }
118
119 /// Returns declared color metadata or the SDR BT.709 limited-range fallback.
120 #[must_use]
121 pub fn effective_color_metadata(&self) -> ColorMetadata {
122 self.color_metadata.unwrap_or_default()
123 }
124}
125
126/// The payload domain of a source.
127#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
128#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
129#[serde(rename_all = "snake_case")]
130pub enum MediaKind {
131 /// A video stream, potentially with an audio stream.
132 Video,
133 /// An audio stream.
134 Audio,
135 /// A still visual image.
136 Image,
137 /// A picture the engine draws from the source's [`Generator`]. The host
138 /// supplies nothing for it.
139 Generated,
140 /// A composition of the project, drawn at the source time as its own
141 /// time. The host supplies nothing for it; the media its nodes read, it
142 /// supplies as for any composition.
143 Composition,
144}