SCRIPT LIBRARY · POWERSHELL
Convert CBR Comics to CBZ, with Optional WebP Pages
Turn a folder of CBR comics into CBZ with 7-Zip, and optionally shrink the pages to WebP with cwebp or ImageMagick, testing every file before anything is replaced.
- What it does
- Extracts each .cbr with 7-Zip, optionally converts JPEG and PNG pages to WebP with cwebp or ImageMagick, and repacks everything as a .cbz. Each new file is tested before the original is replaced or removed.
- Requires
- PowerShell 7+ for parallel image conversion (Windows PowerShell 5.1 works, one page at a time)
- 7-Zip (7z.exe)
- For WebP only, cwebp (from Google's libwebp tools) or ImageMagick 7 (magick.exe)
- Permissions
- Read and write access to your comics folder. No admin rights.
- Runs on
- Windows 10/11
- Tested
- Parse-checked and run against test CBR/CBZ files with 7-Zip 23.01, cwebp 1.3.2 and ImageMagick in PowerShell 7.4, including -WhatIf, a corrupt CBR and re-running over already-converted files
Part 4 of the thread Archive conversion workshop
CBR and CBZ are the same idea, a stack of page images in an archive, with one difference: CBR is RAR inside and CBZ is ZIP. ZIP is open, every reader and server supports it, and tools like Komga, Kavita and Calibre are happier with it. So most people end up standardizing on CBZ.
The old version of this post leaned on nConvert and a hard-coded 12 CPU cores, and had a bug where its "do you want WebP?" prompt was ignored: -match "" is true for any answer, so it converted everything no matter what you typed. It also deleted each CBR before it had built the replacement. This rewrite fixes all of that. 7-Zip does the extracting (it reads RAR and RAR5 fine), and if you want smaller files, you choose the WebP converter with -ImageConverter: cwebp from Google's libwebp tools, or ImageMagick's magick.
It's the last part of the archive series, and it borrows the same safety habits as the others: build the new file in a temp folder, test it, then swap it in.
<#
.SYNOPSIS
Converts CBR comics to CBZ, with optional JPEG/PNG to WebP recompression.
.DESCRIPTION
Uses 7-Zip to extract each comic (RAR or ZIP inside, 7-Zip doesn't care), optionally runs
every page through cwebp or ImageMagick to make WebP images, then repacks the pages as a
stored (uncompressed) ZIP with a .cbz extension. The new file is tested before anything
is replaced or removed. Point it at .cbz files with -Include to shrink an existing library.
Supports -WhatIf.
.PARAMETER Path
Comic files, or folders to search. Accepts pipeline input.
.PARAMETER Include
Extensions to process. Default .cbr. Add .cbz to recompress existing CBZ files (needs -ImageConverter).
.PARAMETER Recurse
Search subfolders when Path is a folder.
.PARAMETER ImageConverter
None (default) just repackages. cwebp or magick converts pages to WebP.
.PARAMETER ConverterPath
Full path to cwebp.exe or magick.exe. If omitted, the script looks on PATH.
.PARAMETER Quality
WebP quality, 1-100. Default 80.
.PARAMETER ThrottleLimit
How many images to convert at once on PowerShell 7. Default: number of logical CPUs.
.PARAMETER RemoveSource
Delete each .cbr after its .cbz has been built and tested.
.PARAMETER Force
Overwrite a .cbz that already exists next to a .cbr.
.PARAMETER SevenZipPath
Full path to 7z.exe. If omitted, the script checks PATH, then the usual Program Files folders.
.EXAMPLE
.\Convert-ComicArchive.ps1 -Path D:\Comics -Recurse -WhatIf
.EXAMPLE
.\Convert-ComicArchive.ps1 -Path D:\Comics -Recurse -Include .cbr, .cbz -ImageConverter cwebp -Quality 82 -RemoveSource
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
[Alias('FullName')]
[string[]]$Path = '.',
[ValidateSet('.cbr', '.cbz')][string[]]$Include = '.cbr',
[switch]$Recurse,
[ValidateSet('None', 'cwebp', 'magick')][string]$ImageConverter = 'None',
[string]$ConverterPath,
[ValidateRange(1, 100)][int]$Quality = 80,
[ValidateRange(1, 64)][int]$ThrottleLimit = [Environment]::ProcessorCount,
[switch]$RemoveSource,
[switch]$Force,
[string]$SevenZipPath
)
begin {
if (-not $SevenZipPath) {
$SevenZipPath = @(
Get-Command -Name 7z.exe, 7z -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source
if ($env:ProgramFiles) { Join-Path $env:ProgramFiles '7-Zip\7z.exe' }
if (${env:ProgramFiles(x86)}) { Join-Path ${env:ProgramFiles(x86)} '7-Zip\7z.exe' }
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -First 1
}
if (-not $SevenZipPath -or -not (Test-Path -LiteralPath $SevenZipPath)) { throw '7-Zip not found. Install it from 7-zip.org or pass -SevenZipPath.' }
if ($ImageConverter -ne 'None') {
if (-not $ConverterPath) { $ConverterPath = Get-Command -Name $ImageConverter -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source }
if (-not $ConverterPath -or -not (Test-Path -LiteralPath $ConverterPath)) { throw "$ImageConverter not found. Install it or pass -ConverterPath." }
}
elseif ($Include -contains '.cbz') { throw 'Re-processing .cbz files only makes sense with -ImageConverter cwebp or magick.' }
function Invoke-SevenZip([string[]]$Arguments) {
$out = & $SevenZipPath @Arguments 2>&1
if ($LASTEXITCODE -ge 2) {
$errText = $out | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] } | ForEach-Object { "$_".Trim() } | Where-Object { $_ -match '\w' -and $_ -notmatch 'RemoteException|^ERRORS:$' -and $_ -ne $Arguments[-1] }
throw "7-Zip exit code ${LASTEXITCODE}: $(($errText | Select-Object -Unique) -join '; ')"
}
}
# One page in, one .webp out. Kept as text so PowerShell 7 can rebuild it inside -Parallel.
$convertPage = @'
param($Exe, $Kind, $Quality, $Source)
$dest = [IO.Path]::ChangeExtension($Source, '.webp')
if ($Kind -eq 'cwebp') { & $Exe -quiet -q $Quality $Source -o $dest 2>$null } else { & $Exe $Source -quality $Quality $dest 2>$null }
if ($LASTEXITCODE -eq 0 -and (Test-Path -LiteralPath $dest)) { Remove-Item -LiteralPath $Source; 1 } else { Remove-Item -LiteralPath $dest -ErrorAction SilentlyContinue; 0 }
'@
}
process {
$comics = foreach ($item in $Path) {
if (Test-Path -LiteralPath $item -PathType Container) {
Get-ChildItem -LiteralPath $item -File -Recurse:$Recurse | Where-Object { $Include -contains $_.Extension.ToLower() }
}
else { Get-Item -LiteralPath $item }
}
foreach ($comic in $comics) {
$target = Join-Path $comic.DirectoryName ($comic.BaseName + '.cbz')
$inPlace = $comic.Extension -eq '.cbz'
$result = [ordered]@{ Name = $comic.Name; BeforeKB = [math]::Round($comic.Length / 1KB); AfterKB = $null; Pages = $null; WebP = 0; Status = $null }
if (-not $inPlace -and (Test-Path -LiteralPath $target) -and -not $Force) { $result.Status = 'SkippedExists'; [pscustomobject]$result; continue }
$action = if ($inPlace) { 'Recompress pages and replace' } else { "Convert to $target" }
if (-not $PSCmdlet.ShouldProcess($comic.FullName, $action)) { continue }
$work = Join-Path ([IO.Path]::GetTempPath()) ('comic-' + [guid]::NewGuid().ToString('N'))
try {
$pagesDir = Join-Path $work 'pages'
$null = New-Item -ItemType Directory -Path $pagesDir -Force
Invoke-SevenZip @('x', '-y', '-bd', "-o$pagesDir", $comic.FullName)
$allFiles = @(Get-ChildItem -LiteralPath $pagesDir -File -Recurse)
$images = @($allFiles | Where-Object { '.jpg', '.jpeg', '.png' -contains $_.Extension.ToLower() })
$result.Pages = @($allFiles | Where-Object { '.jpg', '.jpeg', '.png', '.webp', '.gif' -contains $_.Extension.ToLower() }).Count
if (-not $allFiles) { throw 'Archive is empty.' }
if ($inPlace -and -not $images) { $result.Status = 'NothingToConvert'; [pscustomobject]$result; continue }
if ($ImageConverter -ne 'None' -and $images) {
if ($PSVersionTable.PSVersion.Major -ge 7) {
$done = $images.FullName | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
& ([scriptblock]::Create($using:convertPage)) $using:ConverterPath $using:ImageConverter $using:Quality $_
}
}
else {
$sb = [scriptblock]::Create($convertPage)
$done = foreach ($img in $images.FullName) { & $sb $ConverterPath $ImageConverter $Quality $img }
}
$result.WebP = ($done | Measure-Object -Sum).Sum
if ($result.WebP -lt $images.Count) { Write-Warning "$($comic.Name): $($images.Count - $result.WebP) page(s) kept in their original format." }
}
# Pages are already compressed, so store them (-mx=0). Faster, and readers open it instantly.
$tempCbz = Join-Path $work 'out.cbz'
Invoke-SevenZip @('a', '-tzip', '-mx=0', '-mcu=on', '-y', '-bd', $tempCbz, (Join-Path $pagesDir '*'))
Invoke-SevenZip @('t', '-bd', $tempCbz)
Move-Item -LiteralPath $tempCbz -Destination $target -Force -WhatIf:$false
$result.AfterKB = [math]::Round((Get-Item -LiteralPath $target).Length / 1KB)
$result.Status = if ($inPlace) { 'Recompressed' } else { 'Converted' }
if (-not $inPlace -and $RemoveSource -and $PSCmdlet.ShouldProcess($comic.FullName, 'Delete original CBR')) {
Remove-Item -LiteralPath $comic.FullName -Force
$result.Status = 'ConvertedRemovedCbr'
}
}
catch {
$result.Status = "Failed: $($_.Exception.Message)"
Write-Warning "$($comic.Name): $($_.Exception.Message)"
}
finally {
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue -WhatIf:$false
}
[pscustomobject]$result
}
}
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-Path | string[] | . | Comic files, or folders to search. Takes pipeline input. |
-Include | string[] | .cbr | Which extensions to process. Add .cbz to recompress an existing CBZ library (needs -ImageConverter). |
-Recurse | switch | — | Search subfolders too. You probably want this for a library organized by series. |
-ImageConverter | string | None | None just repackages. cwebp or magick converts JPEG and PNG pages to WebP. |
-ConverterPath | string | — | Full path to cwebp.exe or magick.exe if they're not on your PATH. |
-Quality | int | 80 | WebP quality, 1 to 100. 75 to 85 is the sweet spot for scanned pages. |
-ThrottleLimit | int | CPU count | How many pages to convert at once on PowerShell 7. |
-RemoveSource | switch | — | Delete each .cbr after its .cbz has been built and tested. Honors -WhatIf. |
-Force | switch | — | Overwrite a .cbz that already sits next to a .cbr with the same name. |
-SevenZipPath | string | — | Full path to 7z.exe. Leave it off and the script checks PATH, then Program Files and Program Files (x86). |
Run it
See what would be converted, without touching anything.
.\Convert-ComicArchive.ps1 -Path D:\Comics -Recurse -WhatIfStraight repackage, CBR to CBZ, and clean up the CBRs.
.\Convert-ComicArchive.ps1 -Path D:\Comics -Recurse -RemoveSourceConvert and shrink everything, including CBZs you already have, with cwebp.
.\Convert-ComicArchive.ps1 -Path D:\Comics -Recurse -Include .cbr, .cbz -ImageConverter cwebp -Quality 80 -RemoveSourceUse ImageMagick instead, and leave a couple of cores free.
.\Convert-ComicArchive.ps1 -Path 'D:\Comics\Space Rangers' -ImageConverter magick -ThrottleLimit 4What you'll see
WARNING: Space Rangers 007.cbr: 7-Zip exit code 2: ERROR: D:\Comics\Space Rangers 007.cbr; Cannot open the file as archive
Name BeforeKB AfterKB Pages WebP Status
---- -------- ------- ----- ---- ------
Space Rangers 005.cbr 48213 21870 24 24 ConvertedRemovedCbr
Space Rangers 006.cbr 51002 23114 26 26 ConvertedRemovedCbr
Space Rangers 007.cbr 512 0 Failed: 7-Zip exit code 2: ...
Space Rangers 004.cbz 46120 20988 24 24 Recompressed
Space Rangers 003.cbz 19870 22 0 NothingToConvert
How it works
- Check the tools. It finds 7-Zip the same way as the rest of the series. If you picked a WebP converter, it finds that too (or uses
-ConverterPath), and refuses to start without it. - Extract each comic to a temp folder. 7-Zip handles RAR, RAR5 and mislabeled ZIPs alike, and keeps any subfolders so page order survives.
- Convert pages, if asked. Every
.jpg,.jpegand.pnggoes through cwebp (-q <quality>) or magick (-quality <quality>). On PowerShell 7 that runs in parallel withForEach-Object -Parallel; on 5.1 it runs one page at a time. A page that fails to convert keeps its original format, and you get a warning with the count. - Repack and test. Pages are zipped with
-mx=0 -mcu=oninto a new.cbz, then7z tchecks it. - Replace or remove. The tested CBZ is moved into place. For a CBR,
-RemoveSourcedeletes the original throughShouldProcess. For a CBZ being recompressed, the new file replaces the old one, and-WhatIfshows that before it happens.
Take it further
- Resize while you're at it. cwebp's
-resize 1600 0or magick's-resize 1600x>shrink oversized scans to a sensible reading width, which saves far more than quality tweaks do. - Try lossless for line art. Clean black-and-white pages can come out smaller with cwebp's
-losslessthan with lossy settings, and they stay pixel-perfect. - Point your library server at it afterwards. If you run Komga or Kavita, trigger a library scan once the conversion's done so it picks up the new files.
Things that'll trip you up
- Check your reader handles WebP first. Most current desktop readers and servers do, but some older apps and a lot of e-ink devices don't. Convert one issue, open it everywhere you read, then do the rest.
- WebP is lossy, and there's no going back. Once the JPEGs are gone, they're gone. If you care about the originals, run with -ImageConverter None first, keep a backup of the library, or skip -RemoveSource until you've spot-checked a few.
- Plenty of .cbr files are really ZIPs. Somebody renamed a .zip to .cbr years ago, and it spread. 7-Zip looks at the file's contents, not the extension, so those convert fine.
- ImageMagick 6 doesn't have magick.exe. The -ImageConverter magick option expects ImageMagick 7, where the command is magick. If you only have the older convert.exe, pass it with -ConverterPath; the arguments are the same.
- Pages are stored, not compressed. The CBZ is built with -mx=0 on purpose. JPEG and WebP are already compressed, so zipping them again just costs time, and stored files open faster in readers.