Skip to main content

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}