Skip to main content

rsynko_media/
selection.rs

1use crate::{FORMAT_EXTENSION, FORMAT_HAS_AUDIO, FORMAT_HAS_VIDEO, MediaSorts};
2use alux_ext::ext;
3use ambassador::delegatable_trait;
4
5/// Provides the carrier and primitive constructors for format predicates.
6#[delegatable_trait]
7pub trait FormatPredicateAlg: MediaSorts {
8    /// Defines the predicate accepting every format.
9    fn any_format(&self) -> Self::Predicate;
10    /// Defines the predicate accepting one format identity.
11    fn format_id(&self, id: impl Into<String>) -> Self::Predicate;
12    /// Defines the predicate accepting formats stating the named observation.
13    fn observed_format(&self, key: impl Into<String>) -> Self::Predicate;
14    /// Defines the predicate accepting one textual observation.
15    fn text_format(&self, key: impl Into<String>, value: impl Into<String>) -> Self::Predicate;
16    /// Defines the predicate accepting one stated truth.
17    fn flag_format(&self, key: impl Into<String>, value: bool) -> Self::Predicate;
18    /// Defines the predicate comparing one numeric observation.
19    fn number_format(
20        &self,
21        key: impl Into<String>,
22        comparison: FormatComparison,
23        value: f64,
24    ) -> Self::Predicate;
25    /// Defines conjunction.
26    fn all_formats(&self, left: Self::Predicate, right: Self::Predicate) -> Self::Predicate;
27    /// Defines negation.
28    fn not_format(&self, predicate: Self::Predicate) -> Self::Predicate;
29}
30
31/// Provides the carrier and primitive constructors for selection programs.
32#[delegatable_trait]
33pub trait FormatSelectionAlg: MediaSorts {
34    /// Defines selection of the most preferred matching format.
35    fn best_format(&self, predicate: Self::Predicate) -> Self::Selection;
36    /// Defines selection of the least preferred matching format.
37    fn worst_format(&self, predicate: Self::Predicate) -> Self::Selection;
38    /// Defines ordered combination requiring every child selection.
39    fn merge_formats(
40        &self,
41        selections: impl IntoIterator<Item = Self::Selection>,
42    ) -> Self::Selection;
43    /// Defines left-biased choice of the first successful child selection.
44    fn fallback_formats(
45        &self,
46        selections: impl IntoIterator<Item = Self::Selection>,
47    ) -> Self::Selection;
48}
49
50/// Denotes how an observed number is compared against a stated one.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum FormatComparison {
53    /// Accepts an observation no greater than the stated number.
54    AtMost,
55    /// Accepts an observation equal to the stated number.
56    Exactly,
57    /// Accepts an observation no smaller than the stated number.
58    AtLeast,
59}
60
61/// Specifies interpretation of reified format predicates.
62///
63/// Matching is total over the reified predicate, so the derived meaning is the default and an
64/// interpreter implements this capability without restating it.
65#[delegatable_trait]
66pub trait FormatPredicateMatchAlg: MediaSorts {
67    /// Observes whether a format satisfies a predicate.
68    fn format_matches(&self, predicate: &Self::Predicate, format: &Self::Format) -> bool;
69}
70
71/// Specifies interpretation of a selection program against a preference-ordered catalog.
72///
73/// Interpreting reified syntax is an interpreter, not a derived operation: a program is walked, and
74/// what walking means belongs to whoever chose the selection sort.
75#[delegatable_trait]
76pub trait FormatSelectionApplyAlg: MediaSorts {
77    /// Chooses the formats one program denotes, in program order.
78    fn select_formats<'a>(
79        &self,
80        formats: &'a [Self::Format],
81        selection: &Self::Selection,
82    ) -> Option<Vec<&'a Self::Format>>;
83}
84
85/// Derives named and comparative predicates from primitive observation construction.
86#[ext(name = FormatPredicateExt)]
87pub impl<This> This
88where
89    This: FormatPredicateAlg,
90{
91    /// Defines formats containing audio.
92    fn has_audio(&self) -> This::Predicate {
93        self.flag_format(FORMAT_HAS_AUDIO, true)
94    }
95
96    /// Defines formats containing video.
97    fn has_video(&self) -> This::Predicate {
98        self.flag_format(FORMAT_HAS_VIDEO, true)
99    }
100
101    /// Defines formats stating one container or filename extension.
102    fn format_extension(&self, extension: impl Into<String>) -> This::Predicate {
103        self.text_format(FORMAT_EXTENSION, extension)
104    }
105
106    /// Defines formats whose observation is no greater than the stated number.
107    fn at_most(&self, key: impl Into<String>, value: f64) -> This::Predicate {
108        self.number_format(key, FormatComparison::AtMost, value)
109    }
110
111    /// Defines formats whose observation is no smaller than the stated number.
112    fn at_least(&self, key: impl Into<String>, value: f64) -> This::Predicate {
113        self.number_format(key, FormatComparison::AtLeast, value)
114    }
115
116    /// Defines formats whose observation equals the stated number.
117    fn exactly(&self, key: impl Into<String>, value: f64) -> This::Predicate {
118        self.number_format(key, FormatComparison::Exactly, value)
119    }
120
121    /// Defines formats whose observation is smaller than the stated number.
122    fn less_than(&self, key: impl Into<String>, value: f64) -> This::Predicate {
123        self.not_format(self.at_least(key, value))
124    }
125
126    /// Defines formats whose observation is greater than the stated number.
127    fn greater_than(&self, key: impl Into<String>, value: f64) -> This::Predicate {
128        self.not_format(self.at_most(key, value))
129    }
130
131    /// Defines formats containing both audio and video.
132    fn progressive_format(&self) -> This::Predicate {
133        self.all_formats(self.has_audio(), self.has_video())
134    }
135
136    /// Defines formats containing audio and no video.
137    fn audio_only_format(&self) -> This::Predicate {
138        self.all_formats(self.has_audio(), self.not_format(self.has_video()))
139    }
140
141    /// Defines formats containing video and no audio.
142    fn video_only_format(&self) -> This::Predicate {
143        self.all_formats(self.has_video(), self.not_format(self.has_audio()))
144    }
145}
146
147/// Derives common selection programs by equating predicate carriers.
148#[ext(name = FormatSelectionProgramExt)]
149pub impl<This, Predicate> This
150where
151    This: FormatPredicateAlg<Predicate = Predicate> + FormatSelectionAlg<Predicate = Predicate>,
152{
153    /// Defines the most preferred progressive format.
154    fn best_progressive_format(&self) -> This::Selection {
155        self.best_format(self.progressive_format())
156    }
157
158    /// Defines an exact format with a progressive fallback.
159    fn preferred_progressive_format(&self, id: impl Into<String>) -> This::Selection {
160        self.fallback_formats([
161            self.best_format(self.format_id(id)),
162            self.best_progressive_format(),
163        ])
164    }
165}