zantetsu_core/scoring/
context.rs1use serde::{Deserialize, Serialize};
2
3use crate::types::VideoCodec;
4
5use super::profile::QualityScores;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum DeviceType {
10 Desktop,
12 Laptop,
14 Mobile,
16 TV,
18 Embedded,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum NetworkQuality {
25 Unlimited,
27 Broadband,
29 Limited,
31 Offline,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ClientContext {
38 pub device_type: DeviceType,
40 pub network: NetworkQuality,
42 pub hw_decode_codecs: Vec<VideoCodec>,
44}
45
46impl Default for ClientContext {
47 fn default() -> Self {
48 Self {
49 device_type: DeviceType::Desktop,
50 network: NetworkQuality::Unlimited,
51 hw_decode_codecs: vec![VideoCodec::H264, VideoCodec::HEVC],
52 }
53 }
54}
55
56impl ClientContext {
57 #[must_use]
61 pub fn adjust_score(
62 &self,
63 mut scores: QualityScores,
64 file_video_codec: Option<VideoCodec>,
65 ) -> QualityScores {
66 if let Some(ref mut res_score) = scores.resolution {
68 let multiplier = self.resolution_multiplier(*res_score);
69 *res_score *= multiplier;
70 }
71
72 let network_mult = self.network_multiplier();
74 if let Some(ref mut res) = scores.resolution {
75 *res *= network_mult;
76 }
77 if let Some(ref mut vc) = scores.video_codec {
78 *vc *= network_mult;
79 }
80
81 if let Some(codec) = file_video_codec
83 && !self.hw_decode_codecs.contains(&codec)
84 {
85 scores.video_codec = scores.video_codec.map(|s| s * 0.1);
87 }
88
89 scores
90 }
91
92 fn resolution_multiplier(&self, res_score: f32) -> f32 {
94 match self.device_type {
95 DeviceType::Desktop | DeviceType::TV => 1.0,
96 DeviceType::Laptop => {
97 if res_score > 0.9 {
98 0.85
100 } else {
101 1.0
102 }
103 }
104 DeviceType::Mobile => {
105 if res_score > 0.6 {
106 0.6
108 } else {
109 1.0
110 }
111 }
112 DeviceType::Embedded => {
113 if res_score > 0.5 {
114 0.5
116 } else {
117 1.0
118 }
119 }
120 }
121 }
122
123 fn network_multiplier(&self) -> f32 {
125 match self.network {
126 NetworkQuality::Unlimited => 1.0,
127 NetworkQuality::Broadband => 0.9,
128 NetworkQuality::Limited => 0.3,
129 NetworkQuality::Offline => 1.0, }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::types::{AudioCodec, MediaSource, Resolution};
138
139 fn make_scores(
140 resolution: Option<Resolution>,
141 video_codec: Option<VideoCodec>,
142 ) -> QualityScores {
143 QualityScores::from_metadata(
144 resolution,
145 video_codec,
146 Some(AudioCodec::AAC),
147 Some(MediaSource::WebDL),
148 0.7,
149 )
150 }
151
152 #[test]
153 fn desktop_unlimited_no_penalty() {
154 let ctx = ClientContext::default();
155 let scores = make_scores(Some(Resolution::UHD2160), Some(VideoCodec::H264));
156 let adjusted = ctx.adjust_score(scores.clone(), Some(VideoCodec::H264));
157
158 assert_eq!(adjusted.resolution, scores.resolution);
160 }
161
162 #[test]
163 fn mobile_penalizes_high_resolution() {
164 let ctx = ClientContext {
165 device_type: DeviceType::Mobile,
166 network: NetworkQuality::Unlimited,
167 hw_decode_codecs: vec![VideoCodec::H264, VideoCodec::HEVC],
168 };
169
170 let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::H264));
171 let adjusted = ctx.adjust_score(scores, Some(VideoCodec::H264));
172
173 let expected = 0.85 * 0.6;
175 assert!(
176 (adjusted.resolution.unwrap() - expected).abs() < 0.001,
177 "got {}, expected {}",
178 adjusted.resolution.unwrap(),
179 expected
180 );
181 }
182
183 #[test]
184 fn limited_network_penalizes_all() {
185 let ctx = ClientContext {
186 device_type: DeviceType::Desktop,
187 network: NetworkQuality::Limited,
188 hw_decode_codecs: vec![VideoCodec::H264],
189 };
190
191 let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::H264));
192 let adjusted = ctx.adjust_score(scores, Some(VideoCodec::H264));
193
194 let expected_res = 0.85 * 0.3;
196 assert!((adjusted.resolution.unwrap() - expected_res).abs() < 0.001);
197 }
198
199 #[test]
200 fn unsupported_codec_massive_penalty() {
201 let ctx = ClientContext {
202 device_type: DeviceType::Desktop,
203 network: NetworkQuality::Unlimited,
204 hw_decode_codecs: vec![VideoCodec::H264], };
206
207 let scores = make_scores(Some(Resolution::FHD1080), Some(VideoCodec::AV1));
208 let adjusted = ctx.adjust_score(scores, Some(VideoCodec::AV1));
209
210 assert!((adjusted.video_codec.unwrap() - 0.1).abs() < 0.001);
212 }
213
214 #[test]
215 fn default_context_is_desktop_unlimited() {
216 let ctx = ClientContext::default();
217 assert_eq!(ctx.device_type, DeviceType::Desktop);
218 assert_eq!(ctx.network, NetworkQuality::Unlimited);
219 assert!(ctx.hw_decode_codecs.contains(&VideoCodec::H264));
220 assert!(ctx.hw_decode_codecs.contains(&VideoCodec::HEVC));
221 }
222}