y4m/
lib.rs

1//! # YUV4MPEG2 (.y4m) Encoder/Decoder
2#![deny(missing_docs)]
3
4use std::fmt;
5use std::io;
6use std::io::Read;
7use std::io::Write;
8use std::num;
9use std::str;
10
11const MAX_PARAMS_SIZE: usize = 1024;
12const FILE_MAGICK: &[u8] = b"YUV4MPEG2 ";
13const FRAME_MAGICK: &[u8] = b"FRAME";
14const TERMINATOR: u8 = 0x0A;
15const FIELD_SEP: u8 = b' ';
16const RATIO_SEP: u8 = b':';
17
18/// Both encoding and decoding errors.
19#[derive(Debug)]
20pub enum Error {
21    /// End of the file. Technically not an error, but it's easier to process
22    /// that way.
23    EOF,
24    /// Bad input parameters provided.
25    BadInput,
26    /// Unknown colorspace (possibly just unimplemented).
27    UnknownColorspace,
28    /// Error while parsing the file/frame header.
29    // TODO(Kagami): Better granularity of parse errors.
30    ParseError(ParseError),
31    /// Error while reading/writing the file.
32    IoError(io::Error),
33    /// Out of memory (limits exceeded).
34    OutOfMemory,
35}
36
37impl std::error::Error for crate::Error {
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39        match *self {
40            Error::EOF => None,
41            Error::BadInput => None,
42            Error::UnknownColorspace => None,
43            Error::ParseError(ref err) => Some(err),
44            Error::IoError(ref err) => Some(err),
45            Error::OutOfMemory => None,
46        }
47    }
48}
49
50impl fmt::Display for crate::Error {
51    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52        match *self {
53            Error::EOF => write!(f, "End of file"),
54            Error::BadInput => write!(f, "Bad input parameters provided"),
55            Error::UnknownColorspace => write!(f, "Bad input parameters provided"),
56            Error::ParseError(ref err) => err.fmt(f),
57            Error::IoError(ref err) => err.fmt(f),
58            Error::OutOfMemory => write!(f, "Out of memory (limits exceeded)"),
59        }
60    }
61}
62
63/// Granular ParseError Definiations
64pub enum ParseError {
65    /// Error reading y4m header
66    InvalidY4M,
67    /// Error parsing int
68    Int,
69    /// Error parsing UTF8
70    Utf8,
71    /// General Parsing Error
72    General,
73}
74
75impl std::error::Error for crate::ParseError {
76    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77        match *self {
78            ParseError::InvalidY4M => None,
79            ParseError::Int => None,
80            ParseError::Utf8 => None,
81            ParseError::General => None,
82        }
83    }
84}
85
86impl fmt::Display for ParseError {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        match self {
89            ParseError::InvalidY4M => write!(f, "Error parsing y4m header"),
90            ParseError::Int => write!(f, "Error parsing Int"),
91            ParseError::Utf8 => write!(f, "Error parsing UTF8"),
92            ParseError::General => write!(f, "General parsing error"),
93        }
94    }
95}
96
97impl fmt::Debug for ParseError {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        match self {
100            ParseError::InvalidY4M => write!(f, "Error parsing y4m header"),
101            ParseError::Int => write!(f, "Error parsing Int"),
102            ParseError::Utf8 => write!(f, "Error parsing UTF8"),
103            ParseError::General => write!(f, "General parsing error"),
104        }
105    }
106}
107
108macro_rules! parse_error {
109    ($p:expr) => {
110        return Err(Error::ParseError($p))
111    };
112}
113
114impl From<io::Error> for Error {
115    fn from(err: io::Error) -> Error {
116        match err.kind() {
117            io::ErrorKind::UnexpectedEof => Error::EOF,
118            _ => Error::IoError(err),
119        }
120    }
121}
122
123impl From<num::ParseIntError> for Error {
124    fn from(_: num::ParseIntError) -> Error {
125        Error::ParseError(ParseError::Int)
126    }
127}
128
129impl From<str::Utf8Error> for Error {
130    fn from(_: str::Utf8Error) -> Error {
131        Error::ParseError(ParseError::Utf8)
132    }
133}
134
135trait EnhancedRead {
136    fn read_until(&mut self, ch: u8, buf: &mut [u8]) -> Result<usize, Error>;
137}
138
139impl<R: Read> EnhancedRead for R {
140    // Current implementation does one `read` call per byte. This might be a
141    // bit slow for long headers but it simplifies things: we don't need to
142    // check whether start of the next frame is already read and so on.
143    fn read_until(&mut self, ch: u8, buf: &mut [u8]) -> Result<usize, Error> {
144        let mut collected = 0;
145        while collected < buf.len() {
146            let chunk_size = self.read(&mut buf[collected..=collected])?;
147            if chunk_size == 0 {
148                return Err(Error::EOF);
149            }
150            if buf[collected] == ch {
151                return Ok(collected);
152            }
153            collected += chunk_size;
154        }
155        parse_error!(ParseError::General)
156    }
157}
158
159fn parse_bytes(buf: &[u8]) -> Result<usize, Error> {
160    // A bit kludgy but seems like there is no other way.
161    Ok(str::from_utf8(buf)?.parse()?)
162}
163
164/// A newtype wrapper around Vec<u8> to ensure validity as a vendor extension.
165#[derive(Debug, Clone)]
166pub struct VendorExtensionString(Vec<u8>);
167
168impl VendorExtensionString {
169    /// Create a new vendor extension string.
170    ///
171    /// For example, setting to `b"COLORRANGE=FULL"` sets the interpretation of
172    /// the YUV values to cover the full range (rather a limited "studio swing"
173    /// range).
174    ///
175    /// The argument `x_option` must not contain a space (b' ') character,
176    /// otherwise [Error::BadInput] is returned.
177    pub fn new(value: Vec<u8>) -> Result<VendorExtensionString, Error> {
178        if value.contains(&b' ') {
179            return Err(Error::BadInput);
180        }
181        Ok(VendorExtensionString(value))
182    }
183    /// Get the vendor extension string.
184    pub fn value(&self) -> &[u8] {
185        self.0.as_slice()
186    }
187}
188
189/// Simple ratio structure since stdlib lacks one.
190#[derive(Debug, Clone, Copy)]
191pub struct Ratio {
192    /// Numerator.
193    pub num: usize,
194    /// Denominator.
195    pub den: usize,
196}
197
198impl Ratio {
199    /// Create a new ratio.
200    pub fn new(num: usize, den: usize) -> Ratio {
201        Ratio { num, den }
202    }
203
204    /// Parse a ratio from a byte slice.
205    pub fn parse(value: &[u8]) -> Result<Ratio, Error> {
206        let parts: Vec<_> = value.splitn(2, |&b| b == RATIO_SEP).collect();
207        if parts.len() != 2 {
208            parse_error!(ParseError::General)
209        }
210        let num = parse_bytes(parts[0])?;
211        let den = parse_bytes(parts[1])?;
212        Ok(Ratio::new(num, den))
213    }
214}
215
216impl fmt::Display for Ratio {
217    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218        write!(f, "{}:{}", self.num, self.den)
219    }
220}
221
222/// Colorspace (color model/pixel format). Only subset of them is supported.
223///
224/// From libavformat/yuv4mpegenc.c:
225///
226/// > yuv4mpeg can only handle yuv444p, yuv422p, yuv420p, yuv411p and gray8
227/// pixel formats. And using 'strict -1' also yuv444p9, yuv422p9, yuv420p9,
228/// yuv444p10, yuv422p10, yuv420p10, yuv444p12, yuv422p12, yuv420p12,
229/// yuv444p14, yuv422p14, yuv420p14, yuv444p16, yuv422p16, yuv420p16, gray9,
230/// gray10, gray12 and gray16 pixel formats.
231#[derive(Debug, Clone, Copy)]
232#[non_exhaustive]
233pub enum Colorspace {
234    /// Grayscale only, 8-bit.
235    Cmono,
236    /// Grayscale only, 12-bit.
237    Cmono12,
238    /// 4:2:0 with coincident chroma planes, 8-bit.
239    C420,
240    /// 4:2:0 with coincident chroma planes, 10-bit.
241    C420p10,
242    /// 4:2:0 with coincident chroma planes, 12-bit.
243    C420p12,
244    /// 4:2:0 with biaxially-displaced chroma planes, 8-bit.
245    C420jpeg,
246    /// 4:2:0 with vertically-displaced chroma planes, 8-bit.
247    C420paldv,
248    /// Found in some files. Same as `C420`.
249    C420mpeg2,
250    /// 4:2:2, 8-bit.
251    C422,
252    /// 4:2:2, 10-bit.
253    C422p10,
254    /// 4:2:2, 12-bit.
255    C422p12,
256    /// 4:4:4, 8-bit.
257    C444,
258    /// 4:4:4, 10-bit.
259    C444p10,
260    /// 4:4:4, 12-bit.
261    C444p12,
262}
263
264impl Colorspace {
265    /// Return the bit depth per sample
266    #[inline]
267    pub fn get_bit_depth(self) -> usize {
268        match self {
269            Colorspace::Cmono
270            | Colorspace::C420
271            | Colorspace::C422
272            | Colorspace::C444
273            | Colorspace::C420jpeg
274            | Colorspace::C420paldv
275            | Colorspace::C420mpeg2 => 8,
276            Colorspace::C420p10 | Colorspace::C422p10 | Colorspace::C444p10 => 10,
277            Colorspace::Cmono12
278            | Colorspace::C420p12
279            | Colorspace::C422p12
280            | Colorspace::C444p12 => 12,
281        }
282    }
283
284    /// Return the number of bytes in a sample
285    #[inline]
286    pub fn get_bytes_per_sample(self) -> usize {
287        if self.get_bit_depth() <= 8 {
288            1
289        } else {
290            2
291        }
292    }
293}
294
295fn get_plane_sizes(width: usize, height: usize, colorspace: Colorspace) -> (usize, usize, usize) {
296    let y_plane_size = width * height * colorspace.get_bytes_per_sample();
297
298    let c420_chroma_size =
299        ((width + 1) / 2) * ((height + 1) / 2) * colorspace.get_bytes_per_sample();
300    let c422_chroma_size = ((width + 1) / 2) * height * colorspace.get_bytes_per_sample();
301
302    let c420_sizes = (y_plane_size, c420_chroma_size, c420_chroma_size);
303    let c422_sizes = (y_plane_size, c422_chroma_size, c422_chroma_size);
304    let c444_sizes = (y_plane_size, y_plane_size, y_plane_size);
305
306    match colorspace {
307        Colorspace::Cmono | Colorspace::Cmono12 => (y_plane_size, 0, 0),
308        Colorspace::C420
309        | Colorspace::C420p10
310        | Colorspace::C420p12
311        | Colorspace::C420jpeg
312        | Colorspace::C420paldv
313        | Colorspace::C420mpeg2 => c420_sizes,
314        Colorspace::C422 | Colorspace::C422p10 | Colorspace::C422p12 => c422_sizes,
315        Colorspace::C444 | Colorspace::C444p10 | Colorspace::C444p12 => c444_sizes,
316    }
317}
318
319/// Limits on the resources `Decoder` is allowed to use.
320#[derive(Clone, Copy, Debug)]
321pub struct Limits {
322    /// Maximum allowed size of frame buffer, default is 1 GiB.
323    pub bytes: usize,
324}
325
326impl Default for Limits {
327    fn default() -> Limits {
328        Limits {
329            bytes: 1024 * 1024 * 1024,
330        }
331    }
332}
333
334/// YUV4MPEG2 decoder.
335pub struct Decoder<R: Read> {
336    reader: R,
337    params_buf: Vec<u8>,
338    frame_buf: Vec<u8>,
339    raw_params: Vec<u8>,
340    width: usize,
341    height: usize,
342    framerate: Ratio,
343    pixel_aspect: Ratio,
344    colorspace: Colorspace,
345    y_len: usize,
346    u_len: usize,
347}
348
349impl<R: Read> Decoder<R> {
350    /// Create a new decoder instance.
351    pub fn new(reader: R) -> Result<Decoder<R>, Error> {
352        Decoder::new_with_limits(reader, Limits::default())
353    }
354
355    /// Create a new decoder instance with custom limits.
356    pub fn new_with_limits(mut reader: R, limits: Limits) -> Result<Decoder<R>, Error> {
357        let mut params_buf = vec![0; MAX_PARAMS_SIZE];
358        let end_params_pos = reader.read_until(TERMINATOR, &mut params_buf)?;
359        if end_params_pos < FILE_MAGICK.len() || !params_buf.starts_with(FILE_MAGICK) {
360            parse_error!(ParseError::InvalidY4M)
361        }
362        let raw_params = params_buf[FILE_MAGICK.len()..end_params_pos].to_owned();
363        let mut width = 0;
364        let mut height = 0;
365        // Framerate is actually required per spec, but let's be a bit more
366        // permissive as per ffmpeg behavior.
367        let mut framerate = Ratio::new(25, 1);
368        let mut pixel_aspect = Ratio::new(1, 1);
369        let mut colorspace = None;
370        // We shouldn't convert it to string because encoding is unspecified.
371        for param in raw_params.split(|&b| b == FIELD_SEP) {
372            if param.is_empty() {
373                continue;
374            }
375            let (name, value) = (param[0], &param[1..]);
376            // TODO(Kagami): interlacing, comment.
377            match name {
378                b'W' => width = parse_bytes(value)?,
379                b'H' => height = parse_bytes(value)?,
380                b'F' => framerate = Ratio::parse(value)?,
381                b'A' => pixel_aspect = Ratio::parse(value)?,
382                b'C' => {
383                    colorspace = match value {
384                        b"mono" => Some(Colorspace::Cmono),
385                        b"mono12" => Some(Colorspace::Cmono12),
386                        b"420" => Some(Colorspace::C420),
387                        b"420p10" => Some(Colorspace::C420p10),
388                        b"420p12" => Some(Colorspace::C420p12),
389                        b"422" => Some(Colorspace::C422),
390                        b"422p10" => Some(Colorspace::C422p10),
391                        b"422p12" => Some(Colorspace::C422p12),
392                        b"444" => Some(Colorspace::C444),
393                        b"444p10" => Some(Colorspace::C444p10),
394                        b"444p12" => Some(Colorspace::C444p12),
395                        b"420jpeg" => Some(Colorspace::C420jpeg),
396                        b"420paldv" => Some(Colorspace::C420paldv),
397                        b"420mpeg2" => Some(Colorspace::C420mpeg2),
398                        _ => return Err(Error::UnknownColorspace),
399                    }
400                }
401                _ => {}
402            }
403        }
404        let colorspace = colorspace.unwrap_or(Colorspace::C420);
405        if width == 0 || height == 0 {
406            parse_error!(ParseError::General)
407        }
408        let (y_len, u_len, v_len) = get_plane_sizes(width, height, colorspace);
409        let frame_size = y_len + u_len + v_len;
410        if frame_size > limits.bytes {
411            return Err(Error::OutOfMemory);
412        }
413        let frame_buf = vec![0; frame_size];
414        Ok(Decoder {
415            reader,
416            params_buf,
417            frame_buf,
418            raw_params,
419            width,
420            height,
421            framerate,
422            pixel_aspect,
423            colorspace,
424            y_len,
425            u_len,
426        })
427    }
428
429    /// Iterate over frames. End of input is indicated by `Error::EOF`.
430    pub fn read_frame(&mut self) -> Result<Frame, Error> {
431        let end_params_pos = self.reader.read_until(TERMINATOR, &mut self.params_buf)?;
432        if end_params_pos < FRAME_MAGICK.len() || !self.params_buf.starts_with(FRAME_MAGICK) {
433            parse_error!(ParseError::InvalidY4M)
434        }
435        // We don't parse frame params currently but user has access to them.
436        let start_params_pos = FRAME_MAGICK.len();
437        let raw_params = if end_params_pos - start_params_pos > 0 {
438            // Check for extra space.
439            if self.params_buf[start_params_pos] != FIELD_SEP {
440                parse_error!(ParseError::InvalidY4M)
441            }
442            Some(self.params_buf[start_params_pos + 1..end_params_pos].to_owned())
443        } else {
444            None
445        };
446        self.reader.read_exact(&mut self.frame_buf)?;
447        Ok(Frame::new(
448            [
449                &self.frame_buf[0..self.y_len],
450                &self.frame_buf[self.y_len..self.y_len + self.u_len],
451                &self.frame_buf[self.y_len + self.u_len..],
452            ],
453            raw_params,
454        ))
455    }
456
457    /// Return file width.
458    #[inline]
459    pub fn get_width(&self) -> usize {
460        self.width
461    }
462    /// Return file height.
463    #[inline]
464    pub fn get_height(&self) -> usize {
465        self.height
466    }
467    /// Return file framerate.
468    #[inline]
469    pub fn get_framerate(&self) -> Ratio {
470        self.framerate
471    }
472    /// Return file pixel aspect.
473    #[inline]
474    pub fn get_pixel_aspect(&self) -> Ratio {
475        self.pixel_aspect
476    }
477    /// Return file colorspace.
478    ///
479    /// **NOTE:** normally all .y4m should have colorspace param, but there are
480    /// files encoded without that tag and it's unclear what should we do in
481    /// that case. Currently C420 is implied by default as per ffmpeg behavior.
482    #[inline]
483    pub fn get_colorspace(&self) -> Colorspace {
484        self.colorspace
485    }
486    /// Return file raw parameters.
487    #[inline]
488    pub fn get_raw_params(&self) -> &[u8] {
489        &self.raw_params
490    }
491    /// Return the bit depth per sample
492    #[inline]
493    pub fn get_bit_depth(&self) -> usize {
494        self.colorspace.get_bit_depth()
495    }
496    /// Return the number of bytes in a sample
497    #[inline]
498    pub fn get_bytes_per_sample(&self) -> usize {
499        self.colorspace.get_bytes_per_sample()
500    }
501}
502
503/// A single frame.
504#[derive(Debug)]
505pub struct Frame<'f> {
506    planes: [&'f [u8]; 3],
507    raw_params: Option<Vec<u8>>,
508}
509
510impl<'f> Frame<'f> {
511    /// Create a new frame with optional parameters.
512    /// No heap allocations are made.
513    pub fn new(planes: [&'f [u8]; 3], raw_params: Option<Vec<u8>>) -> Frame<'f> {
514        Frame { planes, raw_params }
515    }
516
517    /// Create a new frame from data in 16-bit format.
518    pub fn from_u16(planes: [&'f [u16]; 3], raw_params: Option<Vec<u8>>) -> Frame<'f> {
519        Frame::new(
520            [
521                unsafe {
522                    std::slice::from_raw_parts::<u8>(
523                        planes[0].as_ptr() as *const u8,
524                        planes[0].len() * 2,
525                    )
526                },
527                unsafe {
528                    std::slice::from_raw_parts::<u8>(
529                        planes[1].as_ptr() as *const u8,
530                        planes[1].len() * 2,
531                    )
532                },
533                unsafe {
534                    std::slice::from_raw_parts::<u8>(
535                        planes[2].as_ptr() as *const u8,
536                        planes[2].len() * 2,
537                    )
538                },
539            ],
540            raw_params,
541        )
542    }
543
544    /// Return Y (first) plane.
545    #[inline]
546    pub fn get_y_plane(&self) -> &[u8] {
547        self.planes[0]
548    }
549    /// Return U (second) plane. Empty in case of grayscale.
550    #[inline]
551    pub fn get_u_plane(&self) -> &[u8] {
552        self.planes[1]
553    }
554    /// Return V (third) plane. Empty in case of grayscale.
555    #[inline]
556    pub fn get_v_plane(&self) -> &[u8] {
557        self.planes[2]
558    }
559    /// Return frame raw parameters if any.
560    #[inline]
561    pub fn get_raw_params(&self) -> Option<&[u8]> {
562        self.raw_params.as_ref().map(|v| &v[..])
563    }
564}
565
566/// Encoder builder. Allows to set y4m file parameters using builder pattern.
567// TODO(Kagami): Accept all known tags and raw params.
568#[derive(Debug)]
569pub struct EncoderBuilder {
570    width: usize,
571    height: usize,
572    framerate: Ratio,
573    pixel_aspect: Ratio,
574    colorspace: Colorspace,
575    vendor_extensions: Vec<Vec<u8>>,
576}
577
578impl EncoderBuilder {
579    /// Create a new encoder builder.
580    pub fn new(width: usize, height: usize, framerate: Ratio) -> EncoderBuilder {
581        EncoderBuilder {
582            width,
583            height,
584            framerate,
585            pixel_aspect: Ratio::new(1, 1),
586            colorspace: Colorspace::C420,
587            vendor_extensions: vec![],
588        }
589    }
590
591    /// Specify file colorspace.
592    pub fn with_colorspace(mut self, colorspace: Colorspace) -> Self {
593        self.colorspace = colorspace;
594        self
595    }
596
597    /// Specify file pixel aspect.
598    pub fn with_pixel_aspect(mut self, pixel_aspect: Ratio) -> Self {
599        self.pixel_aspect = pixel_aspect;
600        self
601    }
602
603    /// Add vendor extension.
604    pub fn append_vendor_extension(mut self, x_option: VendorExtensionString) -> Self {
605        self.vendor_extensions.push(x_option.0);
606        self
607    }
608
609    /// Write header to the stream and create encoder instance.
610    pub fn write_header<W: Write>(self, mut writer: W) -> Result<Encoder<W>, Error> {
611        // XXX(Kagami): Beware that FILE_MAGICK already contains space.
612        writer.write_all(FILE_MAGICK)?;
613        write!(
614            writer,
615            "W{} H{} F{}",
616            self.width, self.height, self.framerate
617        )?;
618        if self.pixel_aspect.num != 1 || self.pixel_aspect.den != 1 {
619            write!(writer, " A{}", self.pixel_aspect)?;
620        }
621        for x_option in self.vendor_extensions.iter() {
622            write!(writer, " X")?;
623            writer.write_all(x_option)?;
624        }
625        write!(writer, " {:?}", self.colorspace)?;
626        writer.write_all(&[TERMINATOR])?;
627        let (y_len, u_len, v_len) = get_plane_sizes(self.width, self.height, self.colorspace);
628        Ok(Encoder {
629            writer,
630            y_len,
631            u_len,
632            v_len,
633        })
634    }
635}
636
637/// YUV4MPEG2 encoder.
638pub struct Encoder<W: Write> {
639    writer: W,
640    y_len: usize,
641    u_len: usize,
642    v_len: usize,
643}
644
645impl<W: Write> Encoder<W> {
646    /// Write next frame to the stream.
647    pub fn write_frame(&mut self, frame: &Frame) -> Result<(), Error> {
648        if frame.get_y_plane().len() != self.y_len
649            || frame.get_u_plane().len() != self.u_len
650            || frame.get_v_plane().len() != self.v_len
651        {
652            return Err(Error::BadInput);
653        }
654        self.writer.write_all(FRAME_MAGICK)?;
655        if let Some(params) = frame.get_raw_params() {
656            self.writer.write_all(&[FIELD_SEP])?;
657            self.writer.write_all(params)?;
658        }
659        self.writer.write_all(&[TERMINATOR])?;
660        self.writer.write_all(frame.get_y_plane())?;
661        self.writer.write_all(frame.get_u_plane())?;
662        self.writer.write_all(frame.get_v_plane())?;
663        Ok(())
664    }
665}
666
667/// Create a new decoder instance. Alias for `Decoder::new`.
668pub fn decode<R: Read>(reader: R) -> Result<Decoder<R>, Error> {
669    Decoder::new(reader)
670}
671
672/// Create a new encoder builder. Alias for `EncoderBuilder::new`.
673pub fn encode(width: usize, height: usize, framerate: Ratio) -> EncoderBuilder {
674    EncoderBuilder::new(width, height, framerate)
675}