Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 182 additions & 14 deletions Cargo.lock

Large diffs are not rendered by default.

90 changes: 41 additions & 49 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ resolver = "3"
members = [
"./crates/nvisy-cli",
"./crates/nvisy-codec",
"./crates/nvisy-context",
"./crates/nvisy-core",
"./crates/nvisy-engine",
"./crates/nvisy-fake",
Expand Down Expand Up @@ -37,44 +38,22 @@ documentation = "https://docs.rs/nvisy-runtime"

# Internal crates
nvisy-codec = { path = "./crates/nvisy-codec", version = "0.1.0", default-features = false }
nvisy-context = { path = "./crates/nvisy-context", version = "0.1.0" }
nvisy-core = { path = "./crates/nvisy-core", version = "0.1.0" }
nvisy-engine = { path = "./crates/nvisy-engine", version = "0.1.0" }
nvisy-fake = { path = "./crates/nvisy-fake", version = "0.1.0" }
nvisy-llm = { path = "./crates/nvisy-llm", version = "0.1.0" }
nvisy-stt = { path = "./crates/nvisy-stt", version = "0.1.0" }
nvisy-toolkit = { path = "./crates/nvisy-toolkit", version = "0.1.0" }
nvisy-ner = { path = "./crates/nvisy-ner", version = "0.1.0" }
nvisy-ocr = { path = "./crates/nvisy-ocr", version = "0.1.0" }
nvisy-pattern = { path = "./crates/nvisy-pattern", version = "0.1.0" }
nvisy-server = { path = "./crates/nvisy-server", version = "0.1.0" }
nvisy-stt = { path = "./crates/nvisy-stt", version = "0.1.0" }
nvisy-toolkit = { path = "./crates/nvisy-toolkit", version = "0.1.0" }

# Inference & AI frameworks
bentoml = { version = "0.5", default-features = false, features = ["rustls-tls", "tracing"] }
rig = { version = "0.38", features = [], default-features = false }

# HTTP client and middleware
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls", "multipart"] }
reqwest-middleware = { version = "0.5", features = ["json", "multipart"] }
reqwest-retry = { version = "0.9", features = [] }
reqwest-tracing = { version = "0.7", features = [] }

# Async runtime and parallelism
tokio = { version = "1.50", features = [] }
tokio-util = { version = "0.7", features = [] }
futures = { version = "0.3", features = [] }
async-trait = { version = "0.1", features = [] }
rayon = { version = "1.10", features = [] }

# Observability
tracing = { version = "0.1", features = ["attributes"] }
tracing-subscriber = { version = "0.3", features = [] }

# (De)serialization
# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = [] }
serde_with = { version = "3.18", features = [] }
schemars = { version = "1.0", features = ["uuid1", "bytes1"] }
csv = { version = "1.0", features = [] }
toml = { version = "1.1", features = [] }
minijinja = { version = "2.5", features = [] }

Expand All @@ -92,38 +71,48 @@ hipstr = { version = "0.8", features = ["serde"] }
jiff = { version = "0.2", features = ["serde"] }
semver = { version = "1.0", features = ["serde"] }
oxilangtag = { version = "0.1", features = ["serde"] }
celes = { version = "2.8", features = [] }
humantime = { version = "2.1", features = [] }
humantime-serde = { version = "1.1", features = [] }
type-map = { version = "0.5", features = [] }

# Encoding and hashing
base64 = { version = "0.22", features = [] }
hex = { version = "0.4", features = [] }
sha2 = { version = "0.11", features = [] }
aes-gcm = { version = "0.10", features = [] }
hmac = { version = "0.13", features = [] }
hex = { version = "0.4", features = [] }

# Pattern matching
# Async runtime and parallelism
tokio = { version = "1.50", features = [] }
tokio-util = { version = "0.7", features = [] }
futures = { version = "0.3", features = [] }
async-trait = { version = "0.1", features = [] }
rayon = { version = "1.10", features = [] }

# Observability
tracing = { version = "0.1", features = ["attributes"] }
tracing-subscriber = { version = "0.3", features = [] }

# Text processing (pattern matching, language detection, unicode)
regex = { version = "1.0", features = [] }
aho-corasick = { version = "1.0", features = [] }
smallvec = { version = "1.13", features = [] }

# Language detection and text segmentation
lingua = { version = "1.8", default-features = false, features = ["english"] }
stop-words = { version = "0.10", features = ["iso"] }
unicode-segmentation = { version = "1.13", features = [] }
unicode-normalization = { version = "0.1", features = [] }

# PDF processing (parsing, text extraction, page-to-image rendering)
lopdf = { version = "0.41", features = [] }
pdfium-render = { version = "0.9", features = [] }
# Checksum / encoding
bs58 = { version = "0.5", features = ["check"] }
phonenumber = { version = "0.3", default-features = false }

# Document parsing
# Tabular document parsing
csv = { version = "1.0", features = [] }
calamine = { version = "0.35", features = [] }

# Rich-document parsing (HTML, PDF)
scraper = { version = "0.27", features = [] }
ego-tree = { version = "0.11", features = [] }
calamine = { version = "0.35", features = [] }
zip = { version = "8.4", features = [] }
quick-xml = { version = "0.40", features = [] }
lopdf = { version = "0.41", features = [] }
pdfium-render = { version = "0.9", features = [] }

# Image processing
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "tiff"] }
Expand All @@ -134,27 +123,30 @@ hound = { version = "3.5", features = [] }
symphonia = { version = "0.6", default-features = false, features = ["wav", "pcm", "mp3"] }
mp3lame-encoder = { version = "0.2", features = [] }

