decrypt::decrypt_sectors now restores chunks when decrypt_unit_full's TS-sync verification fails, preventing 0.18.1's silent corruption of MPLS/CLPI navigation files when DecryptingSectorSource decorates the sweep reader. Fixes E6009 NoStreams on info iso:// for AACS-encrypted UHDs ripped without --raw. Disc::sweep progress takes max(snapshot.bytes_good, bytes_done) so the user-visible counter never regresses below what the producer has already sent.
189 lines
7.5 KiB
Markdown
189 lines
7.5 KiB
Markdown
[](https://crates.io/crates/libfreemkv)
|
|
[](https://docs.rs/libfreemkv)
|
|
[](LICENSE)
|
|
|
|
# libfreemkv
|
|
|
|
Rust library for 4K UHD / Blu-ray / DVD optical drives. Drive access, disc scanning, stream labels, AACS decryption, CSS decryption, KEYDB updates, and content reading in one crate. Bundled drive profiles — no external files needed.
|
|
|
|
**12+ MB/s** sustained read speeds on BD. Full init: unlock, firmware upload, speed calibration — all from pure Rust.
|
|
|
|
Multi-lingual by design — the library outputs structured data and numeric error codes, never English text. Build any UI or localization on top.
|
|
|
|
**[API Documentation](https://docs.rs/libfreemkv)** · **[Technical Docs](docs/)**
|
|
|
|
Part of the [freemkv](https://github.com/freemkv) project.
|
|
|
|
## Install
|
|
|
|
```toml
|
|
[dependencies]
|
|
libfreemkv = "0.18"
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```rust
|
|
use libfreemkv::{Drive, Disc, ScanOptions};
|
|
use std::path::Path;
|
|
|
|
// Open drive — profiles are bundled, auto-identified
|
|
let mut drive = Drive::open(Path::new("/dev/sg4"))?;
|
|
drive.wait_ready()?; // wait for disc
|
|
drive.init()?; // unlock + firmware upload
|
|
drive.probe_disc()?; // probe disc surface for optimal speeds
|
|
|
|
// Scan disc — UDF, playlists, streams, AACS (all automatic)
|
|
let disc = Disc::scan(&mut drive, &ScanOptions::default())?;
|
|
|
|
for title in &disc.titles {
|
|
println!("{} — {} streams", title.duration_display(), title.streams.len());
|
|
}
|
|
|
|
// Stream pipeline — read PES frames from any source, write to any output
|
|
let opts = libfreemkv::InputOptions::default();
|
|
let mut input = libfreemkv::input("iso://Disc.iso", &opts)?;
|
|
let title = input.info().clone();
|
|
let mut output = libfreemkv::output("mkv://Movie.mkv", &title)?;
|
|
while let Ok(Some(frame)) = input.read() {
|
|
output.write(&frame)?;
|
|
}
|
|
output.finish()?;
|
|
```
|
|
|
|
### Multi-pass recovery rip
|
|
|
|
For damaged discs the library exposes two flat verbs — `Disc::sweep` for the
|
|
forward Pass 1 and `Disc::patch` for retrying bad ranges. The library never
|
|
loops; the multipass policy is the caller's job. See
|
|
[`docs/rip-recovery.md`](docs/rip-recovery.md) and the design notes in
|
|
`(internal)/memory/0_18_redesign.md`.
|
|
|
|
```rust
|
|
use libfreemkv::{SweepOptions, PatchOptions};
|
|
use libfreemkv::disc::{mapfile, mapfile_path_for};
|
|
use std::path::Path;
|
|
|
|
let iso = Path::new("disc.iso");
|
|
|
|
// Pass 1: disc → ISO. Skip-on-error, zero-fill, write the sidecar mapfile.
|
|
disc.sweep(&mut drive, iso, &SweepOptions {
|
|
decrypt: true,
|
|
resume: false,
|
|
batch_sectors: None,
|
|
skip_on_error: true,
|
|
progress: None,
|
|
halt: None,
|
|
})?;
|
|
|
|
// Pass 2..N: retry every non-finished range. Idempotent.
|
|
loop {
|
|
let map = mapfile::Mapfile::load(&mapfile_path_for(iso))?;
|
|
let stats = map.stats();
|
|
if stats.bytes_pending + stats.bytes_unreadable == 0 { break; }
|
|
|
|
let outcome = disc.patch(&mut drive, iso, &PatchOptions {
|
|
decrypt: true,
|
|
block_sectors: None,
|
|
full_recovery: true,
|
|
reverse: true,
|
|
wedged_threshold: 50,
|
|
progress: None,
|
|
halt: None,
|
|
})?;
|
|
if outcome.bytes_recovered_this_pass == 0 { break; }
|
|
}
|
|
|
|
// Mux from the ISO via the normal stream pipeline (no drive involvement).
|
|
```
|
|
|
|
## What It Does
|
|
|
|
- **Drive access** — open, identify, unlock, firmware upload, speed calibration, eject
|
|
- **12+ MB/s reads** — auto-detects kernel transfer limits, sustained full speed
|
|
- **Disc scanning** — UDF 2.50 filesystem, MPLS playlists, CLPI clip info
|
|
- **Stream labels** — 5 BD-J format parsers (Paramount, Criterion, Pixelogic, CTRM, Deluxe)
|
|
- **AACS decryption** — transparent key resolution and content decrypt (1.0 + 2.0 bus decryption)
|
|
- **KEYDB updates** — download, verify, save from any HTTP URL (zero deps, raw TCP)
|
|
- **Content reading** — adaptive batch reads with automatic decryption
|
|
- **Stream I/O** — unified stream pipeline for reading and writing any format
|
|
|
|
### Streams
|
|
|
|
| Stream | Input | Output | Transport |
|
|
|--------|-------|--------|-----------|
|
|
| DiscStream | Yes | -- | Optical drive via SCSI |
|
|
| IsoStream | Yes | -- | Blu-ray ISO image file (read via stream pipeline; written via `Disc::sweep()`) |
|
|
| MkvStream | Yes | Yes | Matroska container |
|
|
| M2tsStream | Yes | Yes | BD transport stream with FMKV metadata header |
|
|
| NetworkStream | Yes (listen) | Yes (connect) | TCP with FMKV metadata header |
|
|
| StdioStream | Yes (stdin) | Yes (stdout) | Raw byte pipe |
|
|
| NullStream | -- | Yes | Discard sink (byte counter for benchmarks) |
|
|
|
|
Streams implement `FrameSource` (read) and/or `FrameSink` (write); direction is type-checked. `input()` / `output()` resolve URL strings to PES stream instances. All URLs use the `scheme://path` format — bare paths are rejected.
|
|
|
|
AACS decryption requires a KEYDB.cfg file. If available at `~/.config/aacs/KEYDB.cfg` or passed via `ScanOptions`, the library handles everything — handshake, key derivation, and per-sector decryption — without the application needing to know anything about encryption.
|
|
|
|
## Architecture
|
|
|
|
```text
|
|
Drive — open, identify, init, unlock, single-shot read
|
|
├── ScsiTransport — SG_IO (Linux), IOKit (macOS), SPTI (Windows)
|
|
├── DriveProfile — per-drive unlock parameters (bundled)
|
|
└── PlatformDriver — MediaTek (supported), Renesas (planned)
|
|
|
|
Disc — scan titles, streams, AACS/CSS state
|
|
├── UDF reader — Blu-ray UDF 2.50 with metadata partitions
|
|
├── MPLS parser — playlists → titles + clips + streams
|
|
├── CLPI parser — clip info → EP map → sector extents
|
|
├── IFO parser — DVD title sets, PGC chains, cell addresses
|
|
├── Labels — 5 BD-J format parsers (detect + parse)
|
|
├── AACS — key resolution + content decryption
|
|
├── CSS — DVD CSS cipher (table-driven, no keys needed)
|
|
└── KEYDB — download + verify + save
|
|
|
|
Streams — unified PES pipeline
|
|
├── FrameSource — read() PES frames (direction-typed)
|
|
├── FrameSink — write() PES frames (direction-typed)
|
|
├── DiscStream — sectors → decrypt → TS demux → PES
|
|
├── IsoStream — ISO file → decrypt → TS demux → PES
|
|
├── MkvStream — MKV mux/demux
|
|
├── M2tsStream — BD transport stream
|
|
├── NetworkStream — TCP with FMKV metadata header
|
|
├── StdioStream — stdin/stdout pipe
|
|
└── NullStream — discard sink
|
|
```
|
|
|
|
See [docs/](docs/) for detailed technical documentation on each module.
|
|
|
|
## Error Codes
|
|
|
|
All errors are structured with numeric codes. No user-facing English text — applications format their own messages.
|
|
|
|
| Range | Category |
|
|
|-------|----------|
|
|
| E1xxx | Device errors (not found, permission) |
|
|
| E2xxx | Profile errors (unsupported drive) |
|
|
| E3xxx | Unlock errors (failed, signature) |
|
|
| E4xxx | SCSI errors (command failed, timeout) |
|
|
| E5xxx | I/O errors |
|
|
| E6xxx | Disc format errors |
|
|
| E7xxx | AACS errors |
|
|
| E8xxx | KEYDB update errors |
|
|
|
|
## Platform Support
|
|
|
|
| Platform | Status | Backend |
|
|
|----------|--------|---------|
|
|
| Linux | Supported | SG_IO ioctl |
|
|
| macOS | Supported | IOKit SCSITask |
|
|
| Windows | Supported | SPTI |
|
|
|
|
## Contributing
|
|
|
|
Run `freemkv info disc:// --share` with the [freemkv CLI](https://github.com/freemkv/freemkv) to contribute your drive's profile.
|
|
|
|
## License
|
|
|
|
AGPL-3.0-only
|