Init
This commit is contained in:
commit
60d254952b
4 changed files with 1929 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
1759
Cargo.lock
generated
Normal file
1759
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "cbz2pdf"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.20", features = ["derive"] }
|
||||
image = "0.25.2"
|
||||
log = "0.4.22"
|
||||
pdf-writer = "0.12.0"
|
||||
pretty_env_logger = "0.5.0"
|
||||
walkdir = "2.5.0"
|
||||
zip = "2.2.0"
|
||||
156
src/main.rs
Normal file
156
src/main.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
use clap::{Parser, ValueHint};
|
||||
use log::info;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, Pdf, Rect, Ref};
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use walkdir::WalkDir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command()]
|
||||
struct Cli {
|
||||
#[arg(
|
||||
short = 'i',
|
||||
long = "input",
|
||||
value_hint = ValueHint::FilePath,
|
||||
help = "Path to CBZ file or directory containing CBZ files"
|
||||
)]
|
||||
input_path: String,
|
||||
|
||||
#[arg(
|
||||
short = 'o',
|
||||
long = "output-directory",
|
||||
default_value = ".",
|
||||
value_hint = ValueHint::FilePath,
|
||||
help = "Output directory for PDF files"
|
||||
)]
|
||||
output_dir: String,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let input_path = Path::new(&cli.input_path);
|
||||
|
||||
if input_path.is_file() && input_path.extension() == Some(OsStr::new("cbz")) {
|
||||
convert_cbz(input_path, Path::new(&cli.output_dir))?;
|
||||
} else if input_path.is_dir() {
|
||||
convert_directory(input_path, Path::new(&cli.output_dir))?;
|
||||
} else {
|
||||
eprintln!(
|
||||
"Invalid input path. Please provide a CBZ file or a directory containing CBZ files."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct ImageFile {
|
||||
pub name: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
fn convert_cbz(cbz_path: &Path, output_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Converting {:?}", cbz_path);
|
||||
|
||||
let a4 = Rect::new(0.0, 0.0, 595.0, 842.0);
|
||||
|
||||
let mut zip = ZipArchive::new(File::open(cbz_path)?)?;
|
||||
let mut images = Vec::new();
|
||||
for i in 0..zip.len() {
|
||||
let mut file = zip.by_index(i)?;
|
||||
let mut image_data = Vec::new();
|
||||
let name = file.enclosed_name().expect("Failed to read file name");
|
||||
if name.extension() == Some(OsStr::new("jpg")) {
|
||||
file.read_to_end(&mut image_data)?;
|
||||
images.push(ImageFile {
|
||||
name: name
|
||||
.file_name()
|
||||
.expect("Failed to read file name")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
data: image_data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
images.sort_by_key(|img| img.name.clone());
|
||||
|
||||
let mut pdf = Pdf::new();
|
||||
let catalog_id = Ref::new(1);
|
||||
let page_tree_id = Ref::new(2);
|
||||
pdf.catalog(catalog_id).pages(page_tree_id);
|
||||
|
||||
let mut pages = Vec::new();
|
||||
let image_count: i32 = images.len().try_into().unwrap();
|
||||
|
||||
for (pos, image) in images.iter().enumerate() {
|
||||
let npos: i32 = pos.try_into().unwrap();
|
||||
let page_id = Ref::new(npos + 10);
|
||||
let image_id = Ref::new(image_count + 10 + npos);
|
||||
let content_id = Ref::new(image_count * 3 + 10 + npos);
|
||||
pages.push(page_id);
|
||||
let mut page = pdf.page(page_id);
|
||||
let image_name = Name(b"Im1");
|
||||
let dynamic = image::load_from_memory(&image.data).unwrap();
|
||||
|
||||
page.media_box(a4);
|
||||
page.parent(page_tree_id);
|
||||
page.contents(content_id);
|
||||
page.resources().x_objects().pair(image_name, image_id);
|
||||
page.finish();
|
||||
|
||||
let mut pdf_image = pdf.image_xobject(image_id, &image.data);
|
||||
pdf_image.filter(Filter::DctDecode);
|
||||
pdf_image.width(dynamic.width() as i32);
|
||||
pdf_image.height(dynamic.height() as i32);
|
||||
pdf_image.color_space().device_rgb();
|
||||
pdf_image.bits_per_component(8);
|
||||
pdf_image.finish();
|
||||
|
||||
let mut content = Content::new();
|
||||
content.save_state();
|
||||
content.transform([a4.x2, 0.0, 0.0, a4.y2, 0.0, 0.0]);
|
||||
content.x_object(image_name);
|
||||
content.restore_state();
|
||||
pdf.stream(content_id, &content.finish());
|
||||
}
|
||||
|
||||
let page_count = pages.len();
|
||||
pdf.pages(page_tree_id)
|
||||
.kids(pages)
|
||||
.count(page_count.try_into().unwrap());
|
||||
|
||||
let mut output_path = output_dir.join(cbz_path.file_name().unwrap());
|
||||
output_path.set_extension("pdf");
|
||||
std::fs::write(output_path.clone(), pdf.finish())?;
|
||||
|
||||
info!(
|
||||
"Converted '{}' to '{}'",
|
||||
cbz_path.display(),
|
||||
output_path.display()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_directory(
|
||||
directory: &Path,
|
||||
output_dir: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("Walking {:?}", directory);
|
||||
for entry in WalkDir::new(directory) {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension() == Some(OsStr::new("cbz")) {
|
||||
convert_cbz(path, output_dir)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue