Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_graphics/tests/hevc_films.rs

3.6 KiB, 3 runs

created by r1870400018:21166, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Can the still-picture decoder draw a film's first frame?
2//!
3//! Three quarters of the films in the survey corpus are HEVC, and this crate
4//! already carries a complete HEVC intra decoder written for HEIC photographs.
5//! A film's first frame is an IDR coded with intra prediction only, which is the
6//! same thing a photograph is -- so the question is whether the parameter sets a
7//! camera writes for a film stay inside what the still decoder reads.
8//!
9//! This test asks that question of the real corpus and reports the answer rather
10//! than asserting one, because "how many films can be drawn today" is a
11//! measurement and not a promise. Point `HEVC_FILMS` at a directory of films.
12//!
13//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
14//! Anthropic Claude
15
16use oxedyne_fe2o3_core::prelude::*;
17use oxedyne_fe2o3_graphics::{
18 hevc,
19 mp4,
20};
21
22use std::{
23 collections::BTreeMap,
24 env,
25 fs,
26 path::PathBuf,
27};
28
29/// Every film under a directory, in a stable order.
30fn films(dir: &std::path::Path, out: &mut Vec<PathBuf>) {
31 let entries = match fs::read_dir(dir) {
32 Ok(e) => e,
33 Err(_) => return,
34 };
35 let mut here = Vec::new();
36 for entry in entries.flatten() {
37 let path = entry.path();
38 if path.is_dir() {
39 films(&path, out);
40 } else {
41 let name = path.to_string_lossy().to_lowercase();
42 if name.ends_with(".mp4") || name.ends_with(".mov") {
43 here.push(path);
44 }
45 }
46 }
47 here.sort();
48 out.extend(here);
49}
50
51#[test]
52fn how_many_films_the_still_decoder_can_already_draw() -> Outcome<()> {
53 let dir = match env::var("HEVC_FILMS") {
54 Ok(d) if !d.is_empty() => PathBuf::from(d),
55 _ => {
56 println!("skipped: set HEVC_FILMS to a directory of films");
57 return Ok(());
58 },
59 };
60 let cap = match env::var("HEVC_FILMS_MAX") {
61 Ok(n) => res!(n.parse::<usize>()),
62 Err(_) => 400,
63 };
64
65 let mut all = Vec::new();
66 films(&dir, &mut all);
67 println!("{} films under {}", all.len(), dir.display());
68
69 let mut hevc_films = 0usize;
70 let mut drawn = 0usize;
71 let mut refused: BTreeMap<String, usize> = BTreeMap::new();
72 let mut looked = 0usize;
73
74 for path in &all {
75 if looked >= cap {
76 break;
77 }
78 let bytes = match fs::read(path) {
79 Ok(b) => b,
80 Err(_) => continue,
81 };
82 let film = match mp4::Film::read(&bytes) {
83 Ok(f) => f,
84 Err(_) => continue,
85 };
86 if film.kind() != mp4::Kind::Hevc {
87 continue;
88 }
89 hevc_films += 1;
90 looked += 1;
91 if film.samples() == 0 {
92 *refused.entry("the track holds no sample".to_string()).or_insert(0) += 1;
93 continue;
94 }
95 let sample = match film.sample(&bytes, 0) {
96 Ok(s) => s,
97 Err(e) => {
98 *refused.entry(fmt!("{}", e.plain())).or_insert(0) += 1;
99 continue;
100 },
101 };
102 match hevc::picture(film.config(), sample) {
103 Ok(pic) => {
104 // A picture, and one the right size: a decoder that answered an
105 // empty frame would otherwise count as a success here.
106 let (w, h) = (pic.y.w, pic.y.h);
107 if w == 0 || h == 0 {
108 *refused.entry("an empty picture".to_string()).or_insert(0) += 1;
109 } else {
110 drawn += 1;
111 }
112 },
113 Err(e) => {
114 // The first line only: the tail names the file and would make
115 // every refusal look distinct.
116 let why = e.plain();
117 let head = why.split(". ").next().unwrap_or(&why).to_string();
118 *refused.entry(head).or_insert(0) += 1;
119 },
120 }
121 }
122
123 println!("{} HEVC films looked at, {} first frames drawn", hevc_films, drawn);
124 if hevc_films > 0 {
125 println!("that is {:.1}% of them", 100.0 * drawn as f64 / hevc_films as f64);
126 }
127 println!("refused:");
128 let mut sorted = refused.into_iter().collect::<Vec<_>>();
129 sorted.sort_by(|a, b| b.1.cmp(&a.1));
130 for (why, n) in sorted.iter().take(12) {
131 println!(" {:>5} {}", n, why);
132 }
133 Ok(())
134}