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