Compare commits

..
Author SHA1 Message Date
dependabot[bot] 88c825df2d Update packaging requirement from >=26.2 to >=26.3
Updates the requirements on [packaging](https://github.com/pypa/packaging) to permit the latest version.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/26.2...26.3)

---
updated-dependencies:
- dependency-name: packaging
  dependency-version: '26.3'
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-10 01:42:45 +00:00
32 changed files with 68 additions and 212 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.11
cache: 'pip'
+2 -2
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.11
cache: 'pip'
@@ -71,7 +71,7 @@ jobs:
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- uses: actions/setup-node@v7
- uses: actions/setup-node@v6
with:
node-version: 16
- run: npm install -g appdmg
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
pip3 install --upgrade pip pyinstaller certifi
pip3 install --upgrade -r requirements-osx-legacy.txt
./gen_ui_files.sh
- uses: actions/setup-node@v7
- uses: actions/setup-node@v6
with:
node-version: 16
- run: npm install -g appdmg
+2 -2
View File
@@ -37,7 +37,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.11
cache: 'pip'
@@ -58,7 +58,7 @@ jobs:
name: windows-build-${{ matrix.entry }}
path: dist/*.exe
- id: optional_step_id
uses: signpath/github-action-submit-signing-request@v2.3
uses: signpath/github-action-submit-signing-request@v2.2
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
uses: actions/setup-python@v6
with:
python-version: 3.8
cache: 'pip'
@@ -51,7 +51,7 @@ jobs:
name: windows7-build
path: dist/*.exe
- id: optional_step_id
uses: signpath/github-action-submit-signing-request@v2.3
uses: signpath/github-action-submit-signing-request@v2.2
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
+33 -63
View File
@@ -1,8 +1,4 @@
Most manga conversion methods for ereaders add margins. But KCC enables true fullscreen from top to bottom.
![header](images/header_margins720.jpeg)
(feat. Ya Boy Kongming! and Fire Force from a [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1) PDF source.)
<img src="header.jpg" alt="Header Image" width="400">
# KCC
@@ -15,77 +11,54 @@ Most manga conversion methods for ereaders add margins. But KCC enables true ful
like Kindle, Kobo, ReMarkable, and more.
Pages display in fullscreen without margins,
with proper fixed layout support.
Supported input formats include JPG/PNG image files in folders, archives like CBZ, or PDFs.
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
KCC runs on Windows, macOS, and Linux.
Supported input formats include JPG/PNG image files in folders, archives like CBZ/EPUB, or PDF.
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, PDF, and folders of images.
The absolute highest quality source files are DRM-free PDFs from [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical.
The PDFs are often x12000 resolution or better, while even the 10" Kindle Scribe is only x2480.
KCC's main goal is maximum image quality at significantly smaller file size. For example, KCC
1) can compress a 600 MB manga volume from Humble Bundle to 100 MB.
This is mostly accomplished by downscaling to the native resolution of your specific device.
This can also improve battery life, page turn speed, and general performance
on underpowered ereaders with small memory and storage capacities.
2) fixes black levels to avoid gray/faded blacks found in many Kindle Store manga
![black](images/black720.jpeg)
3) fixes [the rainbow effect on Kaleido 3 color eink](https://www.youtube.com/watch?v=Dw2HTJCGMhw) without blur:
![rainbow](images/rainbow720.jpeg)
4) can [semi-automatically detect 2-page spreads](https://www.youtube.com/watch?v=kfIX67f7Aqk) from pre-split single pages:
![before](images/spreadbefore720.jpeg)
You can view combined spreads either rotated or not:
![full](images/full720.jpeg)
You can view the split halves either before or after combined spreads:
![half](images/half720.jpeg)
## Demo
Just drop your input files into the KCC window, hit convert, and USB drop the output files onto your device's `documents` folder!
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
**WARNING**: Kindle Scribe 2025 support may not be possible. Does not work well currently.
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
**NEW**: PDF output is now supported for direct conversion to reMarkable devices!
When using a reMarkable profile (Rmk1, Rmk2, RmkPP), the format automatically defaults to PDF
for optimal compatibility with your device's native PDF reader.
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
The absolute highest quality source files are print quality DRM-free PDFs from Kodansha/[Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1)/Fanatical,
which can be directly converted by KCC.
The GUI looks like this, built in Qt6.
There are a lot of options, but most people only care about the ones checked below.
Hover over each option to see details in a tooltip.
Its main feature is various optional image processing steps to look good on eink screens,
which have different requirements than normal LCD screens.
Combining that with downscaling to your specific device's screen resolution
can result in filesize reductions of hundreds of MB per volume with no visible quality loss on eink.
This can also improve battery life, page turn speed, and general performance
on underpowered ereaders with small memory and storage capacities.
![settings](images/settings.png)
KCC avoids many common formatting issues (some of which occur [even on the Kindle Store](https://github.com/ciromattia/kcc/wiki/Kindle-Store-bad-formatting)), such as:
1) faded black levels causing unneccessarily low contrast, which is hard to see and can cause eyestrain.
2) unneccessary margins at the bottom of the screen
3) Not utilizing the full 1860x2480 resolution of the 10" Kindle Scribe
4) incorrect page turn direction for manga that's read right to left
5) unaligned two page spreads in landscape, where pages are shifted over by 1
6) Removing without blur the rainbow effect on color eink Kaleido 3 due to manga screentones
7) Fixing page order problems due to Windows sort being different than Kindle/Kobo sort
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
The GUI looks like this, built in Qt6, with my most commonly used settings:
![image](https://github.com/user-attachments/assets/36ad2131-6677-4559-bd6f-314a90c27218)
Simply drag and drop your files/folders into the KCC window,
adjust your settings (hover over each option to see details in a tooltip),
and hit convert to create ereader optimized files.
You can change the default output directory by holding `Shift` while clicking the convert button.
Then just drag and drop the generated output files onto your device's documents folder via USB.
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac.
YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw
Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658
## Kindle Jailbreak for KOreader
Jailbreaking to use KOreader is optional. KCC works just fine with the native mobi manga viewer.
But if you jailbreak your Kindle, you'll unlock additional features like:
1) compatibility with image formats better suited for manga like 4-bit PNG and WEBP. This enables better quality at **half the file size** compared to JPG which must be 8-bit.
This is visually lossless since eink screens are 4-bit.
2) no dependency on kindlegen, which can mean **twice as fast conversions** since you can convert to CBZ directly.
3) You can dither by partially checking the JPG/PNG box and can crop in KCC instead of KOreader. This may increase page turn speed and battery.
### A word of warning
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
@@ -175,8 +148,6 @@ You'll need to install various tools to access important but optional features.
### KindleGen
(This is not needed for Koreader users on jailbroken Kindle. Just set output to CBZ instead.)
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
@@ -287,7 +258,6 @@ PROCESSING:
--pdfwidth Render vector PDFs based on device width instead of height.
-u, --upscale Resize images smaller than device's resolution
-s, --stretch Stretch images to device's resolution
--wallpaper Crop images to to fill screen
-r SPLITTER, --splitter SPLITTER
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]
-g GAMMA, --gamma GAMMA
-15
View File
@@ -693,18 +693,6 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QCheckBox" name="wallpaperBox">
<property name="toolTip">
<string>Auto check various options intended for KOreader wallpapers.
Will also crop images to fill the screen centered.</string>
</property>
<property name="text">
<string>Wallpaper mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -791,9 +779,6 @@ Will also crop images to fill the screen centered.</string>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>Hold shift while clicking for a low quality preview.</string>
</property>
<property name="text">
<string>Label Spreads</string>
</property>
View File

Before

Width:  |  Height:  |  Size: 921 KiB

After

Width:  |  Height:  |  Size: 921 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

+14 -46
View File
@@ -127,8 +127,6 @@ class Icons:
self.MOBIFormat.addPixmap(QPixmap(":/Formats/icons/MOBI.png"), QIcon.Mode.Normal, QIcon.State.Off)
self.CBZFormat = QIcon()
self.CBZFormat.addPixmap(QPixmap(":/Formats/icons/CBZ.png"), QIcon.Mode.Normal, QIcon.State.Off)
self.FOLDERFormat = QIcon()
self.FOLDERFormat.addPixmap(QPixmap(":/Other/icons/folder_new.png"), QIcon.Mode.Normal, QIcon.State.Off)
self.EPUBFormat = QIcon()
self.EPUBFormat.addPixmap(QPixmap(":/Formats/icons/EPUB.png"), QIcon.Mode.Normal, QIcon.State.Off)
self.KFXFormat = QIcon()
@@ -253,8 +251,6 @@ def get_options():
options.righttoleft = True
if GUI.lightnovelBox.isChecked():
options.lightnovel = True
if GUI.wallpaperBox.isChecked():
options.wallpaper = True
if GUI.ebokBox.isChecked():
options.ebok = True
if GUI.invertDirectionBox.isChecked():
@@ -446,9 +442,6 @@ class WorkerThread(QThread):
if gui_current_format == 'CBZ':
MW.addMessage.emit('Creating CBZ files', 'info', False)
GUI.progress.content = 'Creating CBZ files'
elif gui_current_format == 'FOLDER':
MW.addMessage.emit('Creating folders', 'info', False)
GUI.progress.content = 'Creating folders'
elif gui_current_format == 'PDF':
MW.addMessage.emit('Creating PDF files', 'info', False)
GUI.progress.content = 'Creating PDF files'
@@ -494,8 +487,6 @@ class WorkerThread(QThread):
GUI.progress.content = ''
if gui_current_format == 'CBZ':
MW.addMessage.emit('Creating CBZ files... <b>Done!</b>', 'info', True)
elif gui_current_format == 'FOLDER':
MW.addMessage.emit('Creating folders... <b>Done!</b>', 'info', True)
elif gui_current_format == 'PDF':
MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True)
else:
@@ -680,9 +671,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.jobList.scrollToBottom()
def labelSpreadsStart(self):
low_quality_preview = False
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
low_quality_preview = True
currentJobs = []
# TODO: make this a function since it's copy pasted
for i in range(GUI.jobList.count()):
@@ -716,37 +704,29 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
continue
# TODO: with statements
# TODO: ignore 1% of top and bottom too?
im1 = Image.open(os.path.join(root, files[i]))
im2 = Image.open(os.path.join(root, files[i+1]))
if not options.righttoleft:
im1, im2 = im2, im1
im1 = Image.open(os.path.join(root, files[i])).convert('1', dither=Dither.NONE)
size1 = im1.size
crop1 = im1.crop((0, 0, 0.2*size1[0], size1[1]))
crop11 = im1.crop((0.01*size1[0], 0, 0.04*size1[0], size1[1]))
#crop1 = crop11
im2 = Image.open(os.path.join(root, files[i+1])).convert('1', dither=Dither.NONE)
size2 = im2.size
preview_crop1 = im1.crop((0, 0, 0.2*size1[0], size1[1]))
if low_quality_preview:
preview_crop1 = preview_crop1.convert('1', dither=Dither.NONE)
calculation_crop1 = im1.crop((0.01*size1[0], 0, 0.04*size1[0], size1[1])).convert('1', dither=Dither.NONE)
preview_crop2 = im2.crop((0.8*size2[0], 0, size2[0], size2[1]))
if low_quality_preview:
preview_crop2 = preview_crop2.convert('1', dither=Dither.NONE)
calculation_crop2 = im2.crop((0.96*size2[0], 0, .99* size2[0], size2[1])).convert('1', dither=Dither.NONE)
crop2 = im2.crop((0.8*size2[0], 0, size2[0], size2[1]))
crop22 = im2.crop((0.96*size2[0], 0, .99* size2[0], size2[1]))
#crop2 = crop22
# dst = Image.new('1', (im1.width + im2.width, im1.height))
# dst.paste(im2, (0, 0))
# dst.paste(im1, (im1.width, 0))
hist1 = calculation_crop1.histogram()
hist2 = calculation_crop2.histogram()
hist1 = crop11.histogram()
hist2 = crop22.histogram()
# TODO: small percentage instead of zero
if hist1[0] == 0 or hist1[-1] == 0 or hist2[0] == 0 or hist2[-1] == 0:
continue
preview_mode = '1' if low_quality_preview else 'RGB'
dst = Image.new(preview_mode, (preview_crop1.width + preview_crop2.width, preview_crop1.height))
dst = Image.new('1', (crop1.width + crop2.width, crop1.height))
dst.paste(preview_crop2, (0, 0))
dst.paste(preview_crop1, (preview_crop1.width, 0))
dst.paste(crop2, (0, 0))
dst.paste(crop1, (crop1.width, 0))
dst.save(os.path.join(workdir, f'label-{i:04}.png'))
images.append(os.path.join(workdir, f'label-{i:04}.png'))
@@ -971,14 +951,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.noQuantizeBox.setEnabled(False)
GUI.forcePngRgbBox.setEnabled(False)
def toggleWallpaperBox(self, value):
if value == 2:
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
GUI.pngLegacyBox.setChecked(True)
GUI.rotateBox.setChecked(True)
GUI.noRotateBox.setChecked(True)
GUI.upscaleBox.setChecked(True)
self.changeFormat(3)
def togglechunkSizeCheckBox(self, value):
GUI.chunkSizeWidget.setVisible(value)
@@ -1078,10 +1050,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.upscaleBox.setChecked(True)
elif not GUI.webtoonBox.isChecked():
GUI.chunkSizeCheckBox.setEnabled(True)
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'FOLDER', 'PDF') and not GUI.webtoonBox.isChecked():
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
else:
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
@@ -1196,7 +1167,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
'lightnovelBox': GUI.lightnovelBox.checkState(),
'wallpaperBox': GUI.wallpaperBox.checkState(),
'ebokBox': GUI.ebokBox.checkState(),
'invertDirectionBox': GUI.invertDirectionBox.checkState(),
'languageEdit': GUI.languageEdit.text(),
@@ -1378,7 +1348,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"MOBI/AZW3": {'icon': 'MOBI', 'format': 'MOBI'},
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"Folder of images": {'icon': 'FOLDER', 'format': 'FOLDER'},
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
"KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
@@ -1594,7 +1563,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
GUI.mozJpegBox.stateChanged.connect(self.toggleImageFormatBox)
GUI.wallpaperBox.stateChanged.connect(self.toggleWallpaperBox)
GUI.chunkSizeCheckBox.stateChanged.connect(self.togglechunkSizeCheckBox)
GUI.deviceBox.activated.connect(self.changeDevice)
GUI.formatBox.activated.connect(self.changeFormat)
-14
View File
@@ -352,11 +352,6 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.keepComicInfoBox, 11, 3, 1, 1)
self.wallpaperBox = QCheckBox(self.optionWidget)
self.wallpaperBox.setObjectName(u"wallpaperBox")
self.gridLayout_2.addWidget(self.wallpaperBox, 11, 0, 1, 1)
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
@@ -859,21 +854,12 @@ class Ui_mainWindow(object):
"Keeping this file may crash some readers like the Kobo native CBZ reader.", None))
#endif // QT_CONFIG(tooltip)
self.keepComicInfoBox.setText(QCoreApplication.translate("mainWindow", u"Keep ComicInfo.xml", None))
#if QT_CONFIG(tooltip)
self.wallpaperBox.setToolTip(QCoreApplication.translate("mainWindow", u"Auto check various options intended for KOreader wallpapers.\n"
"\n"
"Will also crop images to fill the screen centered.", None))
#endif // QT_CONFIG(tooltip)
self.wallpaperBox.setText(QCoreApplication.translate("mainWindow", u"Wallpaper mode", None))
#if QT_CONFIG(tooltip)
self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None))
self.humbleButton.setText(QCoreApplication.translate("mainWindow", u"Humble Bundle Referral", None))
self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
#if QT_CONFIG(tooltip)
self.labelSpreadsButton.setToolTip(QCoreApplication.translate("mainWindow", u"Hold shift while clicking for a low quality preview.", None))
#endif // QT_CONFIG(tooltip)
self.labelSpreadsButton.setText(QCoreApplication.translate("mainWindow", u"Label Spreads", None))
#if QT_CONFIG(tooltip)
self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None))
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '11.1.0'
__version__ = '11.0.1'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'
+7 -54
View File
@@ -892,13 +892,6 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
cpu = cpu_count()
if not render:
print('PDF input can also use legacy extract option if you have any problems')
if GUI:
GUI.addMessage.emit('PDF input can also use legacy extract option if you have any problems'
, 'info', False)
GUI.addMessage.emit('', '', False)
# make vectors of arguments for the processes
vectors = [(i, cpu, filename, output_dir, target_width, target_height, pdfwidth) for i in range(cpu)]
print("Starting %i processes for '%s'." % (cpu, filename))
@@ -1065,8 +1058,6 @@ def getWorkFolder(afile, options, workdir=None):
def getOutputFilename(srcpath, wantedname, ext, tomenumber):
if options.folder_output:
ext = ''
source_path = Path(srcpath)
if srcpath[-1] == os.path.sep:
srcpath = srcpath[:-1]
@@ -1078,7 +1069,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
ext = '.kepub.epub'
if wantedname is not None:
wanted_root, wanted_ext = os.path.splitext(wantedname)
if not options.folder_output and wantedname.endswith(ext):
if wantedname.endswith(ext):
filename = os.path.abspath(wantedname)
elif wanted_ext == '.mobi' and ext == '.epub':
filename = os.path.abspath(wanted_root + ext)
@@ -1102,10 +1093,10 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
filename = source_path.with_name(name)
else:
filename = os.path.splitext(srcpath)[0] + tomenumber + ext
if os.path.exists(filename):
if os.path.isfile(filename):
counter = 0
basename = os.path.splitext(filename)[0]
while os.path.exists(basename + '_kcc' + str(counter) + ext):
while os.path.isfile(basename + '_kcc' + str(counter) + ext):
counter += 1
filename = basename + '_kcc' + str(counter) + ext
elif options.format == 'MOBI' and ext == '.epub':
@@ -1439,9 +1430,6 @@ def slugify(value, options, is_natural_sorted):
return value
def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
if options.folder_output:
move(basedir, zipfilename)
return zipfilename
start = perf_counter()
if SEVENZIP in available_archive_tools():
if isepub:
@@ -1487,8 +1475,6 @@ def makeParser():
help="Manga style (right-to-left reading and splitting)")
main_options.add_argument("--lightnovel", action="store_true", dest="lightnovel", default=False,
help="Only resize images and preserve original file structure.")
main_options.add_argument("--wallpaper", action="store_true", dest="wallpaper", default=False,
help="Crop to fill screen.")
main_options.add_argument("--ebok", action="store_true", dest="ebok", default=False,
help="Force EBOK tag instead of PDOC for MOBI")
main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False,
@@ -1653,12 +1639,6 @@ def checkOptions(options):
options.keep_epub = True
options.format = 'MOBI'
options.kfx = False
options.skip_zip = False
options.folder_output = False
if options.format == 'FOLDER':
options.format = 'CBZ'
options.skip_zip = True
options.folder_output = True
if options.format == 'Auto':
if options.profile in ['KDX']:
options.format = 'CBZ'
@@ -1831,7 +1811,6 @@ def makeBook(source, qtgui=None, job_progress=''):
print(f"{job_progress}Preparing source images...")
path = getWorkFolder(source, options)
print(f"{job_progress}Checking images...")
_, ext = os.path.splitext(source)
if options.lightnovel:
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
@@ -1852,6 +1831,7 @@ def makeBook(source, qtgui=None, job_progress=''):
if img.size[0] > x or img.size[1] > y:
img = ImageOps.contain(img, (x, y))
img.save(os.path.join(root, file), quality=options.jpegquality)
_, ext = os.path.splitext(source)
if ext != '.epub':
ext = '.cbz'
output_file = getOutputFilename(source, options.output, ext, '')
@@ -1867,20 +1847,14 @@ def makeBook(source, qtgui=None, job_progress=''):
if os.path.exists(source+'.json'):
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
chapterNames = {}
cover_path = None
with open(source+'.json') as f:
data = json.load(f)
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
sorted_files = os_sorted(files)
for i in range(len(sorted_files)):
if not cover_path:
cover_path = os.path.join(root, sorted_files[i])
for i in range(len(files)):
if i in data['spreads']:
im1 = Image.open(os.path.join(root, sorted_files[i]))
im2 = Image.open(os.path.join(root, sorted_files[i+1]))
if not options.righttoleft:
im1, im2 = im2, im1
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
dst.paste(im2, (0, 0))
dst.paste(im1, (im1.width, 0))
@@ -1888,31 +1862,10 @@ def makeBook(source, qtgui=None, job_progress=''):
dst.save(os.path.join(path, 'OEBPS', 'Images', f'{base}-merged.png'))
os.remove(os.path.join(root, sorted_files[i]))
os.remove(os.path.join(root, sorted_files[i+1]))
if not os.path.exists(cover_path):
cover_path = os.path.join(root, f'{base}-merged.png')
if options.filefusion:
# Strip the fusion_0001_ sort prefix from makeFusion if present
chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()}
source_path = Path(source)
options.customcover = False
if source_path.parent.joinpath('Covers').is_dir():
series = os_sorted(filter(lambda s: s.endswith(ext) and '_kcc' not in s, os.listdir(source_path.parent)))
source_index = series.index(os.path.basename(source))
covers = os.listdir(source_path.parent.joinpath('Covers'))
filtered_covers = []
for cover in covers:
_, cover_ext = getImageFileName(cover)
if cover_ext in IMAGE_TYPES:
filtered_covers.append(cover)
sorted_covers = os_sorted(filtered_covers)
try:
cover_path = source_path.parent.joinpath('Covers', sorted_covers[source_index])
options.customcover = True
except IndexError:
pass
cover = None
if not options.webtoon:
cover = image.Cover(cover_path, options)
@@ -1994,7 +1947,7 @@ def makeBook(source, qtgui=None, job_progress=''):
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
if cover and (cover.smartcover or options.customcover):
if cover and cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', '##cover.jpg'), tomeNumber, len(tomes))
if options.comicinfo_xml:
with open(os.path.join(tome, 'OEBPS', 'Images', 'ComicInfo.xml'), 'wb') as xmlOutput:
@@ -2005,7 +1958,7 @@ def makeBook(source, qtgui=None, job_progress=''):
# determine output filename based on source and tome count
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
if cover and (cover.smartcover or options.customcover):
if cover and cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
# use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
-2
View File
@@ -112,8 +112,6 @@ def splitImage(work):
imgProcess = imgEdges.point(lambda p: 255 if p > 6 else 0).convert('1', dither=Dither.NONE)
widthImg, heightImg = imgOrg.size
if widthImg < 300:
raise UserWarning('PDF render failed < 300 px. Try legacy extract option instead.')
if heightImg > opt.height:
if opt.debug:
drawImg = Image.open(filePath).convert(mode='RGBA')
+1 -5
View File
@@ -513,9 +513,7 @@ class ComicPage:
self.image = erase_rainbow_artifacts(self.image, is_color)
def resizeImage(self):
if self.opt.wallpaper:
pass
elif self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
if self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
# TODO: Kindle Scribe case
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
@@ -538,8 +536,6 @@ class ComicPage:
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
elif self.opt.stretch:
self.image = self.image.resize(self.size, method)
elif self.opt.wallpaper:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
pass
else: # if image bigger than device resolution or smaller with upscaling
+1 -1
View File
@@ -2,7 +2,7 @@ Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4
packaging>=26.2
packaging>=26.3
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
# Below requirements are compiled in Dockefile
+1 -1
View File
@@ -3,7 +3,7 @@ Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4
packaging>=26.2
packaging>=26.3
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
numpy<2
+1 -1
View File
@@ -3,7 +3,7 @@ Pillow>=9
psutil>=7.2.2
requests>=2.32.4
python-slugify>=8.0.4
packaging>=26.2
packaging>=26.3
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
numpy==1.23.5
+1 -1
View File
@@ -3,7 +3,7 @@ Pillow>=11.3.0
psutil>=7.2.2
requests>=2.34.2
python-slugify>=8.0.4,<9.0.0
packaging>=26.2
packaging>=26.3
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
numpy>=1.22.4