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