revelo_core/lib.rs
1//! Core parsing engine for [revelo](https://github.com/vbasky/revelo).
2//!
3//! Transliterates MediaInfoLib's `File__Analyze` infrastructure — the
4//! cursor-based byte reader every parser uses (`Get_B*`, `Get_L*`,
5//! `Peek_*`, `Skip_*`, bitstream mode) plus the element trace tree, typed
6//! stream collection, runtime config, and event dispatch — while also
7//! exposing ExifTool-style stream kinds (`Exif`, `Iptc`, `Xmp`, `Icc`,
8//! `C2pa`, `MakerNotes`) for camera-maker-note depth.
9//!
10//! All read methods return native Rust types (`u8`–`u128`, `f32`, `f64`)
11//! rather than out-parameters or C-style aliases. Truncated reads return
12//! `0` / empty slices and set a `truncated()` flag rather than panicking.
13//!
14//! # Key public types
15//!
16//! | Type | Role |
17//! |------|------|
18//! | [`FileAnalyze`] | Cursor over a `&[u8]` buffer; received by every parser |
19//! | [`MediaFile`] | Public type alias for `FileAnalyze` |
20//! | [`StreamCollection`] | All parsed fields, keyed by `(StreamKind, position)` |
21//! | [`Stream`] | A single stream's fields in insertion order plus an `<extra>` bucket |
22//! | [`StreamKind`] | Discriminant: `General`…`Menu` (MediaInfo-compatible 0–6) + `Exif`…`MakerNotes` (7–12) |
23//! | [`ElementTree`] / [`ElementNode`] | Stack-based trace tree (mirrors MediaInfoLib `--trace` output) |
24//! | [`Reader`] | Fluent `Option`-returning wrapper over `FileAnalyze` |
25//! | `MediaConfig` | Demux level, trace verbosity, parse speed, multi-file options |
26//!
27//! # Writing a parser
28//!
29//! ```no_run
30//! use revelo_core::{FileAnalyze, StreamKind};
31//!
32//! fn my_parser(fa: &mut FileAnalyze) -> bool {
33//! // Non-advancing magic check
34//! if !fa.peek_magic(b"RIFF") {
35//! return false;
36//! }
37//! let _chunk_size = fa.get_b4("ChunkSize");
38//! let _form_tag = fa.get_c4("FormTag");
39//!
40//! let pos = fa.stream_prepare(StreamKind::General);
41//! fa.set_field(StreamKind::General, pos, "Format", "MyFormat");
42//! true
43//! }
44//! ```
45//!
46//! Or via the higher-level [`Reader`] API (`None` signals truncation instead
47//! of falling back to 0):
48//!
49//! ```no_run
50//! use revelo_core::{FileAnalyze, Reader, StreamKind};
51//!
52//! fn my_parser(fa: &mut FileAnalyze) -> bool {
53//! let mut r = Reader::wrap(fa);
54//! let Some(_size) = r.be_u32("Size") else { return false; };
55//! let pos = r.stream_prepare(StreamKind::Audio);
56//! r.set_field(StreamKind::Audio, pos, "BitDepth", "24");
57//! true
58//! }
59//! ```
60//!
61//! # `#![deny(unsafe_code)]`
62//!
63//! The entire crate is enforced `unsafe`-free.
64
65#![allow(non_snake_case)]
66#![deny(unsafe_code)]
67
68pub mod element;
69pub mod file_analyze;
70pub mod file_level;
71pub mod prelude;
72pub mod reader;
73mod revelo_util_re_export;
74pub mod stream;
75
76pub use element::{ElementInfo, ElementNode, ElementTree};
77pub use file_analyze::FileAnalyze;
78/// Ergonomic alias for [`FileAnalyze`]. Both names refer to the same type.
79pub type MediaFile<'a> = FileAnalyze<'a>;
80pub use file_level::{FileLevelInfo, fill_file_level_fields};
81pub use reader::Reader;
82pub use stream::{Stream, StreamCollection, StreamKind};
83
84pub mod channel_grouping;
85pub mod channel_splitting;
86pub mod events;
87pub mod ibi;
88pub mod interlacement;
89pub mod mime;
90pub mod timecode;
91/// Container-level reference file tracker.
92pub mod reference {
93
94 pub struct ReferenceFile {
95 pub path: String,
96 pub format: &'static str,
97 pub stream_id: u64,
98 }
99 pub struct ReferenceTracker {
100 pub files: Vec<ReferenceFile>,
101 }
102 impl Default for ReferenceTracker {
103 fn default() -> Self {
104 Self::new()
105 }
106 }
107
108 impl ReferenceTracker {
109 pub fn new() -> Self {
110 Self { files: Vec::new() }
111 }
112 pub fn add(&mut self, path: &str, format: &'static str, stream_id: u64) {
113 self.files.push(ReferenceFile { path: path.to_string(), format, stream_id });
114 }
115 pub fn count(&self) -> usize {
116 self.files.len()
117 }
118 }
119 #[cfg(test)]
120 mod tests {
121 use super::*;
122 #[test]
123 fn test_ref() {
124 let mut t = ReferenceTracker::new();
125 t.add("extra.m2ts", "BDAV", 0x1011);
126 assert_eq!(t.count(), 1);
127 }
128 }
129}
130pub mod computed_fields;
131pub mod config;
132pub mod data_helpers;
133pub mod multi_file;
134
135/// Round-to-nearest duration in milliseconds from sample count and sample rate.
136///
137/// Uses `(samples * 1000 + rate / 2) / rate` to match mediainfo's rounding
138/// rather than truncation via `(samples * 1000) / rate`.
139#[inline]
140pub fn duration_ms(samples: u64, sample_rate: u64) -> u64 {
141 if sample_rate == 0 {
142 return 0;
143 }
144 (samples * 1000 + sample_rate / 2) / sample_rate
145}