revelo_export/lib.rs
1//! Output formatters for the [revelo](https://github.com/vbasky/revelo)
2//! media-metadata library.
3//!
4//! Each formatter takes a [`revelo_core::StreamCollection`] (the parsed
5//! representation of a media file) plus the file path string, and returns
6//! a `String` in the target format. The two primary formats — XML and JSON
7//! — target byte-for-byte compatibility with MediaInfoLib's output so that
8//! automated diffing against the reference implementation is possible.
9//!
10//! # Formatters
11//!
12//! | Function | Output |
13//! |---|---|
14//! | [`to_xml`] | MediaInfo-compatible XML (schema version 2.0) |
15//! | [`to_json`] | MediaInfo-compatible JSON |
16//! | [`to_text`] | Human-readable text report (MediaInfo default style) |
17//! | [`to_csv`] | CSV with per-kind sections; pipe-friendly |
18//! | [`to_summary`] | Compact aggregate statistics across all streams |
19//! | [`to_ebu_core`] | EBU MXF Core Metadata XML (ebuCore 2015) |
20//! | [`to_mpeg7`] | MPEG-7 `MediaInformation` XML |
21//! | [`to_pbcore`] | PBCore 2.x `pbcoreDescriptionDocument` XML |
22//! | [`to_niso`] | NISO Z39.87 (MIX) technical-metadata XML (pragmatic, not schema-validated) |
23//! | [`to_fims`] | FIMS 1.0 media XML (pragmatic, not schema-validated) |
24//! | [`to_graph`] | Graphviz DOT stream-field graph |
25//! | [`to_revtmd`] | RevTMD custom XML |
26//!
27//! # Quick example
28//!
29//! ```no_run
30//! use revelo_core::StreamCollection;
31//! use revelo_export::{to_xml, to_json, to_text, to_csv, to_summary};
32//!
33//! // Obtain a StreamCollection from revelo-core (parsing happens there).
34//! let streams: StreamCollection = todo!("parse your file here");
35//! let path = "/media/video.mp4";
36//!
37//! let xml = to_xml(&streams, path);
38//! let json = to_json(&streams, path);
39//! let text = to_text(&streams, path);
40//! let csv = to_csv(&streams, path);
41//! let summary = to_summary(&streams, path);
42//! ```
43//!
44//! # Format notes
45//!
46//! - **XML / JSON**: field ordering and Duration rendering (stored as integer
47//! milliseconds, emitted as decimal seconds with 3 fraction digits) match
48//! the MediaInfoLib oracle. The `<creatingLibrary>` / `creatingLibrary`
49//! header that upstream emits is intentionally omitted — it identifies the
50//! producing tool, not the file.
51//! - **Text**: uses MediaInfo's friendly field labels ("Bit rate", "Frame
52//! rate mode") and humanised values ("5.26 MiB", "1 min 0 s", "734 kb/s").
53//! - **CSV**: one section per stream kind; the first data row is field names,
54//! subsequent rows are stream positions; values containing commas or quotes
55//! are RFC 4180 escaped.
56//! - **Summary**: aggregates codec lists, resolution ranges, channel counts,
57//! and sample rates across all streams of each kind.
58
59#![allow(non_snake_case)]
60#![deny(unsafe_code)]
61
62pub mod csv;
63pub mod ebu_core;
64pub mod fims;
65pub mod graph;
66pub mod json;
67pub mod mpeg7;
68pub mod niso;
69pub mod pbcore;
70pub mod revtmd;
71pub mod summary;
72pub mod text;
73pub mod xml;
74
75pub use csv::to_csv;
76pub use ebu_core::to_ebu_core;
77pub use fims::to_fims;
78pub use graph::to_graph;
79pub use json::to_json;
80pub use mpeg7::to_mpeg7;
81pub use niso::to_niso;
82pub use pbcore::to_pbcore;
83pub use revtmd::to_revtmd;
84pub use summary::to_summary;
85pub use text::to_text;
86pub use xml::to_xml;
87
88/// Escapes XML text-content special characters (`&`, `<`, `>`) for the
89/// auxiliary exporters (ebuCore / MPEG-7 / PBCore / RevTMD). Without this a
90/// value such as `H.264 & AAC` or a path containing `<`/`&` produces malformed
91/// XML. The primary [`xml`]/[`json`] formatters carry their own byte-exact
92/// escaping and are intentionally not routed through here.
93pub(crate) fn xml_escape(s: &str) -> String {
94 let mut out = String::with_capacity(s.len());
95 for ch in s.chars() {
96 match ch {
97 '&' => out.push_str("&"),
98 '<' => out.push_str("<"),
99 '>' => out.push_str(">"),
100 _ => out.push(ch),
101 }
102 }
103 out
104}