crates/veilvoice-meta/src/lib.rs
what this file is for · veilvoice-meta · 121 lines · the same file on GitHub
The file as it is in the tree, in the colours you chose. A line number is a link, and so is every box in this file’s diagram: it opens here with the function it names marked.
// SPDX-License-Identifier: GPL-3.0-or-later
//! # veilvoice-meta
//!
//! Strip or spoof the identifying metadata that rides along with media files.
//!
//! ## Why this exists
//!
//! De-identifying a voice accomplishes nothing if the file still says who
//! recorded it. A phone recording routinely carries the device model, the
//! recording software, a precise timestamp and, for images, GPS coordinates
//! accurate to a few metres. That is often a far easier way to identify someone
//! than analysing their voice, and it survives every DSP transform because it
//! is not in the audio at all.
//!
//! ## Strip versus spoof
//!
//! Removing every tag is not always the least conspicuous choice. A file with
//! *no* metadata whatsoever is itself a signal: it says the sender was trying to
//! hide something, and it stands out in a set of otherwise ordinary files.
//! [`Policy`] therefore offers two approaches:
//!
//! - [`Policy::Strip`] removes everything. Best when the file is expected to
//! be sanitised anyway, or when any false statement would be worse than an
//! obvious absence.
//! - [`Policy::Realistic`] replaces the tags with plausible, non-identifying
//! values so the file looks unremarkable rather than scrubbed.
//!
//! ## What this crate cannot do
//!
//! It removes *container* metadata. It cannot remove information encoded in the
//! media itself: a photograph still shows the room it was taken in, and audio
//! still carries its room acoustics and background noise. Nor does it touch
//! filesystem timestamps or the filename, both of which are outside the file,
//! callers that care must handle those separately.
//!
//! # In plain words
//!
//! This strips the hidden labels off a file.
//!
//! Photographs and recordings carry information you never typed: where the picture
//! was taken, which phone or microphone made it, what the file was called before,
//! sometimes a name. Removing the sound of a voice and leaving that behind would
//! be pointless, so this takes it out -- not by blanking the fields, but by
//! removing the parts of the file that hold them.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
mod audio;
mod image;
mod wav;
pub use audio::{clean_audio_file, clean_audio_tags};
pub use image::{clean_image_bytes, clean_image_file, ImageKind};
pub use wav::{clean_wav_bytes, is_wav};
/// Crate version string, surfaced in the About panel.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// How aggressively to rewrite metadata.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Policy {
/// Remove every tag, leaving nothing behind.
#[default]
Strip,
/// Replace tags with plausible, non-identifying values, so the file does
/// not stand out by being conspicuously empty.
Realistic,
}
/// What changed in a single file.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Report {
/// Names of the metadata blocks that were removed or rewritten.
pub removed: Vec<String>,
/// Whether the file was modified at all.
pub changed: bool,
}
impl Report {
fn note(&mut self, what: impl Into<String>) {
self.removed.push(what.into());
self.changed = true;
}
}
/// Everything that can go wrong in this crate.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
/// The file could not be read or written.
Io(std::io::Error),
/// The file is not a format this crate understands.
UnsupportedFormat,
/// The file is structurally malformed.
Malformed(String),
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(e) => write!(f, "input/output error: {e}"),
Self::UnsupportedFormat => f.write_str("unsupported media format"),
Self::Malformed(m) => write!(f, "malformed file: {m}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}