# CLI
clap = { version = "4.6", features = [] }
# AI / LLM frameworks
bentoml = { version = "0.5", default-features = false, features = ["rustls-tls", "tracing"] }
rig = { version = "0.38", features = [], default-features = false }

# HTTP client and middleware
reqwest-middleware = { version = "0.5", features = ["json", "multipart"] }
reqwest-retry = { version = "0.9", features = [] }
reqwest-tracing = { version = "0.7", features = [] }

# HTTP server
# HTTP server and middleware
axum = { version = "0.8", features = [] }
aide = { version = "0.16.0-alpha.2", features = [] }
tower = { version = "0.5", features = [] }
tower-http = { version = "0.6", features = [] }

# Filesystem traversal
walkdir = { version = "2.5", features = [] }
# CLI
clap = { version = "4.6", features = [] }

# Storage, file detection, and asset embedding
# Storage and file-type detection
fjall = { version = "3.1", features = [] }
include_dir = { version = "0.7", features = [] }
infer = { version = "0.19", features = [] }

# Utilities
validator = { version = "0.20", features = ["derive"] }
rand = { version = "0.10", features = [] }
tempfile = { version = "3.27", features = [] }

# Fake data generation
fake = { version = "5.1", features = [] }
16 changes: 9 additions & 7 deletions crates/nvisy-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,16 @@ path = "src/main.rs"
nvisy-engine = { workspace = true, features = [] }
nvisy-server = { workspace = true, features = [] }

# CLI
clap = { workspace = true, features = ["derive", "env"] }

# (De)serialization
# Serialization
serde = { workspace = true, features = [] }
toml = { workspace = true, features = [] }
humantime = { workspace = true, features = [] }
humantime-serde = { workspace = true, features = [] }

# Derive macros and error handling
anyhow = { workspace = true, features = [] }

# HTTP server
axum = { workspace = true, features = ["tokio"] }
# Primitive datatypes
humantime = { workspace = true, features = [] }

# Async runtime and parallelism
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] }
Expand All @@ -80,5 +76,11 @@ tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] }
tracing = { workspace = true, features = [] }
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }

# HTTP server and middleware
axum = { workspace = true, features = ["tokio"] }

# CLI
clap = { workspace = true, features = ["derive", "env"] }

