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