rosu_render/model/
render.rs

1use std::{
2    borrow::Cow,
3    fmt::{Display, Formatter, Result as FmtResult},
4};
5
6use hyper::{body::Bytes, StatusCode};
7use serde::{
8    de::{Error as DeError, IgnoredAny, MapAccess, Unexpected, Visitor},
9    Deserialize, Deserializer, Serialize,
10};
11use time::OffsetDateTime;
12
13use crate::{request::Requestable, util::datetime::deserialize_datetime, ClientError};
14
15/// A list of [`Render`].
16#[derive(Clone, Debug, Deserialize)]
17pub struct RenderList {
18    /// Array of renders returned by the api
19    pub renders: Vec<Render>,
20    /// The total number of renders on o!rdr,
21    /// but if search query the total numbers of renders corresponding to that query will be used.
22    #[serde(rename = "maxRenders")]
23    pub max_renders: u32,
24}
25
26impl Requestable for RenderList {
27    fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
28        ClientError::response_error(bytes, status.as_u16())
29    }
30}
31
32#[derive(Clone, Debug, Deserialize)]
33pub struct Render {
34    #[serde(rename = "renderID")]
35    pub id: u32,
36    #[serde(deserialize_with = "deserialize_datetime")]
37    pub date: OffsetDateTime,
38    pub username: Box<str>,
39    pub progress: Box<str>,
40    pub renderer: Box<str>,
41    pub description: Box<str>,
42    pub title: Box<str>,
43    #[serde(rename = "isBot")]
44    pub is_bot: bool,
45    #[serde(rename = "isVerified")]
46    pub is_verified: bool,
47    #[serde(rename = "videoUrl")]
48    pub video_url: Box<str>,
49    #[serde(rename = "mapLink")]
50    pub map_link: Box<str>,
51    #[serde(rename = "mapTitle")]
52    pub map_title: Box<str>,
53    #[serde(rename = "replayDifficulty")]
54    pub replay_difficulty: Box<str>,
55    #[serde(rename = "replayUsername")]
56    pub replay_username: Box<str>,
57    #[serde(rename = "mapID")]
58    pub map_id: u32,
59    #[serde(rename = "needToRedownload")]
60    pub need_to_redownload: bool,
61    #[serde(rename = "motionBlur960fps")]
62    pub motion_blur: bool,
63    #[serde(rename = "renderStartTime", deserialize_with = "deserialize_datetime")]
64    pub render_start_time: OffsetDateTime,
65    #[serde(rename = "renderEndTime", deserialize_with = "deserialize_datetime")]
66    pub render_end_time: OffsetDateTime,
67    #[serde(rename = "uploadEndTime", deserialize_with = "deserialize_datetime")]
68    pub upload_end_time: OffsetDateTime,
69    #[serde(rename = "renderTotalTime")]
70    pub render_total_time: u32,
71    #[serde(rename = "uploadTotalTime")]
72    pub upload_total_time: u32,
73    #[serde(rename = "mapLength")]
74    pub map_length: u32,
75    #[serde(rename = "replayMods")]
76    pub replay_mods: Box<str>,
77    pub removed: bool,
78    #[serde(flatten)]
79    pub options: RenderOptions,
80    #[serde(flatten)]
81    pub skin: RenderSkinOption<'static>,
82}
83
84#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
85pub enum RenderResolution {
86    /// 720x480 (30fps)
87    #[serde(rename = "720x480")]
88    SD480,
89    /// 960x540 (30fps)
90    #[serde(rename = "960x540")]
91    SD960,
92    /// 1280x720 (60fps)
93    #[serde(rename = "1280x720")]
94    HD720,
95    /// 1920x1080 (60fps)
96    #[serde(rename = "1920x1080")]
97    HD1080,
98}
99
100impl RenderResolution {
101    #[must_use]
102    pub fn as_str(self) -> &'static str {
103        match self {
104            Self::SD480 => "720x480",
105            Self::SD960 => "960x540",
106            Self::HD720 => "1280x720",
107            Self::HD1080 => "1920x1080",
108        }
109    }
110}
111
112impl Display for RenderResolution {
113    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
114        f.write_str(self.as_str())
115    }
116}
117
118/// Customize danser settings when rendering.
119#[derive(Clone, Debug, Deserialize, Serialize)]
120pub struct RenderOptions {
121    pub resolution: RenderResolution,
122    /// The global volume for the video, in percent, from 0 to 100.
123    #[serde(rename = "globalVolume")]
124    pub global_volume: u8,
125    /// The music volume for the video, in percent, from 0 to 100.
126    #[serde(rename = "musicVolume")]
127    pub music_volume: u8,
128    /// The hitsounds volume for the video, in percent, from 0 to 100.
129    #[serde(rename = "hitsoundVolume")]
130    pub hitsound_volume: u8,
131    /// Show the hit error meter.
132    #[serde(rename = "showHitErrorMeter")]
133    pub show_hit_error_meter: bool,
134    /// Show the unstable rate, only takes effect if `show_hit_error_meter` is set to true.
135    #[serde(rename = "showUnstableRate")]
136    pub show_unstable_rate: bool,
137    /// Show the score.
138    #[serde(rename = "showScore")]
139    pub show_score: bool,
140    /// Show the HP bar.
141    #[serde(rename = "showHPBar")]
142    pub show_hp_bar: bool,
143    /// Show the combo counter.
144    #[serde(rename = "showComboCounter")]
145    pub show_combo_counter: bool,
146    /// Show the PP Counter or not.
147    #[serde(rename = "showPPCounter")]
148    pub show_pp_counter: bool,
149    /// Show the scoreboard or not.
150    #[serde(rename = "showScoreboard")]
151    pub show_scoreboard: bool,
152    /// Show the playfield borders or not.
153    #[serde(rename = "showBorders")]
154    pub show_borders: bool,
155    /// Show the mods used during the game or not.
156    #[serde(rename = "showMods")]
157    pub show_mods: bool,
158    /// Show the result screen or not.
159    #[serde(rename = "showResultScreen")]
160    pub show_result_screen: bool,
161    /// Use the skin cursor or not. If not, danser cursor will be used.
162    #[serde(rename = "useSkinCursor")]
163    pub use_skin_cursor: bool,
164    /// Use the skin combo colors or not.
165    #[serde(rename = "useSkinColors")]
166    pub use_skin_colors: bool,
167    /// Use skin hitsounds, if false beatmap hitsounds will be used.
168    #[serde(rename = "useSkinHitsounds")]
169    pub use_skin_hitsounds: bool,
170    /// Use the beatmap combo colors or not, overrides useSkinColors if true.
171    #[serde(rename = "useBeatmapColors")]
172    pub use_beatmap_colors: bool,
173    /// Scale cursor to circle size. Does not do anything at the moment.
174    #[serde(rename = "cursorScaleToCS")]
175    pub cursor_scale_to_cs: bool,
176    /// Makes the cursor rainbow, only takes effect if `use_skin_cursor` is set to false.
177    #[serde(rename = "cursorRainbow")]
178    pub cursor_rainbow: bool,
179    /// Have a glow with the trail or not.
180    #[serde(rename = "cursorTrailGlow")]
181    pub cursor_trail_glow: bool,
182    /// Draw follow points between objects or not.
183    #[serde(rename = "drawFollowPoints")]
184    pub draw_follow_points: bool,
185    /// Scale objects to the beat.
186    #[serde(rename = "scaleToTheBeat")]
187    pub beat_scaling: bool,
188    /// Merge sliders or not.
189    #[serde(rename = "sliderMerge")]
190    pub slider_merge: bool,
191    /// Makes the objects rainbow, overrides `use_skin_colors` and `use_beatmap_colors`.
192    #[serde(rename = "objectsRainbow")]
193    pub objects_rainbow: bool,
194    /// Makes the objects flash to the beat.
195    #[serde(rename = "objectsFlashToTheBeat")]
196    pub flash_objects: bool,
197    /// Makes the slider body have the same color as the hit circles.
198    #[serde(rename = "useHitCircleColor")]
199    pub use_slider_hitcircle_color: bool,
200    /// Display a 5 second seizure warning before the video.
201    #[serde(rename = "seizureWarning")]
202    pub seizure_warning: bool,
203    /// Load the background storyboard.
204    #[serde(rename = "loadStoryboard")]
205    pub load_storyboard: bool,
206    /// Load the background video (`load_storyboard` has to be set to true).
207    #[serde(rename = "loadVideo")]
208    pub load_video: bool,
209    /// Background dim for the intro, in percent, from 0 to 100.
210    #[serde(rename = "introBGDim")]
211    pub intro_bg_dim: u8,
212    /// Background dim in game, in percent, from 0 to 100.
213    #[serde(rename = "inGameBGDim")]
214    pub ingame_bg_dim: u8,
215    /// Background dim in break, in percent, from 0 to 100.
216    #[serde(rename = "breakBGDim")]
217    pub break_bg_dim: u8,
218    /// Adds a parallax effect.
219    #[serde(rename = "BGParallax")]
220    pub bg_parallax: bool,
221    /// Show danser logo on the intro.
222    #[serde(rename = "showDanserLogo")]
223    pub show_danser_logo: bool,
224    /// Skip the intro or not.
225    #[serde(rename = "skip")]
226    pub skip_intro: bool,
227    /// Show cursor ripples when keypress.
228    #[serde(rename = "cursorRipples")]
229    pub cursor_ripples: bool,
230    /// Set the cursor size, multiplier from 0.5 to 2.
231    #[serde(rename = "cursorSize")]
232    pub cursor_size: f32,
233    /// Show the cursor trail or not.
234    #[serde(rename = "cursorTrail")]
235    pub cursor_trail: bool,
236    /// Show the combo numbers in objects.
237    #[serde(rename = "drawComboNumbers")]
238    pub draw_combo_numbers: bool,
239    /// Have slider snaking in.
240    #[serde(rename = "sliderSnakingIn")]
241    pub slider_snaking_in: bool,
242    /// Have slider snaking out.
243    #[serde(rename = "sliderSnakingOut")]
244    pub slider_snaking_out: bool,
245    /// Shows a hit counter (100, 50, miss) below the PP counter.
246    #[serde(rename = "showHitCounter")]
247    pub show_hit_counter: bool,
248    /// Show the key overlay or not.
249    #[serde(rename = "showKeyOverlay")]
250    pub show_key_overlay: bool,
251    /// Show avatars on the left of the username of a player on the scoreboard.
252    /// May break some skins because the width of the scoreboard increases.
253    #[serde(rename = "showAvatarsOnScoreboard")]
254    pub show_avatars_on_scoreboard: bool,
255    /// Show the Aim Error Meter or not.
256    #[serde(rename = "showAimErrorMeter")]
257    pub show_aim_error_meter: bool,
258    /// Play nightcore hitsounds or not.
259    #[serde(rename = "playNightcoreSamples")]
260    pub play_nightcore_samples: bool,
261    /// Show the strain graph or not.
262    #[serde(rename = "showStrainGraph")]
263    pub show_strain_graph: bool,
264    /// Show the slider breaks count in the hit counter.
265    #[serde(rename = "showSliderBreaks")]
266    pub show_slider_breaks: bool,
267    /// Ignores fail in the replay or not.
268    #[serde(rename = "ignoreFail")]
269    pub ignore_fail: bool,
270}
271
272impl RenderOptions {
273    pub const DEFAULT_RESOLUTION: RenderResolution = RenderResolution::HD720;
274}
275
276impl Default for RenderOptions {
277    fn default() -> Self {
278        Self {
279            resolution: Self::DEFAULT_RESOLUTION,
280            global_volume: 50,
281            music_volume: 50,
282            hitsound_volume: 50,
283            show_hit_error_meter: true,
284            show_unstable_rate: true,
285            show_score: true,
286            show_hp_bar: true,
287            show_combo_counter: true,
288            show_pp_counter: true,
289            show_key_overlay: true,
290            show_scoreboard: true,
291            show_borders: true,
292            show_mods: true,
293            show_result_screen: true,
294            use_skin_cursor: true,
295            use_skin_colors: false,
296            use_skin_hitsounds: true,
297            use_beatmap_colors: true,
298            cursor_scale_to_cs: false,
299            cursor_rainbow: false,
300            cursor_trail_glow: false,
301            draw_follow_points: true,
302            draw_combo_numbers: true,
303            cursor_size: 1.0,
304            cursor_trail: true,
305            beat_scaling: false,
306            slider_merge: false,
307            objects_rainbow: false,
308            flash_objects: false,
309            use_slider_hitcircle_color: false,
310            seizure_warning: false,
311            load_storyboard: false,
312            load_video: false,
313            intro_bg_dim: 0,
314            ingame_bg_dim: 80,
315            break_bg_dim: 30,
316            bg_parallax: false,
317            show_danser_logo: true,
318            skip_intro: true,
319            cursor_ripples: false,
320            slider_snaking_in: true,
321            slider_snaking_out: true,
322            show_hit_counter: true,
323            show_avatars_on_scoreboard: false,
324            show_aim_error_meter: false,
325            play_nightcore_samples: true,
326            show_strain_graph: false,
327            show_slider_breaks: false,
328            ignore_fail: false,
329        }
330    }
331}
332
333#[derive(Clone, Debug, PartialEq, Eq)]
334pub enum RenderSkinOption<'a> {
335    Official { name: Cow<'a, str> },
336    Custom { id: u32 },
337}
338
339impl Default for RenderSkinOption<'_> {
340    fn default() -> Self {
341        Self::Official {
342            name: "default".into(),
343        }
344    }
345}
346
347impl From<u32> for RenderSkinOption<'_> {
348    fn from(id: u32) -> Self {
349        Self::Custom { id }
350    }
351}
352
353macro_rules! impl_from_name {
354    ( $( $ty:ty ),* ) => {
355        $(
356            impl<'a> From<$ty> for RenderSkinOption<'a> {
357                fn from(name: $ty) -> Self {
358                    Self::Official { name: name.into() }
359                }
360            }
361        )*
362    };
363}
364
365impl_from_name!(&'a str, &'a String, String, Cow<'a, str>);
366
367impl<'de> Deserialize<'de> for RenderSkinOption<'static> {
368    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
369        struct SkinVisitor;
370
371        impl<'de> Visitor<'de> for SkinVisitor {
372            type Value = RenderSkinOption<'static>;
373
374            fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
375                f.write_str("`skin` and `customSkin` fields")
376            }
377
378            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
379                let mut custom_skin: Option<bool> = None;
380                let mut skin: Option<String> = None;
381
382                while let Some(key) = map.next_key()? {
383                    match key {
384                        "customSkin" => custom_skin = Some(map.next_value()?),
385                        "skin" => skin = Some(map.next_value()?),
386                        _ => {
387                            let _: IgnoredAny = map.next_value()?;
388                        }
389                    }
390                }
391
392                let custom_skin =
393                    custom_skin.ok_or_else(|| DeError::missing_field("customSkin"))?;
394                let skin = skin.ok_or_else(|| DeError::missing_field("skin"))?;
395
396                let skin = if custom_skin {
397                    let id = skin
398                        .parse()
399                        .map_err(|_| DeError::invalid_value(Unexpected::Str(&skin), &"a u32"))?;
400
401                    RenderSkinOption::Custom { id }
402                } else {
403                    RenderSkinOption::Official {
404                        name: Cow::Owned(skin),
405                    }
406                };
407
408                Ok(skin)
409            }
410        }
411
412        d.deserialize_map(SkinVisitor)
413    }
414}
415
416#[derive(Clone, Debug, Deserialize, PartialEq)]
417pub struct RenderServers {
418    pub servers: Vec<RenderServer>,
419}
420
421impl Requestable for RenderServers {
422    fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
423        ClientError::response_error(bytes, status.as_u16())
424    }
425}
426
427#[derive(Clone, Debug, Deserialize, PartialEq)]
428pub struct RenderServer {
429    pub enabled: bool,
430    #[serde(rename = "lastSeen", deserialize_with = "deserialize_datetime")]
431    pub last_seen: OffsetDateTime,
432    pub name: Box<str>,
433    pub priority: f32,
434    #[serde(rename = "oldScore")]
435    pub old_score: f32,
436    #[serde(rename = "avgFPS")]
437    pub avg_fps: u32,
438    pub power: Box<str>,
439    pub status: Box<str>,
440    #[serde(rename = "totalRendered")]
441    pub total_rendered: u32,
442    #[serde(rename = "renderingType")]
443    pub rendering_type: Box<str>,
444    pub cpu: Box<str>,
445    pub gpu: Box<str>,
446    #[serde(rename = "motionBlurCapable")]
447    pub motion_blur_capable: bool,
448    #[serde(rename = "usingOsuApi")]
449    pub using_osu_api: bool,
450    #[serde(rename = "uhdCapable")]
451    pub uhd_capable: bool,
452    #[serde(rename = "avgRenderTime")]
453    pub avg_render_time: f32,
454    #[serde(rename = "avgUploadTime")]
455    pub avg_upload_time: f32,
456    #[serde(rename = "totalAvgTime")]
457    pub total_avg_time: f32,
458    #[serde(rename = "totalUploadedVideosSize")]
459    pub total_uploaded_videos_size: u32,
460    #[serde(rename = "ownerUserId")]
461    pub owner_user_id: u32,
462    #[serde(rename = "ownerUsername")]
463    pub owner_username: Box<str>,
464    pub customization: RenderServerOptions,
465}
466
467#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
468pub struct RenderServerOptions {
469    #[serde(rename = "textColor")]
470    pub text_color: Box<str>,
471    #[serde(rename = "backgroundType")]
472    pub background_type: i32,
473}
474
475#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
476pub struct ServerOnlineCount(pub u32);
477
478impl Requestable for ServerOnlineCount {
479    fn response_error(status: StatusCode, bytes: Bytes) -> ClientError {
480        ClientError::response_error(bytes, status.as_u16())
481    }
482}