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: &str, _) = grain_table_header(input).map_err(|e: Err>| anyhow::anyhow!(e.to_string()))?;
55 let (_, segments: Vec>) =
56 many1(grain_table_segment)(input).map_err(|e: Err>| 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: &str, _) = delimited(first:multispace0, second:tag("filmgrn1"), third: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(first:multispace0, second:parser, third: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(parser:preceded(first:opt(char('-')), second: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}
791