# zantetsu
Ultra-fast anime metadata extraction and canonical title matching for Rust.
[](https://crates.io/crates/zantetsu)
[](https://docs.rs/zantetsu)
[](LICENSE)
## What it does
`zantetsu` parses anime release filenames into structured metadata and
optionally resolves the parsed title to a canonical entry with cross-referenced
Kitsu / AniList / MAL identifiers.
```
[SubsPlease] Frieren - Beyond Journey's End - 01 (1080p) [A1B2C3D4].mkv
│ │ │ │
group title episode resolution
```
No machine learning. No network calls required for parsing. No runtime
downloads. Entirely self-contained.
## Install
```toml
[dependencies]
zantetsu = "0.2"
```
Requires Rust 1.85+.
## Quick start
### Parse a filename
```rust
use zantetsu::{EpisodeSpec, Zantetsu};
let engine = Zantetsu::new()?;
let result = engine.parse("[SubsPlease] Cowboy Bebop - 01 [1080p][HEVC].mkv")?;
assert_eq!(result.title.as_deref(), Some("Cowboy Bebop"));
assert_eq!(result.group.as_deref(), Some("SubsPlease"));
assert_eq!(result.episode, Some(EpisodeSpec::Single(1)));
println!("resolution: {:?}", result.resolution); // Some(P1080)
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Match to a canonical title (local Kitsu dump)
```rust,no_run
use zantetsu::{MatchSource, TitleMatcher};
let matcher = TitleMatcher::new(
MatchSource::kitsu_dump("/home/user/.local/share/zantetsu/kitsu-dumps"),
)?;
if let Some(hit) = matcher.match_title("Sousou no Frieren")? {
println!("canonical: {}", hit.canonical_title); // "Frieren: Beyond Journey's End"
println!("score : {:.2}", hit.score); // 0.97
println!("anilist : {:?}", hit.ids.anilist); // Some(154587)
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
### Match to a canonical title (remote GraphQL)
```rust,no_run
use zantetsu::{MatchSource, TitleMatcher};
let matcher = TitleMatcher::new(
MatchSource::remote_endpoint("https://graphql.anilist.co"),
)?;
if let Some(hit) = matcher.match_title("Spy x Family")? {
println!("{} ({})", hit.canonical_title, hit.ids.anilist.unwrap_or(0));
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
## Parsed fields
| `title` | `Option<String>` | `"Cowboy Bebop"` |
| `group` | `Option<String>` | `"SubsPlease"` |
| `episode` | `Option<EpisodeSpec>` | `Single(1)`, `Range(1,3)`, `Special(0.5)` |
| `resolution` | `Option<Resolution>` | `P1080`, `P720`, `P2160` |
| `video_codec` | `Option<VideoCodec>` | `H265`, `H264`, `AV1` |
| `audio_codec` | `Option<AudioCodec>` | `AAC`, `FLAC`, `AC3` |
| `source` | `Option<MediaSource>` | `BluRay`, `WebDL`, `HDTV` |
| `confidence` | `f32` | `0.92` |
## Quality scoring
Score a parse result against a configurable quality profile:
```rust
use zantetsu::{QualityProfile, Zantetsu};
let engine = Zantetsu::new()?;
let result = engine.parse("[EMBER] Violet Evergarden - 01 [BD 1080p HEVC FLAC].mkv")?;
let profile = QualityProfile::default();
let scores = engine.score(&result, &profile);
println!("video score: {}", scores.video);
println!("audio score: {}", scores.audio);
# Ok::<(), Box<dyn std::error::Error>>(())
```
## Crate family
`zantetsu` is a façade over two focused sub-crates. Depend on the façade for
most use cases; depend on the sub-crates directly if you need finer control.
| [`zantetsu`](https://crates.io/crates/zantetsu) | **This crate** — unified public API |
| [`zantetsu-core`](https://crates.io/crates/zantetsu-core) | Heuristic filename parser, quality scoring, types |
| [`zantetsu-vecdb`](https://crates.io/crates/zantetsu-vecdb) | Canonical title matching, `TitleMatcher` |
## Error handling
All fallible functions return `Result<T, ZantetsuError>`. Errors implement
`std::error::Error` via `thiserror`.
```rust
use zantetsu::{Zantetsu, ZantetsuError};
let engine = Zantetsu::new()?;
match engine.parse("") {
Err(ZantetsuError::EmptyInput) => eprintln!("filename is empty"),
Err(e) => eprintln!("error: {e}"),
Ok(res) => println!("{:?}", res.title),
}
# Ok::<(), Box<dyn std::error::Error>>(())
```
## License
MIT