[package.metadata.cargo-machete]
ignored = ["humantime-serde"]
52 changes: 27 additions & 25 deletions crates/nvisy-codec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,27 +100,39 @@ rustdoc-args = ["--cfg", "docsrs"]
# Internal crates
nvisy-core = { workspace = true, features = [] }

# Async runtime
async-trait = { workspace = true, features = [] }
tokio = { workspace = true, features = ["sync"] }

# (De)serialization
# Serialization
serde = { workspace = true, features = [] }
serde_json = { workspace = true, features = [] }
schemars = { workspace = true, features = [] }

# Derive macros and error handling
derive_more = { workspace = true, features = ["as_ref", "deref", "from"] }

# Primitive datatypes
bytes = { workspace = true, features = [] }
uuid = { workspace = true, features = [] }
derive_more = { workspace = true, features = ["as_ref", "deref", "from"] }

# Encoding and hashing
hex = { workspace = true, features = [] }
infer = { workspace = true, features = [] }
schemars = { workspace = true, features = [] }
sha2 = { workspace = true, features = [] }

# Image processing — pulled in unconditionally because the image
# handler structs reference `image::DynamicImage` directly. The
# workspace dep already enables png/jpeg/tiff decoders. `imageproc`
# powers the per-region gaussian blur in `image::redact`.
# Async runtime and parallelism
async-trait = { workspace = true, features = [] }
tokio = { workspace = true, features = ["sync"] }
rayon = { workspace = true, optional = true, features = [] }

# Observability
tracing = { workspace = true, features = [] }

# Tabular document parsing (feature-gated)
csv = { workspace = true, optional = true, features = [] }

# Rich-document parsing (feature-gated: HTML + PDF)
scraper = { workspace = true, optional = true, features = [] }
ego-tree = { workspace = true, optional = true, features = [] }
lopdf = { workspace = true, optional = true, features = [] }
pdfium-render = { workspace = true, optional = true, features = [] }

# Image processing
image = { workspace = true, features = [] }
imageproc = { workspace = true, features = [] }

Expand All @@ -129,18 +141,8 @@ hound = { workspace = true, optional = true, features = [] }
symphonia = { workspace = true, optional = true, features = [] }
mp3lame-encoder = { workspace = true, optional = true, features = [] }

# PDF processing (feature-gated)
lopdf = { workspace = true, optional = true, features = [] }
pdfium-render = { workspace = true, optional = true, features = [] }
rayon = { workspace = true, optional = true, features = [] }

# Document parsing (feature-gated)
csv = { workspace = true, optional = true, features = [] }
scraper = { workspace = true, optional = true, features = [] }
ego-tree = { workspace = true, optional = true, features = [] }

# Observability
tracing = { workspace = true, features = [] }
# Storage and file-type detection
infer = { workspace = true, features = [] }

[dev-dependencies]
tokio = { workspace = true, features = ["macros", "rt"] }
10 changes: 9 additions & 1 deletion crates/nvisy-codec/src/core/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,21 @@ use crate::content::{ContentData, ContentSource};
///
/// `data` is the per-modality wire payload; `location` is the
/// coordinate the handler will accept in [`Handler::read`] /
/// [`Handler::redact`] to address the same chunk again.
/// [`Handler::redact`] to address the same chunk again. `hints`
/// carries out-of-band context strings the chunk's structural
/// neighbours surface — CSV/XLSX column headers, JSON object
/// keys, HTML parent-element text — for downstream context-aware
/// recognizers; handlers without such metadata leave it empty.
#[derive(Debug, Clone, PartialEq)]
pub struct Chunk<M: Modality> {
/// Coordinate addressing this chunk inside the handler.
pub location: M::Location,
/// Wire payload at the chunk's location.
pub data: M::Data,
/// Out-of-band context strings recognizers should treat as
/// in-context (column headers, parent element text, …).
/// Empty when the handler has no such metadata to surface.
pub hints: Vec<String>,
}

