Compare commits

...
25 Commits
Author SHA1 Message Date
Alex Xu ea532c709b Update README.md 2026-09-08 13:19:05 -07:00
Alex Xu 1ce92bac83 Update README.md 2026-09-08 11:53:00 -07:00
Alex Xu 5b67d4019e Update README.md 2026-09-06 22:25:38 -07:00
Alex Xu 2423e2e44a Add files via upload 2026-09-06 22:22:59 -07:00
Alex Xu eb694b2dc3 bump to 11.2.0 2026-09-06 22:14:10 -07:00
Alex Xu 04aac3ffc0 add simple mode (#1434)
* add expert mode

* add helper text
2026-09-06 22:13:48 -07:00
Alex Xu 0f630c5804 fix 7zip warning 2026-09-06 20:56:32 -07:00
Alex Xu d272073748 Update README.md 2026-09-06 20:50:29 -07:00
Alex Xu 66c6b8611a bump to 11.1.0 2026-09-03 23:06:40 -07:00
dependabot[bot]andpublic 1b3b619491 Bump signpath/github-action-submit-signing-request from 2.2 to 2.3 (#1418)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 2.2 to 2.3.
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v2.2...v2.3)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 21:59:40 -07:00
dependabot[bot]andpublic fb784a2eb4 Bump actions/setup-node from 6 to 7 (#1401)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 21:57:10 -07:00
dependabot[bot]andpublic f2deb1061f Bump actions/setup-python from 6 to 7 (#1408)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-03 21:56:56 -07:00
Alex Xu 3be76ec89f add legacy extract warning (#1433) 2026-09-03 21:52:27 -07:00
Alex Xu 38fa13610f A "Covers" folder next to source files defines custom covers (#1432)
* A "Covers" folder next to source files defines custom covers

* fix case with output folder unchecked
2026-09-03 15:00:07 -07:00
Alex Xu 6d3bd0296c add folder output to readme 2026-09-03 10:31:36 -07:00
Alex Xu 9decf0e68e add wallpaper mode for KOreader (#1431)
* add wallpaper mode for KOreader

* fix wallpaper interactions with no rotate
2026-09-03 10:21:08 -07:00
Alex Xu dc2cc8e7b8 rename folder to folder of images 2026-09-02 19:49:13 -07:00
Alex Xu 55468019dd add folder output option (#1430) 2026-09-02 16:56:42 -07:00
Alex Xu 8f191e604b replace more images 2026-08-30 18:12:27 -07:00
Alex Xu 924f83d976 use smaller images 2026-08-30 18:09:30 -07:00
Alex Xu 52d52ff323 Update README.md 2026-08-29 16:21:41 -07:00
Alex Xu e93be5cf68 Add files via upload 2026-08-29 16:21:20 -07:00
Alex Xu 4cdbb4beef bump to 11.0.3 2026-08-28 14:52:42 -07:00
Alex Xu 2c9056c524 label spread preview default is high quality (#1429) 2026-08-28 14:46:18 -07:00
Alex Xu a503f54f9f Spread labels support left to right (#1428) 2026-08-28 14:38:22 -07:00
23 changed files with 1640 additions and 1412 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v7
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@v6
uses: actions/setup-python@v7
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@v6
- uses: actions/setup-node@v7
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@v6
- uses: actions/setup-node@v7
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@v6
uses: actions/setup-python@v7
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.2
uses: signpath/github-action-submit-signing-request@v2.3
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@v6
uses: actions/setup-python@v7
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.2
uses: signpath/github-action-submit-signing-request@v2.3
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
+25 -18
View File
@@ -1,6 +1,6 @@
Most manga conversion methods for ereaders add margins. But KCC enables true fullscreen from top to bottom.
![header](images/header_margins.jpeg)
![header](images/header_margins720.jpeg)
(feat. Ya Boy Kongming! and Fire Force from a [Humble Bundle](https://humblebundleinc.sjv.io/xL6Zv1) PDF source.)
@@ -19,7 +19,7 @@ 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, and 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.
@@ -33,41 +33,45 @@ KCC's main goal is maximum image quality at significantly smaller file size. For
2) fixes black levels to avoid gray/faded blacks found in many Kindle Store manga
![black](images/black.jpeg)
![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/rainbow2.jpeg)
![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/spreadbefore3.jpeg)
![before](images/spreadbefore720.jpeg)
You can view combined spreads either rotated or not:
![full](images/full.jpeg)
![full](images/full720.jpeg)
You can view the split halves either before or after combined spreads:
![half](images/half.jpeg)
![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
![settings](images/easy.png)
There are a lot of options, but most people only care about the ones checked above.
Hover over each option to see details in a tooltip.
NOTE: some checkboxes can have a half-check marks.
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
Here's a quick video demo of how the process works (using expert mode):
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
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.
![settings](images/settings.png)
**WARNING**: Kindle Scribe 2025 MOBI support is limited, converted books may have blank pages. Use PDF instead.
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
@@ -77,7 +81,7 @@ 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.
Jailbreaking to use KOreader is optional. MOBI manga converted by KCC can be opened in Kindles native reader just fine
But if you jailbreak your Kindle, you'll unlock additional features like:
@@ -86,6 +90,10 @@ But if you jailbreak your Kindle, you'll unlock additional features like:
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.
Common KOreader manga mistakes: https://youtu.be/gaakIh9p8tw?si=X7OgPJlAvL9XGbLR
Set per folder setting defaults: https://youtu.be/O9p6HVWIQ6s?si=Jtx607erCBA_auWM
### 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 +183,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,6 +293,7 @@ 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
+688 -660
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

+133 -10
View File
@@ -127,6 +127,8 @@ 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()
@@ -153,17 +155,21 @@ class Icons:
class VersionThread(QThread):
def __init__(self):
def __init__(self, startNumber2):
QThread.__init__(self)
self.newVersion = ''
self.md5 = ''
self.barProgress = 0
self.answer = None
self.startNumber2 = startNumber2
def __del__(self):
self.wait()
def run(self):
if self.startNumber2 < 5:
return
try:
# unauthenticated API requests limit is 60 req/hour
if getattr(sys, 'frozen', False):
@@ -251,6 +257,8 @@ 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():
@@ -442,6 +450,9 @@ 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'
@@ -487,6 +498,8 @@ 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:
@@ -671,9 +684,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.jobList.scrollToBottom()
def labelSpreadsStart(self):
low_quality_preview = True
if QApplication.keyboardModifiers() == Qt.ShiftModifier:
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()):
@@ -708,19 +721,22 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
# 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
size1 = im1.size
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)
#crop1 = crop11
im2 = Image.open(os.path.join(root, files[i+1]))
size2 = im2.size
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 = crop22
# dst = Image.new('1', (im1.width + im2.width, im1.height))
# dst.paste(im2, (0, 0))
# dst.paste(im1, (im1.width, 0))
@@ -811,6 +827,91 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
def toggleExpert(self):
# noinspection PyCallByClass
if self.expertMode:
self.hide_expert_options()
self.expertMode = False
else:
self.show_expert_options()
self.expertMode = True
def show_expert_options(self):
GUI.spreadShiftBox.show()
GUI.fileFusionBox.show()
GUI.jpegQualityBox.show()
GUI.pngLegacyBox.show()
GUI.webpBox.show()
GUI.lightnovelBox.show()
GUI.wallpaperBox.show()
GUI.outputSplit.show()
GUI.maximizeStrips.show()
GUI.noRotateBox.show()
GUI.rotateFirstBox.show()
GUI.coverFillBox.show()
GUI.rotateRightBox.show()
GUI.smartCoverCropBox.show()
GUI.onePageLandscapeBox.show()
GUI.qualityBox.show()
GUI.gammaBox.show()
GUI.interPanelCropBox.show()
GUI.autoLevelBox.show()
GUI.autocontrastBox.show()
GUI.vertical4PanelBox.show()
GUI.tempDirBox.show()
GUI.deleteBox.show()
GUI.metadataTitleBox.show()
GUI.keepComicInfoBox.show()
GUI.pdfWidthBox.show()
GUI.chunkSizeCheckBox.show()
GUI.disableProcessingBox.show()
GUI.noQuantizeBox.show()
GUI.forcePngRgbBox.show()
GUI.invertDirectionBox.show()
GUI.ebokBox.show()
GUI.languageEdit.show()
GUI.labelSpreadsButton.show()
GUI.editorButton.show()
GUI.easyLabel.hide()
def hide_expert_options(self):
GUI.spreadShiftBox.hide()
GUI.fileFusionBox.hide()
GUI.jpegQualityBox.hide()
GUI.pngLegacyBox.hide()
GUI.webpBox.hide()
GUI.lightnovelBox.hide()
GUI.wallpaperBox.hide()
GUI.outputSplit.hide()
GUI.maximizeStrips.hide()
GUI.noRotateBox.hide()
GUI.rotateFirstBox.hide()
GUI.coverFillBox.hide()
GUI.rotateRightBox.hide()
GUI.smartCoverCropBox.hide()
GUI.onePageLandscapeBox.hide()
GUI.qualityBox.hide()
GUI.gammaBox.hide()
GUI.interPanelCropBox.hide()
GUI.autoLevelBox.hide()
GUI.autocontrastBox.hide()
GUI.vertical4PanelBox.hide()
GUI.tempDirBox.hide()
GUI.deleteBox.hide()
GUI.metadataTitleBox.hide()
GUI.keepComicInfoBox.hide()
GUI.pdfWidthBox.hide()
GUI.chunkSizeCheckBox.hide()
GUI.disableProcessingBox.hide()
GUI.noQuantizeBox.hide()
GUI.forcePngRgbBox.hide()
GUI.invertDirectionBox.hide()
GUI.ebokBox.hide()
GUI.languageEdit.hide()
GUI.labelSpreadsButton.hide()
GUI.editorButton.hide()
GUI.easyLabel.show()
def openYouTube(self):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
@@ -959,6 +1060,14 @@ 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)
@@ -1058,9 +1167,10 @@ 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', 'PDF') and not GUI.webtoonBox.isChecked():
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'FOLDER', '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)
@@ -1167,14 +1277,17 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not GUI.convertButton.isEnabled():
event.ignore()
self.settings.setValue('settingsVersion', __version__)
self.settings.setValue('expertMode', self.expertMode)
self.settings.setValue('lastPath', self.lastPath)
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
self.settings.setValue('startNumber', self.startNumber + 1)
self.settings.setValue('startNumber2', self.startNumber2 + 1)
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(),
@@ -1310,11 +1423,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not os.path.exists(self.defaultOutputFolder):
self.defaultOutputFolder = ''
self.expertMode = self.settings.value('expertMode', False, type=bool)
if not self.expertMode:
GUI.hide_expert_options()
# default is Kindle Paperwhite 12th Gen
self.lastDevice = self.settings.value('lastDevice', 3, type=int)
self.currentFormat = self.settings.value('currentFormat', 0, type=int)
self.startNumber = self.settings.value('startNumber', 0, type=int)
self.startNumber2 = self.settings.value('startNumber2', 0, type=int)
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
try:
@@ -1322,7 +1440,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
except Exception:
self.options = default_options
self.worker = WorkerThread()
self.versionCheck = VersionThread()
self.versionCheck = VersionThread(self.startNumber2)
self.progress = ProgressThread()
self.tray = SystemTrayIcon()
self.conversionAlive = False
@@ -1356,6 +1474,7 @@ 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'},
@@ -1539,8 +1658,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
statusBarLabel.setOpenExternalLinks(True)
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
self.addMessage('<b>Tip:</b> Hover mouse over options/buttons to see explanations. Boxes can be partially/fully checked.', 'info')
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
self.addMessage("<b>Tip:</b> Calibre may add margins! USB drop directly into the device's documents folder instead.", 'info')
self.addMessage("<b>Tip:</b> You can toggle easy/expert mode using button at top right.", 'info')
if self.startNumber < 5:
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
@@ -1561,6 +1682,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble)
GUI.expertButton.clicked.connect(self.toggleExpert)
GUI.convertButton.clicked.connect(self.convertStart)
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
@@ -1571,6 +1693,7 @@ 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)
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '11.0.2'
__version__ = '11.2.0'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'
+47 -6
View File
@@ -892,6 +892,13 @@ 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))
@@ -1058,6 +1065,8 @@ 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]
@@ -1069,7 +1078,7 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
ext = '.kepub.epub'
if wantedname is not None:
wanted_root, wanted_ext = os.path.splitext(wantedname)
if wantedname.endswith(ext):
if not options.folder_output and wantedname.endswith(ext):
filename = os.path.abspath(wantedname)
elif wanted_ext == '.mobi' and ext == '.epub':
filename = os.path.abspath(wanted_root + ext)
@@ -1093,10 +1102,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.isfile(filename):
if os.path.exists(filename):
counter = 0
basename = os.path.splitext(filename)[0]
while os.path.isfile(basename + '_kcc' + str(counter) + ext):
while os.path.exists(basename + '_kcc' + str(counter) + ext):
counter += 1
filename = basename + '_kcc' + str(counter) + ext
elif options.format == 'MOBI' and ext == '.epub':
@@ -1430,6 +1439,9 @@ 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:
@@ -1475,6 +1487,8 @@ 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,
@@ -1639,6 +1653,12 @@ 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'
@@ -1811,6 +1831,7 @@ 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')):
@@ -1831,7 +1852,6 @@ 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, '')
@@ -1859,6 +1879,8 @@ def makeBook(source, qtgui=None, job_progress=''):
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))
@@ -1872,6 +1894,25 @@ def makeBook(source, qtgui=None, job_progress=''):
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)
@@ -1953,7 +1994,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:
if cover and (cover.smartcover or options.customcover):
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:
@@ -1964,7 +2005,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:
if cover and (cover.smartcover or options.customcover):
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,6 +112,8 @@ 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 -1
View File
@@ -99,7 +99,7 @@ class ComicArchive:
pass
if missing:
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
raise UserWarning(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
else:
raise OSError(EXTRACTION_ERROR)
+5 -1
View File
@@ -513,7 +513,9 @@ class ComicPage:
self.image = erase_rainbow_artifacts(self.image, is_color)
def resizeImage(self):
if self.opt.norotate and self.targetPathOrder in ('-kcc-a', '-kcc-d') and not self.opt.kindle_scribe_azw3:
if self.opt.wallpaper:
pass
elif 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)
@@ -536,6 +538,8 @@ 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