rsynko_ui/format.rs
1use crate::*;
2use alux_ext::ext;
3use rsynko_manager::MediaStreams;
4
5/// Specifies what one selectable format states where a chooser can read it.
6///
7/// A chooser reads a format to tell it from its alternatives. These are the observations that
8/// distinguish them; everything else an extractor stated stays in the extraction.
9pub trait FormatDescriptionAlg {
10 /// Observes the identity the extractor gave the format.
11 fn format_identity(&self) -> &str;
12 /// Observes the container or file-name extension.
13 fn format_extension(&self) -> Option<&str>;
14 /// Observes the stream roles the format carries, when it carries any.
15 fn format_streams(&self) -> Option<MediaStreams>;
16 /// Observes the quality the extractor named.
17 fn format_quality(&self) -> Option<&str>;
18 /// Observes the picture height in pixels.
19 fn format_height(&self) -> Option<u64>;
20 /// Observes the picture width in pixels.
21 fn format_width(&self) -> Option<u64>;
22 /// Observes the stated bits per second.
23 fn format_bitrate(&self) -> Option<u64>;
24 /// Observes the stated byte count of the whole format.
25 fn format_size(&self) -> Option<u64>;
26 /// Observes the codecs the format is encoded with.
27 fn format_codecs(&self) -> Option<&str>;
28}
29
30/// Denotes what discovery has stated about one request's selectable formats.
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum DiscoveryState<'a> {
33 /// Denotes a source whose formats no one has asked for.
34 Unrequested,
35 /// Denotes a request waiting for an interpreter to inspect the source.
36 Waiting,
37 /// Denotes source inspection currently in progress.
38 Inspecting,
39 /// Denotes formats described in extractor preference order.
40 Described,
41 /// Denotes inspection that failed, and why.
42 Failed(&'a str),
43}
44
45/// Specifies what one queue entry states about the formats a chooser may offer.
46pub trait FormatChoiceViewAlg {
47 /// Represents one described selectable format.
48 type Format;
49
50 /// Observes what discovery has stated about this request's formats.
51 fn discovery(&self) -> DiscoveryState<'_>;
52 /// Observes the described formats in extractor preference order.
53 fn described_formats(&self) -> impl Iterator<Item = &Self::Format>;
54}
55
56/// Derives what a chooser reads about one format.
57#[ext(name = FormatLabelExt)]
58pub impl<This> This
59where
60 This: FormatDescriptionAlg,
61{
62 /// States the format in aligned columns, so alternatives are compared by reading down.
63 fn format_label(&self) -> String {
64 let described = self.format_observations();
65 let columns = format!(
66 "{:<4} {:<5} {:<13}",
67 self.format_identity(),
68 self.format_extension().unwrap_or(UNSTATED),
69 self.format_streams()
70 .map_or("no media streams", MediaStreams::streams_label)
71 );
72 if described.is_empty() {
73 columns
74 } else {
75 format!("{columns} {described}")
76 }
77 }
78
79 /// States what distinguishes the format from an alternative carrying the same streams.
80 fn format_observations(&self) -> String {
81 let quality = self.format_stated_quality();
82 [
83 quality.as_deref().map(str::to_owned),
84 self.format_bitrate()
85 .map(|bitrate| format!("{}/s", bytes_label(bitrate / 8))),
86 self.format_size().map(bytes_label),
87 self.format_codecs().map(str::to_owned),
88 ]
89 .into_iter()
90 .flatten()
91 .collect::<Vec<_>>()
92 .join(" ")
93 }
94
95 /// States the quality the extractor named, or the picture size standing in for it.
96 fn format_stated_quality(&self) -> Option<String> {
97 if let Some(quality) = self.format_quality() {
98 return Some(quality.to_owned());
99 }
100 match (self.format_width(), self.format_height()) {
101 (Some(width), Some(height)) => Some(format!("{width}x{height}")),
102 (None, Some(height)) => Some(format!("{height}p")),
103 (Some(_) | None, None) => None,
104 }
105 }
106}
107
108/// Derives which stream roles a request may actually be asked for.
109#[ext(name = FormatRolesExt)]
110pub impl<This> This
111where
112 This: FormatChoiceViewAlg,
113 This::Format: FormatDescriptionAlg,
114{
115 /// States the roles some described format carries, and every role while none are described.
116 ///
117 /// A chooser offers what can be chosen. Asking for sound alone from a source that only ever
118 /// states one file carrying both would select nothing at all, so a role no format carries is
119 /// not offered at all. Before anything is described there is nothing to rule out, and every
120 /// role stands.
121 fn offered_streams(&self) -> Vec<MediaStreams> {
122 let described = self.described_formats().collect::<Vec<_>>();
123 // Nothing described is not the same as nothing carrying a role: before a source has said
124 // what it holds there is nothing to rule out, and a source that has said, and named only
125 // pictures, offers no role at all.
126 if described.is_empty() {
127 return MediaStreams::OFFERED.to_vec();
128 }
129 let carried = described
130 .into_iter()
131 .filter_map(FormatDescriptionAlg::format_streams)
132 .collect::<Vec<_>>();
133 MediaStreams::OFFERED
134 .into_iter()
135 .filter(|role| carried.contains(role))
136 .collect()
137 }
138}