All formats

sevenz-rust2

LanguageRust
LicenseApache-2.0
Repositorygithub.com/hasenbanck/sevenz-rust
Documentationdocs.rs/sevenz-rust2
Depends onpure Rust (lzma-rust2 and optional codec crates behind features)
Read / writeyes / 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#

Shell
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.

Rust
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:

Rust
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#

Rust
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#

Rust
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:

Rust
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#