av1_grain/
parse.rs

1// Copyright (c) 2022-2022, The rav1e contributors. All rights reserved
2//
3// This source code is subject to the terms of the BSD 2 Clause License and
4// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
5// was not distributed with this source code in the LICENSE file, you can
6// obtain it at www.aomedia.org/license/software. If the Alliance for Open
7// Media Patent License 1.0 was not distributed with this source code in the
8// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
9
10use arrayvec::ArrayVec;
11use nom::{
12    branch::alt,
13    bytes::complete::tag,
14    character::complete::{char, digit1, line_ending, multispace0, multispace1, space0, space1},
15    combinator::{eof, map_res, opt, recognize},
16    error::{Error as NomError, ErrorKind, FromExternalError, ParseError},
17    multi::{many1, separated_list0, separated_list1},
18    sequence::{delimited, preceded},
19    AsChar, Compare, Err as NomErr, IResult, Input, Parser,
20};
21
22use crate::{GrainTableSegment, NUM_UV_COEFFS, NUM_UV_POINTS, NUM_Y_COEFFS, NUM_Y_POINTS};
23
24/// This file has the implementation details of the grain table.
25///
26/// The file format is an ascii representation for readability and
27/// editability. Array parameters are separated from the non-array
28/// parameters and prefixed with a few characters to make for easy
29/// localization with a parameter set. Each entry is prefixed with "E"
30/// and the other parameters are only specified if "apply-grain" is
31/// non-zero.
32///
33/// ```text
34/// filmgrn1
35/// E <start-time> <end-time> <apply-grain> <random-seed> <dynamic-grain>
36///  p <ar_coeff_lag> <ar_coeff_shift> <grain_scale_shift> ...
37///  sY <num_y_points> <point_0_x> <point_0_y> ...
38///  sCb <num_cb_points> <point_0_x> <point_0_y> ...
39///  sCr <num_cr_points> <point_0_x> <point_0_y> ...
40///  cY <ar_coeff_y_0> ....
41///  cCb <ar_coeff_cb_0> ....
42///  cCr <ar_coeff_cr_0> ....
43/// E <start-time> ...
44/// ```
45///
46/// # Errors
47///
48/// - If the file cannot be opened
49/// - If the file does not contain a properly formatted film grain table
50#[inline]
51pub fn parse_grain_table(input: &str) -> anyhow::Result<Vec<GrainTableSegment>> {
52    let (input, _) = grain_table_header(input).map_err(|e| anyhow::anyhow!(e.to_string()))?;
53    let (_, segments) = many1(grain_table_segment)
54        .parse(input)
55        .map_err(|e| anyhow::anyhow!(e.to_string()))?;
56    Ok(segments.into_iter().flatten().collect())
57}
58
59fn grain_table_header(input: &str) -> IResult<&str, ()> {
60    let (input, _) = delimited(multispace0, tag("filmgrn1"), line_ending).parse(input)?;
61    Ok((input, ()))
62}
63
64fn line<I, O, E: ParseError<I>, F>(parser: F) -> impl Parser<I, Output = O, Error = E>
65where
66    I: Input + Clone + Compare<&'static str>,
67    <I as Input>::Item: AsChar + Clone,
68    F: Parser<I, Output = O, Error = E>,
69{
70    delimited(multispace0, parser, alt((line_ending, eof)))
71}
72
73fn grain_table_segment(input: &str) -> IResult<&str, Option<GrainTableSegment>> {
74    let (input, e_params) = e_params(input)?;
75    if !e_params.apply {
76        // I'm not sure *why* there's even an option to generate a film grain config
77        // that doesn't apply film grain. But, well, I didn't make this format.
78        return Ok((input, None));
79    }
80
81    let (input, p_params) = p_params(input)?;
82    let (input, s_y_params) = s_y_params(input)?;
83    let (input, s_cb_params) = s_cb_params(input)?;
84    let (input, s_cr_params) = s_cr_params(input)?;
85    let coeff_count = (2 * p_params.ar_coeff_lag * (p_params.ar_coeff_lag + 1)) as usize;
86    let (input, c_y_params) = c_y_params(input, coeff_count)?;
87    let (input, c_cb_params) = c_cb_params(input, coeff_count)?;
88    let (input, c_cr_params) = c_cr_params(input, coeff_count)?;
89
90    Ok((
91        input,
92        Some(GrainTableSegment {
93            start_time: e_params.start,
94            end_time: e_params.end,
95            scaling_points_y: s_y_params,
96            scaling_points_cb: s_cb_params,
97            scaling_points_cr: s_cr_params,
98            scaling_shift: p_params.scaling_shift,
99            ar_coeff_lag: p_params.ar_coeff_lag,
100            ar_coeffs_y: c_y_params,
101            ar_coeffs_cb: c_cb_params,
102            ar_coeffs_cr: c_cr_params,
103            ar_coeff_shift: p_params.ar_coeff_shift,
104            cb_mult: p_params.cb_mult,
105            cb_luma_mult: p_params.cb_luma_mult,
106            cb_offset: p_params.cb_offset,
107            cr_mult: p_params.cr_mult,
108            cr_luma_mult: p_params.cr_luma_mult,
109            cr_offset: p_params.cr_offset,
110            overlap_flag: p_params.overlap_flag,
111            chroma_scaling_from_luma: p_params.chroma_scaling_from_luma,
112            grain_scale_shift: p_params.grain_scale_shift,
113            random_seed: e_params.seed,
114        }),
115    ))
116}
117
118#[derive(Debug, Clone, Copy)]
119struct EParams {
120    pub start: u64,
121    pub end: u64,
122    pub apply: bool,
123    pub seed: u16,
124}
125
126fn e_params(input: &str) -> IResult<&str, EParams> {
127    let (input, params) = map_res(
128        line(preceded(
129            tag("E"),
130            preceded(space1, separated_list1(space1, digit1)),
131        )),
132        |items: Vec<&str>| {
133            if items.len() != 5 {
134                return Err(NomErr::<NomError<&str>>::Failure(
135                    NomError::from_external_error(
136                        input,
137                        ErrorKind::Verify,
138                        "Expected 5 values on E line",
139                    ),
140                ));
141            }
142            let parsed = EParams {
143                start: items[0].parse().map_err(|_e| {
144                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
145                        input,
146                        ErrorKind::Digit,
147                        "Failed to parse start_time",
148                    ))
149                })?,
150                end: items[1].parse().map_err(|_e| {
151                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
152                        input,
153                        ErrorKind::Digit,
154                        "Failed to parse end_time",
155                    ))
156                })?,
157                apply: items[2].parse::<u8>().map_err(|_e| {
158                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
159                        input,
160                        ErrorKind::Digit,
161                        "Failed to parse apply_grain",
162                    ))
163                })? > 0,
164                seed: items[3].parse().map_err(|_e| {
165                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
166                        input,
167                        ErrorKind::Digit,
168                        "Failed to parse random_seed",
169                    ))
170                })?,
171            };
172            Ok::<_, NomErr<NomError<&str>>>(parsed)
173        },
174    )
175    .parse(input)?;
176
177    if params.end < params.start {
178        return Err(NomErr::Failure(NomError::from_external_error(
179            input,
180            ErrorKind::Verify,
181            "Start time must be before end time",
182        )));
183    }
184
185    Ok((input, params))
186}
187
188#[derive(Debug, Clone, Copy)]
189struct PParams {
190    ar_coeff_lag: u8,
191    ar_coeff_shift: u8,
192    grain_scale_shift: u8,
193    scaling_shift: u8,
194    chroma_scaling_from_luma: bool,
195    overlap_flag: bool,
196    cb_mult: u8,
197    cb_luma_mult: u8,
198    cb_offset: u16,
199    cr_mult: u8,
200    cr_luma_mult: u8,
201    cr_offset: u16,
202}
203
204#[allow(clippy::too_many_lines)]
205fn p_params(input: &str) -> IResult<&str, PParams> {
206    let (input, params) = map_res(
207        line(preceded(
208            tag("p"),
209            preceded(space1, separated_list1(space1, digit1)),
210        )),
211        |items: Vec<&str>| {
212            if items.len() != 12 {
213                return Err(NomErr::<NomError<&str>>::Failure(
214                    NomError::from_external_error(
215                        input,
216                        ErrorKind::Verify,
217                        "Expected 12 values on p line",
218                    ),
219                ));
220            }
221
222            let parsed = PParams {
223                ar_coeff_lag: items[0].parse().map_err(|_e| {
224                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
225                        input,
226                        ErrorKind::Digit,
227                        "Failed to parse ar_coeff_lag",
228                    ))
229                })?,
230                ar_coeff_shift: items[1].parse().map_err(|_e| {
231                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
232                        input,
233                        ErrorKind::Digit,
234                        "Failed to parse ar_coeff_shift",
235                    ))
236                })?,
237                grain_scale_shift: items[2].parse().map_err(|_e| {
238                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
239                        input,
240                        ErrorKind::Digit,
241                        "Failed to parse grain_scale_shift",
242                    ))
243                })?,
244                scaling_shift: items[3].parse().map_err(|_e| {
245                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
246                        input,
247                        ErrorKind::Digit,
248                        "Failed to parse scaling_shift",
249                    ))
250                })?,
251                chroma_scaling_from_luma: items[4].parse::<u8>().map_err(|_e| {
252                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
253                        input,
254                        ErrorKind::Digit,
255                        "Failed to parse chroma_scaling_from_luma",
256                    ))
257                })? > 0,
258                overlap_flag: items[5].parse::<u8>().map_err(|_e| {
259                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
260                        input,
261                        ErrorKind::Digit,
262                        "Failed to parse overlap_flag",
263                    ))
264                })? > 0,
265                cb_mult: items[6].parse().map_err(|_e| {
266                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
267                        input,
268                        ErrorKind::Digit,
269                        "Failed to parse cb_mult",
270                    ))
271                })?,
272                cb_luma_mult: items[7].parse().map_err(|_e| {
273                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
274                        input,
275                        ErrorKind::Digit,
276                        "Failed to parse cb_luma_mult",
277                    ))
278                })?,
279                cb_offset: items[8].parse().map_err(|_e| {
280                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
281                        input,
282                        ErrorKind::Digit,
283                        "Failed to parse cb_offset",
284                    ))
285                })?,
286                cr_mult: items[9].parse().map_err(|_e| {
287                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
288                        input,
289                        ErrorKind::Digit,
290                        "Failed to parse cr_mult",
291                    ))
292                })?,
293                cr_luma_mult: items[10].parse().map_err(|_e| {
294                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
295                        input,
296                        ErrorKind::Digit,
297                        "Failed to parse cr_luma_mult",
298                    ))
299                })?,
300                cr_offset: items[11].parse().map_err(|_e| {
301                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
302                        input,
303                        ErrorKind::Digit,
304                        "Failed to parse cr_offset",
305                    ))
306                })?,
307            };
308            Ok::<_, NomErr<NomError<&str>>>(parsed)
309        },
310    )
311    .parse(input)?;
312
313    if params.scaling_shift < 8 || params.scaling_shift > 11 {
314        return Err(NomErr::Failure(NomError::from_external_error(
315            input,
316            ErrorKind::Verify,
317            "scaling_shift must be between 8 and 11",
318        )));
319    }
320    if params.ar_coeff_lag > 3 {
321        return Err(NomErr::Failure(NomError::from_external_error(
322            input,
323            ErrorKind::Verify,
324            "ar_coeff_lag must be between 0 and 3",
325        )));
326    }
327    if params.ar_coeff_shift < 6 || params.ar_coeff_shift > 9 {
328        return Err(NomErr::Failure(NomError::from_external_error(
329            input,
330            ErrorKind::Verify,
331            "ar_coeff_shift must be between 6 and 9",
332        )));
333    }
334
335    Ok((input, params))
336}
337
338fn s_y_params(input: &str) -> IResult<&str, ArrayVec<[u8; 2], NUM_Y_POINTS>> {
339    let (input, values) = map_res(
340        line(preceded(
341            tag("sY"),
342            preceded(space1, separated_list1(space1, digit1)),
343        )),
344        |items: Vec<&str>| {
345            let mut parsed = Vec::with_capacity(items.len());
346            for item in items {
347                parsed.push(item.parse::<u8>().map_err(|_e| {
348                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
349                        input,
350                        ErrorKind::Digit,
351                        "Failed to parse Y-plane points",
352                    ))
353                })?);
354            }
355            Ok::<_, NomErr<NomError<&str>>>(parsed)
356        },
357    )
358    .parse(input)?;
359
360    let len = values[0] as usize;
361    if values.len() != len * 2 + 1 {
362        return Err(NomErr::Failure(NomError::from_external_error(
363            input,
364            ErrorKind::Verify,
365            format!(
366                "Expected {} Y-plane points, got {}",
367                len * 2,
368                values.len() - 1
369            ),
370        )));
371    }
372
373    Ok((
374        input,
375        values[1..]
376            .chunks_exact(2)
377            .map(|chunk| [chunk[0], chunk[1]])
378            .collect(),
379    ))
380}
381
382fn s_cb_params(input: &str) -> IResult<&str, ArrayVec<[u8; 2], NUM_UV_POINTS>> {
383    let (input, values) = map_res(
384        line(preceded(
385            tag("sCb"),
386            preceded(space1, separated_list1(space1, digit1)),
387        )),
388        |items: Vec<&str>| {
389            let mut parsed = Vec::with_capacity(items.len());
390            for item in items {
391                parsed.push(item.parse::<u8>().map_err(|_e| {
392                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
393                        input,
394                        ErrorKind::Digit,
395                        "Failed to parse Cb-plane points",
396                    ))
397                })?);
398            }
399            Ok::<_, NomErr<NomError<&str>>>(parsed)
400        },
401    )
402    .parse(input)?;
403
404    let len = values[0] as usize;
405    if values.len() != len * 2 + 1 {
406        return Err(NomErr::Failure(NomError::from_external_error(
407            input,
408            ErrorKind::Verify,
409            format!(
410                "Expected {} Cb-plane points, got {}",
411                len * 2,
412                values.len() - 1
413            ),
414        )));
415    }
416
417    Ok((
418        input,
419        values[1..]
420            .chunks_exact(2)
421            .map(|chunk| [chunk[0], chunk[1]])
422            .collect(),
423    ))
424}
425
426fn s_cr_params(input: &str) -> IResult<&str, ArrayVec<[u8; 2], NUM_UV_POINTS>> {
427    let (input, values) = map_res(
428        line(preceded(
429            tag("sCr"),
430            preceded(space1, separated_list1(space1, digit1)),
431        )),
432        |items: Vec<&str>| {
433            let mut parsed = Vec::with_capacity(items.len());
434            for item in items {
435                parsed.push(item.parse::<u8>().map_err(|_e| {
436                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
437                        input,
438                        ErrorKind::Digit,
439                        "Failed to parse Cr-plane points",
440                    ))
441                })?);
442            }
443            Ok::<_, NomErr<NomError<&str>>>(parsed)
444        },
445    )
446    .parse(input)?;
447
448    let len = values[0] as usize;
449    if values.len() != len * 2 + 1 {
450        return Err(NomErr::Failure(NomError::from_external_error(
451            input,
452            ErrorKind::Verify,
453            format!(
454                "Expected {} Cr-plane points, got {}",
455                len * 2,
456                values.len() - 1
457            ),
458        )));
459    }
460
461    Ok((
462        input,
463        values[1..]
464            .chunks_exact(2)
465            .map(|chunk| [chunk[0], chunk[1]])
466            .collect(),
467    ))
468}
469
470fn integer(input: &str) -> IResult<&str, &str> {
471    recognize(preceded(opt(char('-')), digit1)).parse(input)
472}
473
474fn c_y_params(input: &str, count: usize) -> IResult<&str, ArrayVec<i8, NUM_Y_COEFFS>> {
475    let (input, values) = map_res(
476        line(preceded(
477            tag("cY"),
478            preceded(space0, separated_list0(multispace1, integer)),
479        )),
480        |items: Vec<&str>| {
481            let mut parsed = Vec::with_capacity(items.len());
482            for item in items {
483                parsed.push(item.parse::<i8>().map_err(|_e| {
484                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
485                        input,
486                        ErrorKind::Digit,
487                        "Failed to parse Y-plane coeffs",
488                    ))
489                })?);
490            }
491            Ok::<_, NomErr<NomError<&str>>>(parsed)
492        },
493    )
494    .parse(input)?;
495
496    if values.len() != count {
497        return Err(NomErr::Failure(NomError::from_external_error(
498            input,
499            ErrorKind::Verify,
500            format!("Expected {} Y-plane coeffs, got {}", count, values.len()),
501        )));
502    }
503
504    Ok((input, values.into_iter().collect()))
505}
506
507fn c_cb_params(input: &str, count: usize) -> IResult<&str, ArrayVec<i8, NUM_UV_COEFFS>> {
508    let (input, values) = map_res(
509        line(preceded(
510            tag("cCb"),
511            preceded(space1, separated_list1(multispace1, integer)),
512        )),
513        |items: Vec<&str>| {
514            let mut parsed = Vec::with_capacity(items.len());
515            for item in items {
516                parsed.push(item.parse::<i8>().map_err(|_e| {
517                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
518                        input,
519                        ErrorKind::Digit,
520                        "Failed to parse Cb-plane coeffs",
521                    ))
522                })?);
523            }
524            Ok::<_, NomErr<NomError<&str>>>(parsed)
525        },
526    )
527    .parse(input)?;
528
529    if values.len() != count + 1 {
530        return Err(NomErr::Failure(NomError::from_external_error(
531            input,
532            ErrorKind::Verify,
533            format!(
534                "Expected {} Cb-plane coeffs, got {}",
535                count + 1,
536                values.len()
537            ),
538        )));
539    }
540
541    Ok((input, values.into_iter().collect()))
542}
543
544fn c_cr_params(input: &str, count: usize) -> IResult<&str, ArrayVec<i8, NUM_UV_COEFFS>> {
545    let (input, values) = map_res(
546        line(preceded(
547            tag("cCr"),
548            preceded(space1, separated_list1(multispace1, integer)),
549        )),
550        |items: Vec<&str>| {
551            let mut parsed = Vec::with_capacity(items.len());
552            for item in items {
553                parsed.push(item.parse::<i8>().map_err(|_e| {
554                    NomErr::<NomError<&str>>::Failure(NomError::from_external_error(
555                        input,
556                        ErrorKind::Digit,
557                        "Failed to parse Cr-plane coeffs",
558                    ))
559                })?);
560            }
561            Ok::<_, NomErr<NomError<&str>>>(parsed)
562        },
563    )
564    .parse(input)?;
565
566    if values.len() != count + 1 {
567        return Err(NomErr::Failure(NomError::from_external_error(
568            input,
569            ErrorKind::Verify,
570            format!(
571                "Expected {} Cr-plane coeffs, got {}",
572                count + 1,
573                values.len()
574            ),
575        )));
576    }
577
578    Ok((input, values.into_iter().collect()))
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn parse_luma_only_table() {
587        // This is the luma-only table format generated by
588        // both aomenc's photon noise utility and by av1an.
589        let input = r#"filmgrn1
590E 0 9223372036854775807 1 7391 1
591  p 0 6 0 8 0 1 0 0 0 0 0 0
592  sY 14  0 20 20 5 39 4 59 3 78 3 98 3 118 3 137 3 157 3 177 3 196 3 216 4 235 4 255 4
593  sCb 0
594  sCr 0
595  cY
596  cCb 0
597  cCr 0
598"#;
599        let expected = GrainTableSegment {
600            start_time: 0,
601            end_time: 9_223_372_036_854_775_807,
602            scaling_points_y: ArrayVec::from([
603                [0, 20],
604                [20, 5],
605                [39, 4],
606                [59, 3],
607                [78, 3],
608                [98, 3],
609                [118, 3],
610                [137, 3],
611                [157, 3],
612                [177, 3],
613                [196, 3],
614                [216, 4],
615                [235, 4],
616                [255, 4],
617            ]),
618            scaling_points_cb: ArrayVec::new(),
619            scaling_points_cr: ArrayVec::new(),
620            scaling_shift: 8,
621            ar_coeff_lag: 0,
622            ar_coeffs_y: ArrayVec::new(),
623            ar_coeffs_cb: ArrayVec::try_from([0].as_slice()).expect("Arrayvec has capacity"),
624            ar_coeffs_cr: ArrayVec::try_from([0].as_slice()).expect("Arrayvec has capacity"),
625            ar_coeff_shift: 6,
626            cb_mult: 0,
627            cb_luma_mult: 0,
628            cb_offset: 0,
629            cr_mult: 0,
630            cr_luma_mult: 0,
631            cr_offset: 0,
632            overlap_flag: true,
633            chroma_scaling_from_luma: false,
634            grain_scale_shift: 0,
635            random_seed: 7391,
636        };
637        let output = parse_grain_table(input).expect("Test failed");
638        assert_eq!(vec![expected], output);
639    }
640
641    #[test]
642    fn parse_luma_chroma_table() {
643        // This is the luma+chroma table format generated by
644        // both aomenc's photon noise utility and by av1an.
645        let input = r#"filmgrn1
646E 0 9223372036854775807 1 7391 1
647  p 0 6 0 8 0 1 128 192 256 128 192 256
648  sY 14  0 0 20 4 39 3 59 3 78 3 98 3 118 4 137 4 157 4 177 4 196 4 216 5 235 5 255 5
649  sCb 10 0 0 28 0 57 0 85 0 113 0 142 0 170 0 198 0 227 0 255 1
650  sCr 10 0 0 28 0 57 0 85 0 113 0 142 0 170 0 198 0 227 0 255 1
651  cY
652  cCb 0
653  cCr 0
654"#;
655        let expected = GrainTableSegment {
656            start_time: 0,
657            end_time: 9_223_372_036_854_775_807,
658            scaling_points_y: ArrayVec::from([
659                [0, 0],
660                [20, 4],
661                [39, 3],
662                [59, 3],
663                [78, 3],
664                [98, 3],
665                [118, 4],
666                [137, 4],
667                [157, 4],
668                [177, 4],
669                [196, 4],
670                [216, 5],
671                [235, 5],
672                [255, 5],
673            ]),
674            scaling_points_cb: ArrayVec::from([
675                [0, 0],
676                [28, 0],
677                [57, 0],
678                [85, 0],
679                [113, 0],
680                [142, 0],
681                [170, 0],
682                [198, 0],
683                [227, 0],
684                [255, 1],
685            ]),
686            scaling_points_cr: ArrayVec::from([
687                [0, 0],
688                [28, 0],
689                [57, 0],
690                [85, 0],
691                [113, 0],
692                [142, 0],
693                [170, 0],
694                [198, 0],
695                [227, 0],
696                [255, 1],
697            ]),
698            scaling_shift: 8,
699            ar_coeff_lag: 0,
700            ar_coeffs_y: ArrayVec::new(),
701            ar_coeffs_cb: ArrayVec::try_from([0].as_slice()).expect("Arrayvec has capacity"),
702            ar_coeffs_cr: ArrayVec::try_from([0].as_slice()).expect("Arrayvec has capacity"),
703            ar_coeff_shift: 6,
704            cb_mult: 128,
705            cb_luma_mult: 192,
706            cb_offset: 256,
707            cr_mult: 128,
708            cr_luma_mult: 192,
709            cr_offset: 256,
710            overlap_flag: true,
711            chroma_scaling_from_luma: false,
712            grain_scale_shift: 0,
713            random_seed: 7391,
714        };
715        let output = parse_grain_table(input).expect("Test failed");
716        assert_eq!(vec![expected], output);
717    }
718
719    #[test]
720    fn parse_complex_table() {
721        let input = r#"filmgrn1
722E 0 417083 1 7391 1
723	p 3 7 0 11 0 1 128 192 256 128 192 256
724	sY 6  0 53 13 53 40 64 94 49 121 46 255 46
725	sCb 2 0 14 255 13
726	sCr 2 0 12 255 14
727	cY 1 -4 1 4 8 3 -2 -6 9 14 -27 -25 -2 4 5 15 -80 94 28 -3 -2 6 -47 121
728	cCb -3 1 -4 6 -1 2 -2 1 11 -10 -2 -16 -1 3 -2 -14 -26 65 19 -3 -5 2 -6 75 -1
729	cCr 0 0 -4 8 -1 0 1 2 -1 -9 4 -7 -5 -2 -5 -14 0 45 18 3 -3 4 8 49 5
730E 417083 7090416 1 0 1
731	p 3 7 0 11 0 1 128 192 256 128 192 256
732	sY 4  0 46 40 54 108 39 255 38
733	sCb 2 0 14 255 14
734	sCr 2 0 12 255 14
735	cY 1 -4 1 5 8 4 -2 -6 9 13 -28 -28 -5 5 5 13 -76 91 32 -1 -3 7 -50 124
736	cCb -2 1 -3 3 -2 1 -1 2 8 -10 0 -12 -2 2 -1 -14 -20 61 18 -1 -4 -2 -1 70 -1
737	cCr 0 0 -3 6 -1 -1 0 1 -2 -8 6 -4 -5 -2 -6 -12 4 41 17 4 -2 3 13 44 5
738E 7090416 7507500 1 0 1
739	p 3 7 0 11 0 1 128 192 256 128 192 256
740	sY 4  0 54 40 64 108 46 255 44
741	sCb 2 0 14 255 13
742	sCr 2 0 12 255 14
743	cY 1 -4 2 3 7 3 -2 -6 9 14 -26 -25 -3 5 6 15 -81 95 27 -3 -3 5 -46 121
744	cCb -2 1 -4 4 -2 1 -1 2 9 -12 3 -13 -1 2 -2 -16 -26 66 17 -2 -5 -1 1 73 0
745	cCr 1 -1 -5 8 -1 -1 1 1 -3 -9 9 -5 -6 -2 -7 -14 1 44 17 3 -3 5 15 46 4
746E 7507500 10010000 1 0 1
747	p 3 7 0 11 0 1 128 192 256 128 192 256
748	sY 4  0 49 40 59 108 43 255 41
749	sCb 2 0 14 255 14
750	sCr 2 0 13 255 15
751	cY 1 -4 0 6 8 3 -2 -5 8 14 -29 -26 -3 4 3 15 -76 92 29 -2 -3 8 -49 121
752	cCb -3 0 -3 6 0 1 -2 1 10 -9 -4 -15 -1 2 -1 -13 -22 62 20 -3 -4 2 -7 73 -1
753	cCr -1 0 -3 6 0 0 0 2 0 -9 2 -7 -5 -1 -4 -14 0 45 19 2 -2 3 7 50 4
754E 10010000 13346666 1 0 1
755	p 3 7 0 11 0 1 128 192 256 128 192 256
756	sY 6  0 33 27 39 40 53 54 55 108 52 255 52
757	sCb 2 0 16 255 14
758	sCr 2 0 11 255 12
759	cY 1 -4 1 5 9 4 -2 -7 12 11 -27 -30 -5 5 6 10 -73 89 35 -1 -3 6 -49 124
760	cCb -2 0 -2 1 -2 1 -2 0 9 -9 -2 -14 -1 2 0 -11 -26 65 18 -2 -4 -2 -8 75 -5
761	cCr 0 0 -4 5 -2 0 1 3 -1 -9 6 -5 -5 -1 -6 -14 1 43 18 4 -3 3 13 49 3
762E 13346666 16683333 1 0 1
763	p 3 7 0 11 0 1 128 192 256 128 192 256
764	sY 6  0 36 27 42 40 58 54 60 108 57 255 57
765	sCb 2 0 15 255 14
766	sCr 4 0 11 40 17 94 13 255 13
767	cY 1 -4 1 5 8 3 -2 -6 10 12 -27 -27 -4 4 5 12 -73 90 32 -2 -3 6 -47 121
768	cCb -2 0 -3 4 -1 1 -2 0 10 -9 -2 -14 1 3 -1 -10 -24 62 16 -2 -4 0 -6 72 -7
769	cCr 0 0 -3 6 -1 0 1 3 1 -9 3 -7 -5 -1 -5 -14 -2 46 19 2 -3 3 7 54 3
770E 16683333 17100416 1 0 1
771	p 3 7 0 11 0 1 128 192 256 128 192 256
772	sY 7  0 41 13 41 27 49 40 66 54 68 108 65 255 65
773	sCb 2 0 18 255 14
774	sCr 4 0 11 40 18 67 14 255 13
775	cY 0 -3 1 4 7 3 -2 -5 7 13 -27 -23 -3 4 5 15 -79 94 26 -3 -2 5 -45 120
776	cCb -1 -2 -1 1 0 0 -3 -2 12 -6 -3 -15 3 2 2 -8 -42 75 12 -3 -4 -2 -8 82 -3
777	cCr 0 0 -5 7 -2 0 1 3 0 -11 6 -7 -5 -1 -6 -15 -5 48 18 2 -3 3 10 55 2
778E 17100416 20020000 1 0 1
779	p 3 7 0 11 0 1 128 192 256 128 192 256
780	sY 6  0 37 27 44 40 61 54 63 108 60 255 60
781	sCb 2 0 14 255 14
782	sCr 4 0 11 40 18 94 13 255 13
783	cY 1 -3 0 6 7 2 -1 -5 7 13 -28 -25 -2 3 3 13 -73 91 29 -2 -2 7 -47 119
784	cCb -2 -1 -3 4 0 1 -2 -1 11 -7 -6 -15 1 2 -1 -9 -25 63 16 -3 -4 2 -11 73 -8
785	cCr -1 1 -2 6 0 1 0 2 3 -9 -2 -10 -4 0 -3 -14 -6 50 20 0 -3 3 -1 59 3
786E 20020000 9223372036854775807 1 0 1
787	p 3 6 0 11 0 1 128 192 256 128 192 256
788	sY 6  0 32 27 37 40 50 54 52 121 49 255 49
789	sCb 4 0 21 40 23 81 17 255 15
790	sCr 2 0 11 255 12
791	cY 1 -3 1 2 5 3 -2 -6 8 6 -12 -18 -2 3 5 7 -42 44 21 -3 -1 4 -29 67
792	cCb -1 0 1 0 -1 0 -1 0 5 -4 -3 -9 1 1 2 -4 -21 39 10 -2 -3 -2 -7 44 1
793	cCr 1 0 -3 2 -3 -1 0 1 -1 -4 5 -2 -1 -1 -5 -6 3 20 10 4 -2 0 9 23 -1"#;
794        let output = parse_grain_table(input);
795        assert!(output.is_ok());
796    }
797}