Coverage Report

Created: 2026-02-26 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/y4m-0.8.0/src/lib.rs
Line
Count
Source
1
//! # YUV4MPEG2 (.y4m) Encoder/Decoder
2
#![deny(missing_docs)]
3
4
use std::fmt;
5
use std::io;
6
use std::io::Read;
7
use std::io::Write;
8
use std::num;
9
use std::str;
10
11
const MAX_PARAMS_SIZE: usize = 1024;
12
const FILE_MAGICK: &[u8] = b"YUV4MPEG2 ";
13
const FRAME_MAGICK: &[u8] = b"FRAME";
14
const TERMINATOR: u8 = 0x0A;
15
const FIELD_SEP: u8 = b' ';
16
const RATIO_SEP: u8 = b':';
17
18
/// Both encoding and decoding errors.
19
#[derive(Debug)]
20
pub 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
37
impl std::error::Error for crate::Error {
38
0
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39
0
        match *self {
40
0
            Error::EOF => None,
41
0
            Error::BadInput => None,
42
0
            Error::UnknownColorspace => None,
43
0
            Error::ParseError(ref err) => Some(err),
44
0
            Error::IoError(ref err) => Some(err),
45
0
            Error::OutOfMemory => None,
46
        }
47
0
    }
48
}
49
50
impl fmt::Display for crate::Error {
51
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52
0
        match *self {
53
0
            Error::EOF => write!(f, "End of file"),
54
0
            Error::BadInput => write!(f, "Bad input parameters provided"),
55
0
            Error::UnknownColorspace => write!(f, "Bad input parameters provided"),
56
0
            Error::ParseError(ref err) => err.fmt(f),
57
0
            Error::IoError(ref err) => err.fmt(f),
58
0
            Error::OutOfMemory => write!(f, "Out of memory (limits exceeded)"),
59
        }
60
0
    }
61
}
62
63
/// Granular ParseError Definiations
64
pub 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
75
impl std::error::Error for crate::ParseError {
76
0
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77
0
        match *self {
78
0
            ParseError::InvalidY4M => None,
79
0
            ParseError::Int => None,
80
0
            ParseError::Utf8 => None,
81
0
            ParseError::General => None,
82
        }
83
0
    }
84
}
85
86
impl fmt::Display for ParseError {
87
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88
0
        match self {
89
0
            ParseError::InvalidY4M => write!(f, "Error parsing y4m header"),
90
0
            ParseError::Int => write!(f, "Error parsing Int"),
91
0
            ParseError::Utf8 => write!(f, "Error parsing UTF8"),
92
0
            ParseError::General => write!(f, "General parsing error"),
93
        }
94
0
    }
95
}
96
97
impl fmt::Debug for ParseError {
98
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99
0
        match self {
100
0
            ParseError::InvalidY4M => write!(f, "Error parsing y4m header"),
101
0
            ParseError::Int => write!(f, "Error parsing Int"),
102
0
            ParseError::Utf8 => write!(f, "Error parsing UTF8"),
103
0
            ParseError::General => write!(f, "General parsing error"),
104
        }
105
0
    }
106
}
107
108
macro_rules! parse_error {
109
    ($p:expr) => {
110
        return Err(Error::ParseError($p))
111
    };
112
}
113
114
impl From<io::Error> for Error {
115
0
    fn from(err: io::Error) -> Error {
116
0
        match err.kind() {
117
0
            io::ErrorKind::UnexpectedEof => Error::EOF,
118
0
            _ => Error::IoError(err),
119
        }
120
0
    }
121
}
122
123
impl From<num::ParseIntError> for Error {
124
0
    fn from(_: num::ParseIntError) -> Error {
125
0
        Error::ParseError(ParseError::Int)
126
0
    }
127
}
128
129
impl From<str::Utf8Error> for Error {
130
0
    fn from(_: str::Utf8Error) -> Error {
131
0
        Error::ParseError(ParseError::Utf8)
132
0
    }
133
}
134
135
trait EnhancedRead {
136
    fn read_until(&mut self, ch: u8, buf: &mut [u8]) -> Result<usize, Error>;
137
}
138
139
impl<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
0
    fn read_until(&mut self, ch: u8, buf: &mut [u8]) -> Result<usize, Error> {
144
0
        let mut collected = 0;
145
0
        while collected < buf.len() {
146
0
            let chunk_size = self.read(&mut buf[collected..=collected])?;
147
0
            if chunk_size == 0 {
148
0
                return Err(Error::EOF);
149
0
            }
150
0
            if buf[collected] == ch {
151
0
                return Ok(collected);
152
0
            }
153
0
            collected += chunk_size;
154
        }
155
0
        parse_error!(ParseError::General)
156
0
    }
