sevenz-rust2
| Language | Rust |
|---|---|
| License | Apache-2.0 |
| Repository | github.com/hasenbanck/sevenz-rust |
| Documentation | docs.rs/sevenz-rust2 |
| Depends on | pure Rust (lzma-rust2 and optional codec crates behind features) |
| Read / write | yes / yes (LZMA, LZMA2, BZip2, Deflate, Zstd, Brotli, LZ4, PPMd, Copy, BCJ filters; AES) |
sevenz-rust2 is a maintained fork of sevenz-rust, a pure Rust 7z implementation. ArchiveReader::open parses the archive header; archive().files lists entries; read_file(name) decompresses one file; for_each_entries walks all entries in archive order with a reader for each. compress_to_path creates an archive from a folder in one call, and ArchiveWriter gives full control.
In a solid archive, read_file decodes the block from its start every time; use for_each_entries to read many pages in one pass.
Installation#
cargo add sevenz-rust2
Basic usage: list pages in natural order#
is_page and natural_cmp are the helpers from the zip crate CBZ page: an image-extension filter that skips __MACOSX and dotfiles, and a natural, case-insensitive compare.
use sevenz_rust2::{ArchiveReader, Password};
fn list_pages(reader: &ArchiveReader<std::fs::File>) -> Vec<String> {
let mut pages: Vec<String> = reader
.archive()
.files
.iter()
.filter(|f| !f.is_directory() && is_page(f.name()))
.map(|f| f.name().to_string())
.collect();
pages.sort_by(|a, b| natural_cmp(a, b));
pages
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).ok_or("usage: pages <file.cb7>")?;
let reader = ArchiveReader::open(&path, Password::empty())?;
for (i, name) in list_pages(&reader).iter().enumerate() {
println!("{:3} {}", i + 1, name);
}
Ok(())
}
How to read all pages in one pass#
for_each_entries calls the closure for every entry in archive order; return Ok(true) to continue:
use std::collections::HashMap;
use std::io::Read;
fn read_pages(path: &str) -> Result<Vec<(String, Vec<u8>)>, Box<dyn std::error::Error>> {
let mut reader = ArchiveReader::open(path, Password::empty())?;
let mut data = HashMap::new();
reader.for_each_entries(|entry, r| {
if !entry.is_directory() && is_page(entry.name()) {
let mut buf = Vec::with_capacity(entry.size() as usize);
r.read_to_end(&mut buf)?;
data.insert(entry.name().to_string(), buf);
}
Ok(true)
})?;
let mut pages: Vec<(String, Vec<u8>)> = data.into_iter().collect();
pages.sort_by(|a, b| natural_cmp(&a.0, &b.0));
Ok(pages)
}
How to extract the cover#
fn extract_cover(path: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut reader = ArchiveReader::open(path, Password::empty())?;
let first = list_pages(&reader).into_iter().next().ok_or("no pages")?;
let data = reader.read_file(&first)?;
let ext = first.rsplit_once('.').map(|(_, e)| e.to_lowercase()).unwrap_or_default();
std::fs::write(format!("cover.{ext}"), data)?;
Ok(())
}
How to read ComicInfo.xml#
fn print_comic_info(path: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut reader = ArchiveReader::open(path, Password::empty())?;
let name = reader.archive().files.iter()
.map(|f| f.name().to_string())
.find(|n| n.eq_ignore_ascii_case("ComicInfo.xml"));
let Some(name) = name else { return Ok(()) };
let xml = String::from_utf8(reader.read_file(&name)?)?;
let doc = roxmltree::Document::parse(&xml)?;
let root = doc.root_element();
let get = |tag: &str| root.children().find(|n| n.has_tag_name(tag)).and_then(|n| n.text()).unwrap_or("");
println!("{} #{}: {}", get("Series"), get("Number"), get("Title"));
for p in root.descendants().filter(|n| n.has_tag_name("Page")) {
if p.attribute("Type").unwrap_or("").contains("FrontCover") {
println!("cover is page index {}", p.attribute("Image").unwrap_or("0"));
}
}
Ok(())
}
How to create a CB7 from a folder of images#
compress_to_path packs a whole folder with the default LZMA2 settings:
fn make_cb7(dir: &str, out: &str) -> Result<(), Box<dyn std::error::Error>> {
sevenz_rust2::compress_to_path(dir, out)?;
Ok(())
}
The folder’s files keep their names, so zero-pad page numbers before packing. JPEG and PNG pages gain nothing from LZMA2; for a faster archive, write entries with the Copy method through ArchiveWriter and set_content_methods (see the crate documentation).
Tips#
- Encrypted archives:
ArchiveReader::open(path, Password::from("secret")). reader.set_thread_count(n)lets LZMA2 blocks decode in parallel.compress_to_pathanddecompress_fileneed thecompressandutilfeatures, which are on by default.