Skip to main content

yog_formats/
video.rs

1//! `.yvideo` — a video: what it shows, at what size and frame rate, and
2//! how it plays (`docs/design/video.md`). Its id is its path under
3//! `resources/video/` with `/` as `.` (`resources/video/intro.yvideo` is
4//! `<mod id>:intro`). The build renders it — the picture as AV1, its sound
5//! as an Ogg sound event — into a definition the runtime plays.
6
7use yog_data::{Res, Schema};
8
9mod montage;
10
11pub use montage::{Montage, PICTURE_SECONDS, PictureClip, PictureTrack};
12
13/// Where `.yvideo` files live, under `resources/`.
14pub const DIR: &str = yog_ids::kind::VIDEO;
15
16/// A montage's frame rate when the file doesn't say.
17pub const MONTAGE_FPS: f32 = 30.0;
18
19/// The best quality asked for and the worst: AV1's constant rate factor
20/// (lower is better and bigger).
21pub const BEST_QUALITY: u32 = 0;
22pub const WORST_QUALITY: u32 = 63;
23/// A good look at a modest size.
24const QUALITY: u32 = 35;
25
26/// A video (a `.yvideo` file).
27#[derive(Schema, Debug, Clone, PartialEq)]
28pub struct Video {
29    /// The `.yres` video it shows…
30    pub source: Option<Res>,
31    /// …or a montage of videos, pictures and sounds.
32    pub montage: Option<Montage>,
33    /// Its size, pixels: a source's when not given (one given, the other
34    /// keeps the source's proportions); a montage needs both.
35    pub width: Option<u32>,
36    pub height: Option<u32>,
37    /// Frames a second: a source's when not given, a montage's
38    /// [`MONTAGE_FPS`].
39    pub fps: Option<f32>,
40    /// AV1's rate factor, [`BEST_QUALITY`]..=[`WORST_QUALITY`].
41    #[data(default = "quality")]
42    pub quality: u32,
43    /// It starts again at its end.
44    #[data(default)]
45    pub looping: bool,
46    /// Its sound kept (played with it).
47    #[data(default = "yes")]
48    pub sound: bool,
49    /// Its sound's loudness.
50    #[data(default = "one")]
51    pub volume: f32,
52}
53
54fn quality() -> u32 {
55    QUALITY
56}
57
58fn yes() -> bool {
59    true
60}
61
62fn one() -> f32 {
63    1.0
64}
65
66/// A video's id from its file's path under `resources/` and the mod's id;
67/// none for a file outside `resources/video/`.
68#[must_use]
69pub fn id_of(mod_id: &str, rel: &str) -> Option<String> {
70    crate::resource_id(mod_id, rel, DIR, yog_ids::Format::Video)
71}
72
73/// The sound event of video `name` (its path in the mod): `video.<name>`.
74#[must_use]
75pub fn sound_event(name: &str) -> String {
76    format!("{DIR}{}{name}", yog_ids::sep::DOT)
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn a_video_is_named_by_its_path_under_video() {
85        assert_eq!(id_of("m", "video/intro.yvideo").as_deref(), Some("m:intro"));
86        assert_eq!(id_of("m", "sound/intro.yvideo"), None);
87        assert_eq!(sound_event("intro"), "video.intro");
88    }
89
90    #[test]
91    fn a_video_needs_only_its_source() {
92        let v = yog_data::from_text::<Video>(r#"{ source: "@Videos.intro" }"#)
93            .unwrap()
94            .value;
95        assert_eq!(
96            (v.quality, v.sound, v.looping, v.volume),
97            (QUALITY, true, false, 1.0)
98        );
99    }
100}