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().map_or("no media streams", MediaStreams::streams_label)
70 );
71 if described.is_empty() { columns } else { format!("{columns} {described}") }
72 }
73
74 /// States what distinguishes the format from an alternative carrying the same streams.
75 fn format_observations(&self) -> String {
76 let quality = self.format_stated_quality();
77 [
78 quality.as_deref().map(str::to_owned),
79 self.format_bitrate().map(|bitrate| format!("{}/s", bytes_label(bitrate / 8))),
80 self.format_size().map(bytes_label),
81 self.format_codecs().map(str::to_owned),
82 ]
83 .into_iter()
84 .flatten()
85 .collect::<Vec<_>>()
86 .join(" ")
87 }
88
89 /// States the quality the extractor named, or the picture size standing in for it.
90 fn format_stated_quality(&self) -> Option<String> {
91 if let Some(quality) = self.format_quality() {
92 return Some(quality.to_owned());
93 }
94 match (self.format_width(), self.format_height()) {
95 (Some(width), Some(height)) => Some(format!("{width}x{height}")),
96 (None, Some(height)) => Some(format!("{height}p")),
97 (Some(_) | None, None) => None,
98 }
99 }
100}
101
102/// Derives which stream roles a request may actually be asked for.
103#[ext(name = FormatRolesExt)]
104pub impl<This> This
105where
106 This: FormatChoiceViewAlg,
107 This::Format: FormatDescriptionAlg,
108{
109 /// States the roles some described format carries, and every role while none are described.
110 ///
111 /// A chooser offers what can be chosen. Asking for sound alone from a source that only ever
112 /// states one file carrying both would select nothing at all, so a role no format carries is
113 /// not offered at all. Before anything is described there is nothing to rule out, and every
114 /// role stands.
115 fn offered_streams(&self) -> Vec<MediaStreams> {
116 let described = self.described_formats().collect::<Vec<_>>();
117 // Nothing described is not the same as nothing carrying a role: before a source has said
118 // what it holds there is nothing to rule out, and a source that has said, and named only
119 // pictures, offers no role at all.
120 if described.is_empty() {
121 return MediaStreams::OFFERED.to_vec();
122 }
123 let carried = described.into_iter().filter_map(FormatDescriptionAlg::format_streams).collect::<Vec<_>>();
124 MediaStreams::OFFERED.into_iter().filter(|role| carried.contains(role)).collect()
125 }
126}