Skip to main content

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}