What Are CBZ and CBR?
CBZ and CBR are container formats for digital comic books. The name says it all:
| Format | Real Container | Extension |
|---|---|---|
| CBZ | ZIP | .cbz |
| CBR | RAR | .cbr |
| CB7 | 7-Zip | .cb7 |
| CBT | TAR | .cbt |
| CBA | ACE | .cba (obsolete) |
Internally they are compressed archives containing images (JPG, PNG, WebP) sorted by filename. Comic readers (CDisplayEx, YACReader, Komga, Kavita) extract the images in order and display them as pages.
Why CBZ dominates today:
- ZIP is an open, patent-free standard
- Python has native support via the built-in
zipfilemodule - RAR requires third-party libraries (unrar) with potential licensing restrictions
- CB7 with 7-Zip offers better compression but lower universal compatibility
Reading and Extracting CBZ with Python
import zipfile
import os
from pathlib import Path
def list_cbz_pages(cbz_path):
"""List all images in a CBZ in the correct reading order."""
image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif', '.bmp'}
with zipfile.ZipFile(cbz_path, 'r') as cbz:
files = sorted([
f for f in cbz.namelist()
if Path(f).suffix.lower() in image_extensions
and not f.startswith('__MACOSX') # Skip macOS metadata
])
return files
def extract_cbz(cbz_path, output_dir):
"""Extract all pages from a CBZ to a directory."""
Path(output_dir).mkdir(parents=True, exist_ok=True)
pages = list_cbz_pages(cbz_path)
with zipfile.ZipFile(cbz_path, 'r') as cbz:
for i, page in enumerate(pages, 1):
ext = Path(page).suffix.lower()
dest = Path(output_dir) / f"page_{i:04d}{ext}"
dest.write_bytes(cbz.read(page))
print(f"Extracted {len(pages)} pages to '{output_dir}'")
return pages
def cbz_info(cbz_path):
"""Display detailed information about a CBZ file."""
pages = list_cbz_pages(cbz_path)
size = os.path.getsize(cbz_path) / 1024 / 1024
print(f"File: {cbz_path}")
print(f"Pages: {len(pages)}")
print(f"Size: {size:.2f} MB")
print(f"First: {pages[0] if pages else 'N/A'}")
print(f"Last: {pages[-1] if pages else 'N/A'}")
return {'pages': len(pages), 'size_mb': size}
# Example
cbz_info('amazing_spiderman_001.cbz')
extract_cbz('batman_001.cbz', 'batman_pages')
Creating CBZ from Images
import zipfile
from pathlib import Path
def create_cbz(images_folder, output_name):
"""
Create a CBZ from a folder of images.
Files are sorted numerically for correct page order.
"""
folder = Path(images_folder)
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif'}
images = sorted([
f for f in folder.iterdir()
if f.suffix.lower() in extensions
], key=lambda p: p.stem) # Alphanumeric sort by filename
if not images:
raise ValueError(f"No images found in {folder}")
cbz_path = Path(output_name)
if cbz_path.suffix.lower() != '.cbz':
cbz_path = cbz_path.with_suffix('.cbz')
with zipfile.ZipFile(cbz_path, 'w', zipfile.ZIP_STORED) as cbz:
# ZIP_STORED: no additional compression (JPGs are already compressed)
for i, image in enumerate(images, 1):
internal_name = f"{i:04d}{image.suffix.lower()}"
cbz.write(image, internal_name)
size_mb = cbz_path.stat().st_size / 1024 / 1024
print(f"CBZ created: {cbz_path} ({len(images)} pages, {size_mb:.2f} MB)")
return str(cbz_path)
def create_cbz_from_pdf(pdf_path, output_name, dpi=150):
"""Convert a PDF to CBZ (useful for scanned comics)."""
try:
from pdf2image import convert_from_path
except ImportError:
raise ImportError("pip install pdf2image")
import io
print(f"Converting PDF to images ({dpi} DPI)...")
pages = convert_from_path(pdf_path, dpi=dpi)
cbz_path = Path(output_name).with_suffix('.cbz')
with zipfile.ZipFile(cbz_path, 'w', zipfile.ZIP_STORED) as cbz:
for i, page in enumerate(pages, 1):
buf = io.BytesIO()
page.save(buf, 'JPEG', quality=90, optimize=True)
cbz.writestr(f"{i:04d}.jpg", buf.getvalue())
print(f"CBZ created: {cbz_path} ({len(pages)} pages)")
return str(cbz_path)
# Example
create_cbz('scans/comic_001/', 'comic_001.cbz')
Extracting CBR (RAR) with Python
CBR requires unrar system tool or the rarfile library:
def extract_cbr(cbr_path, output_dir):
"""Extract a CBR using rarfile (requires unrar/unar installed)."""
try:
import rarfile
except ImportError:
raise ImportError("pip install rarfile # also: apt install unrar")
Path(output_dir).mkdir(parents=True, exist_ok=True)
extensions = {'.jpg', '.jpeg', '.png', '.webp'}
with rarfile.RarFile(cbr_path) as cbr:
files = sorted([
f for f in cbr.namelist()
if Path(f).suffix.lower() in extensions
])
for i, file in enumerate(files, 1):
ext = Path(file).suffix.lower()
dest = Path(output_dir) / f"page_{i:04d}{ext}"
dest.write_bytes(cbr.read(file))
print(f"CBR extracted: {len(files)} pages to '{output_dir}'")
def cbr_to_cbz(cbr_path, cbz_path=None):
"""Convert CBR to CBZ (more portable, no RAR dependency)."""
try:
import rarfile
except ImportError:
raise ImportError("pip install rarfile")
cbr_path = Path(cbr_path)
if cbz_path is None:
cbz_path = cbr_path.with_suffix('.cbz')
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif'}
with rarfile.RarFile(cbr_path) as cbr:
files = sorted([
f for f in cbr.namelist()
if Path(f).suffix.lower() in extensions
])
with zipfile.ZipFile(cbz_path, 'w', zipfile.ZIP_STORED) as cbz:
for i, file in enumerate(files, 1):
ext = Path(file).suffix.lower()
name = f"{i:04d}{ext}"
cbz.writestr(name, cbr.read(file))
orig = os.path.getsize(cbr_path) / 1024 / 1024
new = os.path.getsize(cbz_path) / 1024 / 1024
print(f"CBR→CBZ: {cbz_path} ({orig:.1f}MB → {new:.1f}MB)")
Converting CBZ to PDF
from pathlib import Path
import zipfile, io
from PIL import Image
def cbz_to_pdf(cbz_path, pdf_path=None):
"""
Convert CBZ to PDF.
Useful for sending to eBook readers or printing.
"""
cbz_path = Path(cbz_path)
if pdf_path is None:
pdf_path = cbz_path.with_suffix('.pdf')
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.gif'}
pages = []
with zipfile.ZipFile(cbz_path, 'r') as cbz:
files = sorted([
f for f in cbz.namelist()
if Path(f).suffix.lower() in extensions
and not f.startswith('__MACOSX')
])
for file in files:
data = cbz.read(file)
img = Image.open(io.BytesIO(data))
if img.mode != 'RGB':
img = img.convert('RGB')
pages.append(img)
if not pages:
raise ValueError("No images found in CBZ")
pages[0].save(
pdf_path,
save_all=True,
append_images=pages[1:],
resolution=150
)
size_mb = Path(pdf_path).stat().st_size / 1024 / 1024
print(f"PDF created: {pdf_path} ({len(pages)} pages, {size_mb:.1f} MB)")
return str(pdf_path)
Managing a Comic Collection
import json
from datetime import datetime
def scan_collection(root_dir):
"""Index all CBZ/CBR files in a collection."""
root = Path(root_dir)
collection = []
for file in sorted(root.rglob('*')):
if file.suffix.lower() not in {'.cbz', '.cbr', '.cb7'}:
continue
try:
entry = {
'path': str(file.relative_to(root)),
'name': file.stem,
'format': file.suffix.lower()[1:].upper(),
'size_mb': file.stat().st_size / 1024 / 1024,
'modified': datetime.fromtimestamp(
file.stat().st_mtime
).strftime('%Y-%m-%d'),
}
if file.suffix.lower() == '.cbz':
with zipfile.ZipFile(file) as cbz:
exts = {'.jpg', '.jpeg', '.png', '.webp'}
pages = [
f for f in cbz.namelist()
if Path(f).suffix.lower() in exts
]
entry['pages'] = len(pages)
else:
entry['pages'] = None # CBR requires rarfile
collection.append(entry)
except Exception as e:
print(f" Error reading {file.name}: {e}")
print(f"Collection indexed: {len(collection)} comics")
return collection
def export_index(collection, output='collection.json'):
"""Save collection index as JSON."""
with open(output, 'w', encoding='utf-8') as f:
json.dump(collection, f, ensure_ascii=False, indent=2)
print(f"Index saved: {output}")
def normalize_filenames(directory):
"""
Standardize CBZ filenames:
'Batman #001 (2011).cbz' → 'batman_001_2011.cbz'
"""
import re
changes = []
for f in Path(directory).glob('*.cbz'):
name = f.stem
new = re.sub(r'[^\w\s-]', '', name.lower())
new = re.sub(r'[\s-]+', '_', new).strip('_')
new = f"{new}.cbz"
if new != f.name:
f.rename(f.parent / new)
changes.append((f.name, new))
print(f"Renamed: {len(changes)} files")
return changes
# Usage
collection = scan_collection('D:/Comics/')
export_index(collection)
ComicInfo.xml — Standard Metadata
def create_comicinfo(title, number, year, writer, artist, publisher):
"""Generate standard ComicInfo.xml for CBZ."""
return f'''<?xml version="1.0" encoding="utf-8"?>
<ComicInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Title>{title}</Title>
<Number>{number}</Number>
<Year>{year}</Year>
<Writer>{writer}</Writer>
<Penciller>{artist}</Penciller>
<Publisher>{publisher}</Publisher>
<LanguageISO>en</LanguageISO>
<Format>Comic Book Archive</Format>
</ComicInfo>'''
def add_metadata_to_cbz(cbz_path, meta):
"""Insert ComicInfo.xml into an existing CBZ."""
cbz_path = Path(cbz_path)
tmp_path = cbz_path.with_suffix('.tmp.cbz')
xml = create_comicinfo(**meta)
with zipfile.ZipFile(cbz_path, 'r') as orig:
with zipfile.ZipFile(tmp_path, 'w', zipfile.ZIP_STORED) as new:
new.writestr('ComicInfo.xml', xml)
for item in orig.namelist():
new.writestr(item, orig.read(item))
tmp_path.replace(cbz_path)
print(f"Metadata added to {cbz_path}")
# Usage
add_metadata_to_cbz('batman_001.cbz', {
'title': 'Batman',
'number': 1,
'year': 2011,
'writer': 'Scott Snyder',
'artist': 'Greg Capullo',
'publisher': 'DC Comics',
})
CBZ vs PDF for Comics
| Criterion | CBZ | |
|---|---|---|
| Image quality | Original, lossless | Depends on export |
| File size | Similar to zipped images | Similar or larger |
| Comic reader support | ✅ Universal (Komga, YACReader…) | ⚠️ Partial |
| eReader support | ⚠️ Some only (not Kindle) | ✅ Universal |
| Zoom/pan | ✅ Native in comic readers | ✅ Native in PDF viewers |
| Metadata | ⚠️ Basic (ComicInfo.xml) | ✅ Advanced |
| Text search | ❌ Images only | ✅ With OCR |
| DRM | ❌ Not supported | ✅ Optional |
Conclusion
CBZ is the preferred format for digital comics due to its simplicity (standard ZIP) and universal support. Python handles complete CBZ collections without any additional software. For CBR (RAR) archives, the rarfile library makes migration to CBZ straightforward, eliminating the unrar dependency. The recommended workflow for collections: scan → convert CBR to CBZ → add ComicInfo.xml → organize with standard names.
Related conversions
Frequent conversions across the catalogue: