Skip to main content

ff_decode/video/builder/
scale.rs

1//! Output scale options for [`VideoDecoderBuilder`].
2
3use super::{OutputScale, VideoDecoderBuilder};
4
5impl VideoDecoderBuilder {
6    /// Scales decoded frames to the given exact dimensions.
7    ///
8    /// The frame is scaled in the same `libswscale` pass as pixel-format
9    /// conversion, so there is no extra copy. If `output_format` is not set,
10    /// the source pixel format is preserved while scaling.
11    ///
12    /// Width and height must be greater than zero. They are rounded up to the
13    /// nearest even number if necessary (required by most pixel formats).
14    ///
15    /// Calling this method overwrites any previous `output_width` or
16    /// `output_height` call. The last setter wins.
17    ///
18    /// # Errors
19    ///
20    /// [`build()`](Self::build) returns `DecodeError::InvalidOutputDimensions`
21    /// if either dimension is zero after rounding.
22    ///
23    /// # Examples
24    ///
25    /// ```ignore
26    /// use ff_decode::VideoDecoder;
27    ///
28    /// // Decode every frame at 320×240
29    /// let decoder = VideoDecoder::open("video.mp4")?
30    ///     .output_size(320, 240)
31    ///     .build()?;
32    /// ```
33    #[must_use]
34    pub fn output_size(mut self, width: u32, height: u32) -> Self {
35        self.output_scale = Some(OutputScale::Exact { width, height });
36        self
37    }
38
39    /// Scales decoded frames to the given width, preserving the aspect ratio.
40    ///
41    /// The height is computed from the source aspect ratio and rounded to the
42    /// nearest even number. Calling this method overwrites any previous
43    /// `output_size` or `output_height` call. The last setter wins.
44    ///
45    /// # Errors
46    ///
47    /// [`build()`](Self::build) returns `DecodeError::InvalidOutputDimensions`
48    /// if `width` is zero.
49    ///
50    /// # Examples
51    ///
52    /// ```ignore
53    /// use ff_decode::VideoDecoder;
54    ///
55    /// // Decode at 1280 px wide, preserving aspect ratio
56    /// let decoder = VideoDecoder::open("video.mp4")?
57    ///     .output_width(1280)
58    ///     .build()?;
59    /// ```
60    #[must_use]
61    pub fn output_width(mut self, width: u32) -> Self {
62        self.output_scale = Some(OutputScale::FitWidth(width));
63        self
64    }
65
66    /// Scales decoded frames to the given height, preserving the aspect ratio.
67    ///
68    /// The width is computed from the source aspect ratio and rounded to the
69    /// nearest even number. Calling this method overwrites any previous
70    /// `output_size` or `output_width` call. The last setter wins.
71    ///
72    /// # Errors
73    ///
74    /// [`build()`](Self::build) returns `DecodeError::InvalidOutputDimensions`
75    /// if `height` is zero.
76    ///
77    /// # Examples
78    ///
79    /// ```ignore
80    /// use ff_decode::VideoDecoder;
81    ///
82    /// // Decode at 720 px tall, preserving aspect ratio
83    /// let decoder = VideoDecoder::open("video.mp4")?
84    ///     .output_height(720)
85    ///     .build()?;
86    /// ```
87    #[must_use]
88    pub fn output_height(mut self, height: u32) -> Self {
89        self.output_scale = Some(OutputScale::FitHeight(height));
90        self
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use crate::error::DecodeError;
98    use crate::video::builder::VideoDecoder;
99    use std::path::PathBuf;
100
101    #[test]
102    fn builder_output_size_should_set_exact_scale() {
103        let builder = VideoDecoderBuilder::new(PathBuf::from("test.mp4")).output_size(1280, 720);
104
105        assert_eq!(
106            builder.output_scale,
107            Some(OutputScale::Exact {
108                width: 1280,
109                height: 720
110            })
111        );
112    }
113
114    #[test]
115    fn builder_output_width_should_set_fit_width() {
116        let builder = VideoDecoderBuilder::new(PathBuf::from("test.mp4")).output_width(1920);
117
118        assert_eq!(builder.output_scale, Some(OutputScale::FitWidth(1920)));
119    }
120
121    #[test]
122    fn builder_output_height_should_set_fit_height() {
123        let builder = VideoDecoderBuilder::new(PathBuf::from("test.mp4")).output_height(1080);
124
125        assert_eq!(builder.output_scale, Some(OutputScale::FitHeight(1080)));
126    }
127
128    #[test]
129    fn builder_output_size_last_setter_wins() {
130        let builder = VideoDecoderBuilder::new(PathBuf::from("test.mp4"))
131            .output_width(1280)
132            .output_size(640, 480);
133
134        assert_eq!(
135            builder.output_scale,
136            Some(OutputScale::Exact {
137                width: 640,
138                height: 480
139            })
140        );
141    }
142
143    #[test]
144    fn build_with_zero_width_should_return_invalid_dimensions() {
145        let result = VideoDecoder::open("nonexistent.mp4")
146            .output_size(0, 480)
147            .build();
148        assert!(matches!(
149            result,
150            Err(DecodeError::InvalidOutputDimensions { .. })
151        ));
152    }
153
154    #[test]
155    fn build_with_zero_height_should_return_invalid_dimensions() {
156        let result = VideoDecoder::open("nonexistent.mp4")
157            .output_size(640, 0)
158            .build();
159        assert!(matches!(
160            result,
161            Err(DecodeError::InvalidOutputDimensions { .. })
162        ));
163    }
164
165    #[test]
166    fn build_with_zero_output_width_should_return_invalid_dimensions() {
167        let result = VideoDecoder::open("nonexistent.mp4")
168            .output_width(0)
169            .build();
170        assert!(matches!(
171            result,
172            Err(DecodeError::InvalidOutputDimensions { .. })
173        ));
174    }
175
176    #[test]
177    fn build_with_zero_output_height_should_return_invalid_dimensions() {
178        let result = VideoDecoder::open("nonexistent.mp4")
179            .output_height(0)
180            .build();
181        assert!(matches!(
182            result,
183            Err(DecodeError::InvalidOutputDimensions { .. })
184        ));
185    }
186}