/// Per-modality capability trait every format handler implements.
Expand Down
5 changes: 3 additions & 2 deletions crates/nvisy-codec/src/core/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//! [`Format::loader`]: super::Format::loader
//! [`FormatId`]: super::FormatId

use std::marker::PhantomData;
use std::sync::Arc;

use nvisy_core::Error;
Expand Down Expand Up @@ -100,7 +101,7 @@ where
{
Arc::new(LoaderAdapter {
loader,
_phantom: std::marker::PhantomData,
_phantom: PhantomData,
})
}

Expand All @@ -109,7 +110,7 @@ where
/// [`erase`]; not part of the public API.
struct LoaderAdapter<M: Modality, L: Loader<M>> {
loader: L,
_phantom: std::marker::PhantomData<fn() -> M>,
_phantom: PhantomData<fn() -> M>,
}

#[async_trait::async_trait]
Expand Down
9 changes: 6 additions & 3 deletions crates/nvisy-codec/src/document/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ mod tabular;
#[cfg(feature = "internal_text")]
mod text;

use std::any::type_name;
use std::fmt;

use derive_more::From;
#[cfg(feature = "internal_audio")]
use nvisy_core::modality::Audio;
Expand Down Expand Up @@ -226,11 +229,11 @@ impl<M: Modality> DocumentHandle<M> {
}
}

impl<M: Modality> std::fmt::Debug for DocumentHandle<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<M: Modality> fmt::Debug for DocumentHandle<M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DocumentHandle")
.field("format_id", &self.format_id)
.field("modality", &std::any::type_name::<M>())
.field("modality", &type_name::<M>())
.finish()
}
}
17 changes: 7 additions & 10 deletions crates/nvisy-codec/src/handler/audio/mp3_codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
//! redaction of MP3 streams. Callers wanting bit-perfect preservation
//! of unredacted regions should not round-trip.

use std::io::Cursor;
use std::io::{Cursor, ErrorKind as IoErrorKind};

use bytes::Bytes;
use mp3lame_encoder::{Builder, FlushNoGap, InterleavedPcm, MonoPcm};
use nvisy_core::Error;
use symphonia::core::audio::{Audio as _, GenericAudioBufferRef};
use symphonia::core::audio::conv::{ConvertibleSample, FromSample};
use symphonia::core::audio::{Audio as _, AudioBuffer, GenericAudioBufferRef};
use symphonia::core::codecs::audio::AudioDecoderOptions;
use symphonia::core::errors::Error as SymError;
use symphonia::core::formats::probe::Hint;
Expand Down Expand Up @@ -139,9 +140,7 @@ pub(super) fn decode_to_pcm(bytes: &Bytes) -> Result<DecodedMp3, Error> {
let packet = match reader.next_packet() {
Ok(Some(p)) => p,
Ok(None) => break,
Err(SymError::IoError(io_err))
if io_err.kind() == std::io::ErrorKind::UnexpectedEof =>
{
Err(SymError::IoError(io_err)) if io_err.kind() == IoErrorKind::UnexpectedEof => {
break;
}
Err(e) => {
Expand Down Expand Up @@ -198,22 +197,20 @@ fn append_interleaved_f32(
channels: usize,
out: &mut Vec<f32>,
) {
use symphonia::core::audio::conv::ConvertibleSample;

fn extend<S: ConvertibleSample + Copy>(
buf: &symphonia::core::audio::AudioBuffer<S>,
buf: &AudioBuffer<S>,
channels: usize,
out: &mut Vec<f32>,
) where
f32: symphonia::core::audio::conv::FromSample<S>,
f32: FromSample<S>,
{
let frames = buf.frames();
out.reserve(frames * channels);
for frame in 0..frames {
for ch in 0..channels {
let plane = buf.plane(ch).expect("plane for known channel index");
let sample = plane[frame];
out.push(<f32 as symphonia::core::audio::conv::FromSample<S>>::from_sample(sample));
out.push(<f32 as FromSample<S>>::from_sample(sample));
}
}
}
Expand Down
Loading