157
}
158
159
0
fn parse_bytes(buf: &[u8]) -> Result<usize, Error> {
160
    // A bit kludgy but seems like there is no other way.
161
0
    Ok(str::from_utf8(buf)?.parse()?)
162
0
}
163
164
/// A newtype wrapper around Vec<u8> to ensure validity as a vendor extension.
165
#[derive(Debug, Clone)]
166
pub struct VendorExtensionString(Vec<u8>);
167
168
impl 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
0
    pub fn new(value: Vec<u8>) -> Result<VendorExtensionString, Error> {
178
0
        if value.contains(&b' ') {
179
0
            return Err(Error::BadInput);
180
0
        }
181
0
        Ok(VendorExtensionString(value))
182
0
    }
183
    /// Get the vendor extension string.
184
0
    pub fn value(&self) -> &[u8] {
185
0
        self.0.as_slice()
186
0
    }
187
}
188
189
/// Simple ratio structure since stdlib lacks one.
190
#[derive(Debug, Clone, Copy)]
191
pub struct Ratio {
192
    /// Numerator.
193
    pub num: usize,
194
    /// Denominator.
195
    pub den: usize,
196
}
197
198
impl Ratio {
199
    /// Create a new ratio.
200
0
    pub fn new(num: usize, den: usize) -> Ratio {
201
0
        Ratio { num, den }
202
0
    }
203
204
    /// Parse a ratio from a byte slice.
205
0
    pub fn parse(value: &[u8]) -> Result<Ratio, Error> {
206
0
        let parts: Vec<_> = value.splitn(2, |&b| b == RATIO_SEP).collect();
207
0
        if parts.len() != 2 {
208
0
            parse_error!(ParseError::General)
209
0
        }
210
0
        let num = parse_bytes(parts[0])?;
211
0
        let den = parse_bytes(parts[1])?;
212
0
        Ok(Ratio::new(num, den))
213
0
    }
214
}
215
216
impl fmt::Display for Ratio {
217
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218
0
        write!(f, "{}:{}", self.num, self.den)
219
0
    }
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]
233
pub 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
264
impl Colorspace {
265
    /// Return the bit depth per sample
266
    #[inline]
267
0
    pub fn get_bit_depth(self) -> usize {
268
0
        match self {
269
            Colorspace::Cmono
270
            | Colorspace::C420
271
            | Colorspace::C422
272
            | Colorspace::C444
273
            | Colorspace::C420jpeg
274
            | Colorspace::C420paldv
275
0
            | Colorspace::C420mpeg2 => 8,
276
0
            Colorspace::C420p10 | Colorspace::C422p10 | Colorspace::C444p10 => 10,
277
            Colorspace::Cmono12
278
            | Colorspace::C420p12
279
            | Colorspace::C422p12
280
0
            | Colorspace::C444p12 => 12,
281
        }
282
0
    }
283
284
    /// Return the number of bytes in a sample
285
    #[inline]
286
0
    pub fn get_bytes_per_sample(self) -> usize {
287
0
        if self.get_bit_depth() <= 8 {
288
0
            1
289
        } else {
290
0
            2
291
        }
292
0
    }
293
}
294
295
0
fn get_plane_sizes(width: usize, height: usize, colorspace: Colorspace) -> (usize, usize, usize) {
296
0
    let y_plane_size = width * height * colorspace.get_bytes_per_sample();
297
298
0
    let c420_chroma_size =
299
0
        ((width + 1) / 2) * ((height + 1) / 2) * colorspace.get_bytes_per_sample();
300
0
    let c422_chroma_size = ((width + 1) / 2) * height * colorspace.get_bytes_per_sample();
301
302
0
    let c420_sizes = (y_plane_size, c420_chroma_size, c420_chroma_size);
303
0
    let c422_sizes = (y_plane_size, c422_chroma_size, c422_chroma_size);
304
0
    let c444_sizes = (y_plane_size, y_plane_size, y_plane_size);
305
306
0
    match colorspace {
307
0
        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
0
        | Colorspace::C420mpeg2 => c420_sizes,
314
0
        Colorspace::C422 | Colorspace::C422p10 | Colorspace::C422p12 => c422_sizes,
315
0
        Colorspace::C444 | Colorspace::C444p10 | Colorspace::C444p12 => c444_sizes,
316
    }
317
0
}
318
319
/// Limits on the resources `Decoder` is allowed to use.
320
#[derive(Clone, Copy, Debug)]
321
pub struct Limits {
322
    /// Maximum allowed size of frame buffer, default is 1 GiB.
323
    pub bytes: usize,
324
}
325
326
impl Default for Limits {
327
0
    fn default() -> Limits {
328
0
        Limits {
329
0
            bytes: 1024 * 1024 * 1024,
330
0
        }
331
0
    }
332
}
333
334
/// YUV4MPEG2 decoder.
335
pub 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
349
impl<R: Read> Decoder<R> {
350
    /// Create a new decoder instance.
351
0
    pub fn new(reader: R) -> Result<Decoder<R>, Error> {
352
0
        Decoder::new_with_limits(reader, Limits::default())
353
0
    }
354
355
    /// Create a new decoder instance with custom limits.
356
0
    pub fn new_with_limits(mut reader: R, limits: Limits) -> Result<Decoder<R>, Error> {
357
0
        let mut params_buf = vec![0; MAX_PARAMS_SIZE];
358
0
        let end_params_pos = reader.read_until(TERMINATOR, &mut params_buf)?;
359
0
        if end_params_pos < FILE_MAGICK.len() || !params_buf.starts_with(FILE_MAGICK) {
360
0
            parse_error!(ParseError::InvalidY4M)
361
0
        }
362
0
        let raw_params = params_buf[FILE_MAGICK.len()..end_params_pos].to_owned();
363
0
        let mut width = 0;
364
0
        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
0
        let mut framerate = Ratio::new(25, 1);
368
0
        let mut pixel_aspect = Ratio::new(1, 1);
369
0
        let mut colorspace = None;
370
        // We shouldn't convert it to string because encoding is unspecified.
371
0
        for param in raw_params.split(|&b| b == FIELD_SEP) {
372
0
            if param.is_empty() {
373
0
                continue;
374
0
            }
375
0
            let (name, value) = (param[0], &param[1..]);
376
            // TODO(Kagami): interlacing, comment.
377
0
            match name {
378
0
                b'W' => width = parse_bytes(value)?,
379
0
                b'H' => height = parse_bytes(value)?,
380
0
                b'F' => framerate = Ratio::parse(value)?,
381
0
                b'A' => pixel_aspect = Ratio::parse(value)?,
382
                b'C' => {
383
0
                    colorspace = match value {
384
0
                        b"mono" => Some(Colorspace::Cmono),
385
0
                        b"mono12" => Some(Colorspace::Cmono12),
386
0
                        b"420" => Some(Colorspace::C420),
387
0
                        b"420p10" => Some(Colorspace::C420p10),
388
0
                        b"420p12" => Some(Colorspace::C420p12),
389
0
                        b"422" => Some(Colorspace::C422),
390
0
                        b"422p10" => Some(Colorspace::C422p10),
391
0
                        b"422p12" => Some(Colorspace::C422p12),
392
0
                        b"444" => Some(Colorspace::C444),
393
0
                        b"444p10" => Some(Colorspace::C444p10),
394
0
                        b"444p12" => Some(Colorspace::C444p12),
395
0
                        b"420jpeg" => Some(Colorspace::C420jpeg),
396
0
                        b"420paldv" => Some(Colorspace::C420paldv),
397
0
                        b"420mpeg2" => Some(Colorspace::C420mpeg2),
398
0
                        _ => return Err(Error::UnknownColorspace),
399
                    }
400
                }
401
0
                _ => {}
402
            }
403
        }
404
0
        let colorspace = colorspace.unwrap_or(Colorspace::C420);
405
0
        if width == 0 || height == 0 {
406
0
            parse_error!(ParseError::General)
407
0
        }
408
0
        let (y_len, u_len, v_len) = get_plane_sizes(width, height, colorspace);
409
0
        let frame_size = y_len + u_len + v_len;
410
0
        if frame_size > limits.bytes {
411
0
            return Err(Error::OutOfMemory);
412
0
        }
413
0
        let frame_buf = vec![0; frame_size];
414
0
        Ok(Decoder {
415
0
            reader,
416
0
            params_buf,
417
0
            frame_buf,
418
0
            raw_params,
419
0
            width,
420
0
            height,
421
0
            framerate,
422
0
            pixel_aspect,
423
0
            colorspace,
424
0
            y_len,
425
0
            u_len,
426
0
        })
427
0
    }
428
429
    /// Iterate over frames. End of input is indicated by `Error::EOF`.
430
0
    pub fn read_frame(&mut self) -> Result<Frame, Error> {
431
0
        let end_params_pos = self.reader.read_until(TERMINATOR, &mut self.params_buf)?;
432
0
        if end_params_pos < FRAME_MAGICK.len() || !self.params_buf.starts_with(FRAME_MAGICK) {
433
0
            parse_error!(ParseError::InvalidY4M)
434
0
        }
435
        // We don't parse frame params currently but user has access to them.
436
0
        let start_params_pos = FRAME_MAGICK.len();
437
0
        let raw_params = if end_params_pos - start_params_pos > 0 {
438
            // Check for extra space.
439
0
            if self.params_buf[start_params_pos] != FIELD_SEP {
440
0
                parse_error!(ParseError::InvalidY4M)
441
0
            }
442
0
            Some(self.params_buf[start_params_pos + 1..end_params_pos].to_owned())
443
        } else {
444
0
            None
445
        };
446
0
        self.reader.read_exact(&mut self.frame_buf)?;
447
0
        Ok(Frame::new(
448
0
            [
449
0
                &self.frame_buf[0..self.y_len],
450
0
                &self.frame_buf[self.y_len..self.y_len + self.u_len],
451
0
                &self.frame_buf[self.y_len + self.u_len..],
452
0
            ],
453
0
            raw_params,
454
0
        ))
455
0
    }
456
457
    /// Return file width.
458
    #[inline]
459
0
    pub fn get_width(&self) -> usize {
460
0
        self.width
461
0
    }
462
    /// Return file height.
463
    #[inline]
464
0
    pub fn get_height(&self) -> usize {
465
0
        self.height
466
0
    }
467
    /// Return file framerate.
468
    #[inline]
469
0
    pub fn get_framerate(&self) -> Ratio {
470
0
        self.framerate
471
0
    }
472
    /// Return file pixel aspect.
473
    #[inline]
474
0
    pub fn get_pixel_aspect(&self) -> Ratio {
475
0
        self.pixel_aspect
476
0
    }
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
0
    pub fn get_colorspace(&self) -> Colorspace {
484
0
        self.colorspace
485
0
    }
486
    /// Return file raw parameters.
487
    #[inline]
488
0
    pub fn get_raw_params(&self) -> &[u8] {
489
0
        &self.raw_params
490
0
    }
491
    /// Return the bit depth per sample
492
    #[inline]
493
0
    pub fn get_bit_depth(&self) -> usize {
494
0
        self.colorspace.get_bit_depth()
495
0
    }
496
    /// Return the number of bytes in a sample
497
    #[inline]
498
0
    pub fn get_bytes_per_sample(&self) -> usize {
499
0
        self.colorspace.get_bytes_per_sample()
500
0
    }
501
}
502
503
/// A single frame.
504
#[derive(Debug)]
505
pub struct Frame<'f> {
506
    planes: [&'f [u8]; 3],
507
    raw_params: Option<Vec<u8>>,
508
}
509
510
impl<'f> Frame<'f> {
511
    /// Create a new frame with optional parameters.
512
    /// No heap allocations are made.
513
0
    pub fn new(planes: [&'f [u8]; 3], raw_params: Option<Vec<u8>>) -> Frame<'f> {
514
0
        Frame { planes, raw_params }
515
0
    }
516
517
    /// Create a new frame from data in 16-bit format.
518
0
    pub fn from_u16(planes: [&'f [u16]; 3], raw_params: Option<Vec<u8>>) -> Frame<'f> {
519
0
        Frame::new(
520
0
            [
521
0
                unsafe {
522
0
                    std::slice::from_raw_parts::<u8>(
523
0
                        planes[0].as_ptr() as *const u8,
524
0
                        planes[0].len() * 2,
525
0
                    )
526
0
                },
527
0
                unsafe {
528
0
                    std::slice::from_raw_parts::<u8>(
529
0
                        planes[1].as_ptr() as *const u8,
530
0
                        planes[1].len() * 2,
531
0
                    )
532
0
                },
533
0
                unsafe {
534
0
                    std::slice::from_raw_parts::<u8>(
535
0
                        planes[2].as_ptr() as *const u8,
536
0
                        planes[2].len() * 2,
537
0
                    )
538
0
                },
539
0
            ],
540
0
            raw_params,
541
        )
542
0
    }
543
544
    /// Return Y (first) plane.
545
    #[inline]
546
0
    pub fn get_y_plane(&self) -> &[u8] {
547
0
        self.planes[0]
548
0
    }
549
    /// Return U (second) plane. Empty in case of grayscale.
550
    #[inline]
551
0
    pub fn get_u_plane(&self) -> &[u8] {
552
0
        self.planes[1]
553
0
    }
554
    /// Return V (third) plane. Empty in case of grayscale.
555
    #[inline]
556
0
    pub fn get_v_plane(&self) -> &[u8] {
557
0
        self.planes[2]
558
0
    }
559
    /// Return frame raw parameters if any.
560
    #[inline]
561
0
    pub fn get_raw_params(&self) -> Option<&[u8]> {
562
0
        self.raw_params.as_ref().map(|v| &v[..])
563
0
    }
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)]
569
pub 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
578
impl EncoderBuilder {
579
    /// Create a new encoder builder.
580
0
    pub fn new(width: usize, height: usize, framerate: Ratio) -> EncoderBuilder {
581
0
        EncoderBuilder {
582
0
            width,
583
0
            height,
584
0
            framerate,
585
0
            pixel_aspect: Ratio::new(1, 1),
586
0
            colorspace: Colorspace::C420,
587
0
            vendor_extensions: vec![],
588
0
        }
589
0
    }
590
591
    /// Specify file colorspace.
592
0
    pub fn with_colorspace(mut self, colorspace: Colorspace) -> Self {
593
0
        self.colorspace = colorspace;
594
0
        self
595
0
    }
596
597
    /// Specify file pixel aspect.
598
0
    pub fn with_pixel_aspect(mut self, pixel_aspect: Ratio) -> Self {
599
0
        self.pixel_aspect = pixel_aspect;
600
0
        self
601
0
    }
602
603
    /// Add vendor extension.
604
0
    pub fn append_vendor_extension(mut self, x_option: VendorExtensionString) -> Self {
605
0
        self.vendor_extensions.push(x_option.0);
606
0
        self
607
0
    }
608
609
    /// Write header to the stream and create encoder instance.
610
0
    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
0
        writer.write_all(FILE_MAGICK)?;
613
0
        write!(
614
0
            writer,
615
0
            "W{} H{} F{}",
616
            self.width, self.height, self.framerate
617
0
        )?;
618
0
        if self.pixel_aspect.num != 1 || self.pixel_aspect.den != 1 {
619
0
            write!(writer, " A{}", self.pixel_aspect)?;
620
0
        }
621
0
        for x_option in self.vendor_extensions.iter() {
622
0
            write!(writer, " X")?;
623
0
            writer.write_all(x_option)?;
624
        }
625
0
        write!(writer, " {:?}", self.colorspace)?;
626
0
        writer.write_all(&[TERMINATOR])?;
627
0
        let (y_len, u_len, v_len) = get_plane_sizes(self.width, self.height, self.colorspace);
628
0
        Ok(Encoder {
629
0
            writer,
630
0
            y_len,
631
0
            u_len,
632
0
            v_len,
633
0
        })
634
0
    }
635
}
636
637
/// YUV4MPEG2 encoder.
638
pub struct Encoder<W: Write> {
639
    writer: W,
640
    y_len: usize,
641
    u_len: usize,
642
    v_len: usize,
643
}
644
645
impl<W: Write> Encoder<W> {
646
    /// Write next frame to the stream.
647
0
    pub fn write_frame(&mut self, frame: &Frame) -> Result<(), Error> {
648
0
        if frame.get_y_plane().len() != self.y_len
649
0
            || frame.get_u_plane().len() != self.u_len
650
0
            || frame.get_v_plane().len() != self.v_len
651
        {
652
0
            return Err(Error::BadInput);
653
0
        }
654
0
        self.writer.write_all(FRAME_MAGICK)?;
655
0
        if let Some(params) = frame.get_raw_params() {
656
0
            self.writer.write_all(&[FIELD_SEP])?;
657
0
            self.writer.write_all(params)?;
658
0
        }
659
0
        self.writer.write_all(&[TERMINATOR])?;
660
0
        self.writer.write_all(frame.get_y_plane())?;
661
0
        self.writer.write_all(frame.get_u_plane())?;
662
0
        self.writer.write_all(frame.get_v_plane())?;
663
0
        Ok(())
664
0
    }
665
}
666
667
/// Create a new decoder instance. Alias for `Decoder::new`.
668
0
pub fn decode<R: Read>(reader: R) -> Result<Decoder<R>, Error> {
669
0
    Decoder::new(reader)
670
0
}
671
672
/// Create a new encoder builder. Alias for `EncoderBuilder::new`.
673
0
pub fn encode(width: usize, height: usize, framerate: Ratio) -> EncoderBuilder {
674
0
    EncoderBuilder::new(width, height, framerate)
675
0
}