1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-20 07:58:51 +00:00

Compare commits

...

37 Commits

Author SHA1 Message Date
Alex Xu
a7a9f35686 Update README.md 2026-04-19 10:27:52 -07:00
Alex Xu
d5146d02fc Update README.md 2026-04-19 10:10:18 -07:00
Alex Xu
b0374e127d Smarter covers (#1300)
* smart crop semi-wide covers

* make it even smarter

* make it even smarter

* adjust ratio
2026-04-18 07:39:23 -07:00
Alex Xu
894dbfc8a2 don't bisect images with < 1.16 or > 1.75 aspect ratios (#1301) 2026-04-17 18:03:31 -07:00
Alex Xu
72f98bb032 bump to 9.7.2 2026-04-16 13:30:08 -07:00
Alex Xu
96bf14d386 Kindle Scribe 2025 default is PDF (#1297) 2026-04-16 13:26:04 -07:00
Alex Xu
d4e1565e4a colorsoft is wrong too 2026-04-16 13:25:41 -07:00
Alex Xu
c3030e8bd1 Paperwhite 12th Gen is actually 1272x1696 2026-04-16 13:25:41 -07:00
Alex Xu
c5744117e3 bump to 9.7.1 2026-04-15 21:59:27 -07:00
Alex Xu
cd2eeb4d0f option: create temporary files directory on source file drive (#1296) 2026-04-15 21:40:03 -07:00
Alex Xu
e7b7054b0e smart cover crop is default on and implemented for all formats (#1295) 2026-04-15 21:25:08 -07:00
Alex Xu
6f26bd5874 add epub series metadata (#1294) 2026-04-15 17:22:00 -07:00
Alex Xu
d5ca8fb407 don't bisect images with aspect ratio > 2 (#1293) 2026-04-13 23:03:26 -07:00
Alex Xu
8d61a9e558 bump to 9.7.0 2026-04-12 11:31:27 -07:00
Alex Xu
8aaedf274d add youtube, discord, humble buttons (#1292)
* add youtube, discord, humble buttons

* restore margins of top buttons

* fix button height on windows

* don't bold messages
2026-04-12 11:30:18 -07:00
Alex Xu
5782a44e7b sign windows 7 version (#1291) 2026-04-12 09:17:23 -07:00
Alex Xu
e4c918f0f3 add webp output support (ignored for Kindle MOBI/EPUB and all PDF) (#1290)
* add webp output support (ignored for Kindle MOBI/EPUB)

* disable png extra optons by default

* pdf webp is not supported
2026-04-12 09:04:21 -07:00
Alex Xu
8f4072bfab disable extra png options when png is not selected (#1289) 2026-04-11 19:32:49 -07:00
Alex Xu
61f3097be5 smart cover crop is now an option default off (#1288) 2026-04-11 19:09:04 -07:00
Alex Xu
fa33ef8f89 revert 2x render, too expensive (#1287) 2026-04-11 18:06:03 -07:00
Alex Xu
232bac00a9 ZIP stored instead of deflate (#1283) 2026-04-06 21:03:17 -07:00
Alex Xu
d19a4754fa if cropping, render pdf to 2x (#1276) 2026-03-26 08:09:25 -07:00
Alex Xu
9a93cc4b17 Update __init__.py 2026-03-17 16:11:57 -07:00
Alex Xu
e0471b2dc9 fix autocrop in certain situations (#1274) 2026-03-17 16:11:40 -07:00
Alex Xu
a87eb318cf bump 9.6.1 2026-03-17 15:10:02 -07:00
Alex Xu
87987c9ebf fix humble bundle pdf png autocontrast (#1273) 2026-03-17 15:09:02 -07:00
Alex Xu
f5fe8d93b0 color images are always saved as JPG by default (#1272)
* use jpg for color images always

* add colorOutput variable

* fix typos

* remove dither

* add box

* clarify png

* remove debug code

* remove unneeded check
2026-03-17 13:41:41 -07:00
Alex Xu
249f823f01 add PNG legacy option (#1271) 2026-03-13 21:45:05 -07:00
Alex Xu
3a9d4f274d bump to 9.6.0 2026-03-13 16:29:50 -07:00
Alex Xu
b5de6fd39d add pdf width box (#1270) 2026-03-13 16:28:53 -07:00
Alex Xu
b4b9e41a0c add no quantize option (#1269) 2026-03-13 15:07:41 -07:00
Alex Xu
9b9181a715 Add rotate right option (#1268) 2026-03-13 14:15:14 -07:00
Alex Xu
472fdc97b5 PDF PNG half size, Kindle DX PNG CBZ fixed (#1267)
* PNG for PDF or Kindle DX CBZ is dithered to 16 colors for ~1/2 filesize reduction

* Kindle DX default no borders
2026-03-13 12:17:38 -07:00
Alex Xu
6fdfddd7d9 bump to 9.5.1 2026-03-10 14:00:54 -07:00
Alex Xu
34fb68ac65 downgrade to pyside 6.9 2026-03-10 13:50:59 -07:00
Alex Xu
b4d72cd581 remove setuptools and wheel (#1265) 2026-03-10 13:37:59 -07:00
Alex Xu
1dead9af8f add PDF 200 MB option (#1264) 2026-03-10 13:29:46 -07:00
14 changed files with 1716 additions and 1294 deletions

View File

@@ -35,7 +35,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2 libxcb-cursor0
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
python -m pip install --upgrade pip certifi pyinstaller --no-binary pyinstaller
python -m pip install -r requirements.txt
- name: build binary
run: |

View File

@@ -38,7 +38,7 @@ jobs:
cache: 'pip'
- name: Install python dependencies
run: |
python -m pip install --upgrade pip setuptools wheel pyinstaller certifi
python -m pip install --upgrade pip pyinstaller certifi
pip install -r requirements.txt
- name: Install the Apple certificate and provisioning profile
# TODO signing

View File

@@ -40,7 +40,7 @@ jobs:
- name: Install Python dependencies
run: |
python3 --version
pip3 install --upgrade pip setuptools wheel pyinstaller certifi
pip3 install --upgrade pip pyinstaller certifi
pip3 install --upgrade -r requirements-osx-legacy.txt
./gen_ui_files.sh
- uses: actions/setup-node@v6

View File

@@ -45,7 +45,7 @@ jobs:
env:
PYINSTALLER_COMPILE_BOOTLOADER: 1
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install certifi pyinstaller --no-binary pyinstaller
- name: build binary

View File

@@ -37,7 +37,7 @@ jobs:
env:
PYINSTALLER_COMPILE_BOOTLOADER: 1
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade pip
pip install -r requirements-win7.txt
pip install certifi pyinstaller --no-binary pyinstaller
.\gen_ui_files.bat
@@ -50,6 +50,17 @@ jobs:
with:
name: windows7-build
path: dist/*.exe
- id: optional_step_id
uses: signpath/github-action-submit-signing-request@v2.0
if: ${{ github.repository == 'ciromattia/kcc' }}
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
project-slug: 'kcc'
signing-policy-slug: 'release-signing'
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'dist/'
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')

View File

@@ -4,7 +4,7 @@ FROM python:3.13-slim-bullseye AS builder
# Install system dependencies
RUN set -x && \
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev python3-setuptools python3-wheel" && \
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev" && \
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}

View File

@@ -11,7 +11,7 @@
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, or PDFs.
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.
@@ -115,15 +115,14 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
## FAQ
- Should I use Calibre?
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting.
Additionally, it will break page numbers.
Viewing KCC output in Calibre will also not work properly.
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended.
Direct USB dropping is reccomended.
- Blank pages?
- May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast.
- May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast. You can try PDF output.
Going back a few pages and exiting and re-entering book should fix it temporarily.
- What output format should I use?
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable.
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable or Kindle Scribe 2025.
- All options have additional information in tooltips if you hover over the option.
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
- Kindle panel view not working?
@@ -137,9 +136,6 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
(no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps.
- How to make AZW3 instead of MOBI?
- The `.mobi` file generated by KCC is a dual filetype, it's both MOBI and AZW3. The file extension is `.mobi` for compatibility reasons.
- [Windows 7 support](https://github.com/ciromattia/kcc/issues/678)
- Image too dark?
- The default gamma correction of 1.8 makes the image darker, and is useful for faded/gray artwork/text. Disable by setting gamma = 1.0
- Huge margins / slow page turns?
- You likely modified the file during transfer using a 3rd party app. Try simply dragging and dropping the final mobi/kepub file into the Kindle documents folder via USB.
@@ -198,8 +194,9 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
'KPW34': ("Kindle Paperwhite 3/4", (1072, 1448), Palette16, 1.0),
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0),
'KPW6': ("Kindle Paperwhite 6", (1272, 1696), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0),
'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0),
'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0),
'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0),
@@ -249,6 +246,7 @@ MAIN:
PROCESSING:
-n, --noprocessing Do not modify image and ignore any profile or processing option
--pdfextract Use legacy PDF image extraction method from KCC 8 and earlier.
--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
-r SPLITTER, --splitter SPLITTER
@@ -269,13 +267,19 @@ PROCESSING:
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
--blackborders Disable autodetection and force black borders
--whiteborders Disable autodetection and force white borders
--nosmartcovercrop Disable attempt to crop main cover from wide image
--coverfill Center-crop only the cover to fill target device screen
--forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG
--forcepng Create PNG files instead JPEG for black and white images
--webp Replace JPG with lossy WEBP and PNG with lossless WEBP
--force-png-rgb Force color images to be saved as PNG
--pnglegacy Use a more compatible 8 bit PNG instead of 4 bit.
--noquantize Don't quantize PNG images to 16 colors
--mozjpeg Create JPEG files using mozJpeg
--jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices.
--maximizestrips Turn 1x4 strips to 2x2 strips
-d, --delete Delete source file(s) or a directory. It's not recoverable.
--tempdir Create temporary files directory on source file drive.
OUTPUT SETTINGS:
-o OUTPUT, --output OUTPUT
@@ -292,6 +296,7 @@ OUTPUT SETTINGS:
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
--spreadshift Shift first page to opposite side in landscape for two page spread alignment
--norotate Do not rotate double page spreads in spread splitter option.
--rotateright Rotate double page spreads in opposite direction.
--rotatefirst Put rotated spread first in spread splitter option.
--filefusion Combines all input files into a single file.
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies

1470
gui/KCC.ui

File diff suppressed because it is too large Load Diff

View File

@@ -195,7 +195,7 @@ class VersionThread(QThread):
icon = 'bindle'
if category == 'kofi':
icon = 'kofi'
message = f"<b>{payload.get('name')}</b>"
message = f"{payload.get('name')}"
if payload.get('link'):
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
if payload.get('showDeadline'):
@@ -329,6 +329,10 @@ class WorkerThread(QThread):
options.noprocessing = True
if GUI.pdfExtractBox.isChecked():
options.pdfextract = True
if GUI.pdfWidthBox.isChecked():
options.pdfwidth = True
if GUI.noSmartCoverCropBox.isChecked():
options.nosmartcovercrop = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
@@ -337,6 +341,8 @@ class WorkerThread(QThread):
options.metadatatitle = 2
if GUI.deleteBox.isChecked():
options.delete = True
if GUI.tempDirBox.isChecked():
options.tempdir = True
if GUI.spreadShiftBox.isChecked():
options.spreadshift = True
if GUI.fileFusionBox.isChecked():
@@ -345,12 +351,22 @@ class WorkerThread(QThread):
options.filefusion = False
if GUI.noRotateBox.isChecked():
options.norotate = True
if GUI.rotateRightBox.isChecked():
options.rotateright = True
if GUI.rotateFirstBox.isChecked():
options.rotatefirst = True
if GUI.forcePngRgbBox.isChecked():
options.force_png_rgb = True
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
options.forcepng = True
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
options.mozjpeg = True
if GUI.webpBox.isChecked():
options.webp = True
if GUI.pngLegacyBox.isChecked():
options.pnglegacy = True
if GUI.noQuantizeBox.isChecked():
options.noquantize = True
if GUI.jpegQualityBox.isChecked():
options.jpegquality = GUI.jpegQualitySpinBox.value()
if GUI.currentMode > 2:
@@ -682,6 +698,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://ko-fi.com/eink_dude'))
def openHumble(self):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
def openYouTube(self):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
def openDiscord(self):
# noinspection PyCallByClass
QDesktopServices.openUrl(QUrl('https://discord.gg/um5JRKwmGT'))
def modeChange(self, mode):
if mode == 1:
self.currentMode = 1
@@ -816,6 +844,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if bad_format in current_format:
self.addMessage('Scribe PNG MOBI/EPUB has a lot of problems like blank pages/sections. Use JPG instead.', 'warning')
break
GUI.pngLegacyBox.setEnabled(True)
GUI.noQuantizeBox.setEnabled(True)
GUI.forcePngRgbBox.setEnabled(True)
else:
GUI.pngLegacyBox.setEnabled(False)
GUI.noQuantizeBox.setEnabled(False)
GUI.forcePngRgbBox.setEnabled(False)
def togglechunkSizeCheckBox(self, value):
GUI.chunkSizeWidget.setVisible(value)
@@ -883,6 +919,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if bad_format in current_format:
self.addMessage('Colorsoft MOBI/EPUB can have blank pages. Just go back a few pages, exit, and reenter book.', 'info')
break
elif profile['Label'] == 'KDX':
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
GUI.pngLegacyBox.setChecked(True)
if not profile['PVOptions']:
GUI.qualityBox.setChecked(False)
if str(GUI.deviceBox.currentText()) == 'Other':
@@ -909,7 +949,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
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():
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
def stripTags(self, html):
s = HTMLStripper()
@@ -1038,18 +1078,26 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
'pdfWidthBox': GUI.pdfWidthBox.checkState(),
'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(),
'coverFillBox': GUI.coverFillBox.checkState(),
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
'mozJpegBox': GUI.mozJpegBox.checkState(),
'forcePngRgbBox': GUI.forcePngRgbBox.checkState(),
'webpBox': GUI.webpBox.checkState(),
'pngLegacyBox': GUI.pngLegacyBox.checkState(),
'noQuantizeBox': GUI.noQuantizeBox.checkState(),
'jpegQualityBox': GUI.jpegQualityBox.checkState(),
'jpegQuality': GUI.jpegQualitySpinBox.value(),
'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(),
'deleteBox': GUI.deleteBox.checkState(),
'tempDirBox': GUI.tempDirBox.checkState(),
'spreadShiftBox': GUI.spreadShiftBox.checkState(),
'fileFusionBox': GUI.fileFusionBox.checkState(),
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
'noRotateBox': GUI.noRotateBox.checkState(),
'rotateRightBox': GUI.rotateRightBox.checkState(),
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
'maximizeStrips': GUI.maximizeStrips.checkState(),
'gammaSlider': float(self.gammaValue) * 100,
@@ -1172,7 +1220,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'convertButton', 'formatBox']:
getattr(GUI, element).setMinimumSize(QSize(0, 0))
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'gridLayout_6', 'horizontalLayout_2']:
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
if self.windowSize == '0x0':
MW.resize(500, 500)
@@ -1182,6 +1230,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
@@ -1211,10 +1260,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS',
},
"Kindle Scribe 3": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3',
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3',
},
"Kindle Scribe Colorsoft": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS',
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS',
},
"Kindle 11": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'K11',
@@ -1223,7 +1272,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW5',
},
"Kindle Paperwhite 12": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KO',
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW6',
},
"Kindle Colorsoft": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': True, 'Label': 'KCS',
@@ -1379,6 +1428,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.wikiButton.clicked.connect(self.openWiki)
GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble)
GUI.youtubeButton.clicked.connect(self.openYouTube)
GUI.discordButton.clicked.connect(self.openDiscord)
GUI.convertButton.clicked.connect(self.convertStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
__version__ = '9.5.0'
__version__ = '9.7.2'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'

View File

@@ -310,6 +310,15 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
for author in options.authors:
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
if not options.iskindle and options.series:
f.writelines(['<meta property="belongs-to-collection" id="c02">', hescape(options.series), "</meta>\n"])
f.writelines(['<meta refines="#c02" property="collection-type">', "series", "</meta>\n"])
if options.volume and options.number:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(f"{options.volume}.{options.number}"), "</meta>\n"])
elif options.volume:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.volume), "</meta>\n"])
elif options.number:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.number), "</meta>\n"])
f.write("<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n")
if cover:
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
@@ -353,6 +362,8 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
mt = 'image/png'
elif '.gif' == filename[1]:
mt = 'image/gif'
elif '.webp' == filename[1]:
mt = 'image/webp'
else:
mt = 'image/jpeg'
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
@@ -545,7 +556,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
f.close()
build_html_start = perf_counter()
if cover:
cover.save_to_epub(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
cover.save_to_folder(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
dot_clean(path)
options.covers.append((cover, options.uuid))
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
@@ -689,7 +700,6 @@ def imgFileProcessing(work):
workImg = image.ComicPageParser((dirpath, afile), opt)
for i in workImg.payload:
img = image.ComicPage(opt, *i)
is_color = (opt.forcecolor and img.color)
if opt.cropping == 2 and not opt.webtoon:
img.cropPageNumber(opt.croppingp, opt.croppingm)
if opt.cropping == 1 and not opt.webtoon:
@@ -699,18 +709,24 @@ def imgFileProcessing(work):
img.gammaCorrectImage()
if not img.colorOutput:
img.convertToGrayscale()
img.autocontrastImage()
img.resizeImage()
img.optimizeForDisplay(opt.eraserainbow, is_color)
img.optimizeForDisplay(opt.eraserainbow, img.colorOutput)
if is_color:
if img.colorOutput:
pass
elif opt.forcepng:
img.convertToGrayscale()
if opt.format != 'PDF':
if not opt.noquantize:
img.quantizeImage()
else:
img.convertToGrayscale()
if opt.format == 'PDF':
img.convertToGrayscale()
elif opt.profile == 'KDX' and opt.format == 'CBZ':
img.convertToGrayscale()
elif opt.pnglegacy:
img.convertToGrayscale()
output.append(img.saveToDir())
return output
except Exception:
@@ -740,7 +756,9 @@ def render_page(vector):
cpu = vector[1] # number of CPUs
filename = vector[2] # document filename
output_dir = vector[3]
target_height = vector[4]
target_width = vector[4]
target_height = vector[5]
pdf_width = vector[6]
with pymupdf.open(filename) as doc: # open the document
num_pages = doc.page_count # get number of pages
@@ -751,7 +769,10 @@ def render_page(vector):
for i in range(seg_from, seg_to): # work through our page segment
page = doc[i]
zoom = target_height / page.rect.height
if not pdf_width or page.rect.width > page.rect.height:
zoom = target_height / page.rect.height
else:
zoom = target_width / page.rect.width
mat = pymupdf.Matrix(zoom, zoom)
# TODO: decide colorspace earlier so later color check is cheaper.
# This is actually pretty hard when you have to deal with color vector text
@@ -816,7 +837,7 @@ def extract_page(vector):
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_height):
render = False
with pymupdf.open(filename) as doc:
for page in doc:
@@ -836,7 +857,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
cpu = cpu_count()
# make vectors of arguments for the processes
vectors = [(i, cpu, filename, output_dir, target_height) for i in range(cpu)]
vectors = [(i, cpu, filename, output_dir, target_width, target_height, options.pdfwidth) for i in range(cpu)]
print("Starting %i processes for '%s'." % (cpu, filename))
@@ -853,7 +874,8 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
def getWorkFolder(afile, workdir=None):
if not workdir:
workdir = mkdtemp('', 'KCC-')
# workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
if options.tempdir:
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
else:
fullPath = workdir
@@ -881,13 +903,15 @@ def getWorkFolder(afile, workdir=None):
if njpg == 0:
raise UserWarning("Failed to extract images from PDF file.")
return workdir
target_height = options.profileData[1][1]
target_width, target_height = options.profileData[1]
if options.cropping == 1:
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
target_height *= 1.2 #Account for possible margin at the top and bottom
target_width *= 1.2
elif options.cropping == 2:
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
target_height *= 1.25 #Account for possible margin at the top and bottom with page number
target_width *= 1.25
try:
mupdf_pdf_process_pages_parallel(afile, fullPath, target_height)
mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height)
except Exception as e:
rmtree(path, True)
raise UserWarning(f"Failed to extract images from PDF file. {e}")
@@ -978,6 +1002,9 @@ def getMetadata(path, originalpath):
options.comicinfo_chapters = []
options.summary = ''
titleSuffix = ''
options.volume = ''
options.number = ''
options.series = ''
if options.title == 'defaulttitle':
defaultTitle = True
if os.path.isdir(originalpath):
@@ -1006,8 +1033,10 @@ def getMetadata(path, originalpath):
options.title = xml.data['Series']
if xml.data['Volume']:
titleSuffix += ' Vol. ' + xml.data['Volume'].zfill(2)
options.volume = xml.data['Volume']
if xml.data['Number']:
titleSuffix += ' #' + xml.data['Number'].zfill(3)
options.number = xml.data['Number']
if options.metadatatitle == 1 and xml.data['Title']:
titleSuffix += ': ' + xml.data['Title']
options.title += titleSuffix
@@ -1025,6 +1054,8 @@ def getMetadata(path, originalpath):
options.comicinfo_chapters = xml.data['Bookmarks']
if xml.data['Summary']:
options.summary = xml.data['Summary']
if xml.data['Series']:
options.series = xml.data['Series']
os.remove(xmlPath)
if originalpath.lower().endswith('.pdf'):
@@ -1285,12 +1316,12 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w')
mimetypeFile.write('application/epub+zip')
mimetypeFile.close()
subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
subprocess_run([SEVENZIP, 'a', '-mx0', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
# crazy hack to ensure mimetype is first when using 7zip
if isepub:
subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir)
else:
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
zipOutput = ZipFile(zipfilename, 'w', ZIP_STORED)
if isepub:
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
for dirpath, _, filenames in os.walk(basedir):
@@ -1353,6 +1384,8 @@ def makeParser():
help="Shift first page to opposite side in landscape for spread alignment")
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
help="Do not rotate double page spreads in spread splitter option.")
output_options.add_argument("--rotateright", action="store_true", dest="rotateright", default=False,
help="Rotate double page spreads in opposite direction.")
output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False,
help="Put rotated 2 page spread first in spread splitter option.")
@@ -1360,6 +1393,10 @@ def makeParser():
help="Do not modify image and ignore any profile or processing option")
processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False,
help="Use the legacy PDF image extraction method from KCC 8 and earlier")
processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
help="Render vector PDFs to device width instead of height.")
processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False,
help="Disable attempt to crop main cover from wide image")
processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False,
help="Crop cover to fill screen")
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
@@ -1397,7 +1434,15 @@ def makeParser():
output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False,
help="Erase rainbow effect on color eink screen by attenuating interfering frequencies")
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG")
help="Create PNG files instead JPEG for black and white images")
processing_options.add_argument("--force-png-rgb", action="store_true", dest="force_png_rgb", default=False,
help="Force color images to be saved as PNG")
processing_options.add_argument("--webp", action="store_true", dest="webp", default=False,
help="Replace JPG with lossy WEBP and PNG with lossless WEBP")
processing_options.add_argument("--pnglegacy", action="store_true", dest="pnglegacy", default=False,
help="Use a more compatible 8 bit png instead of 4 bit")
processing_options.add_argument("--noquantize", action="store_true", dest="noquantize", default=False,
help="Don't quantize to 16 color PNG")
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
help="Create JPEG files using mozJpeg")
processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality",
@@ -1406,6 +1451,8 @@ def makeParser():
help="Turn 1x4 strips to 2x2 strips")
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
help="Delete source file(s) or a directory. It's not recoverable.")
processing_options.add_argument("--tempdir", action="store_true", dest="tempdir", default=False,
help="Create temporary files directory on source file drive.")
custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0,
help="Replace screen width provided by device profile")
@@ -1424,6 +1471,11 @@ def checkOptions(options):
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
if options.format == 'PDF-200MB':
options.targetsize = 195
options.format = 'PDF'
if options.batchsplit != 2:
options.batchsplit = 1
if options.format == 'EPUB-200MB':
options.targetsize = 195
options.format = 'EPUB'
@@ -1482,9 +1534,6 @@ def checkOptions(options):
if 'Ko' in options.profile:
options.panelview = False
options.hq = False
# CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
if options.format == 'KFX':
options.format = 'EPUB'
@@ -1510,6 +1559,15 @@ def checkOptions(options):
options.jpegquality = 85
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3
options.webp_output = options.format != 'PDF' and not options.kindle_azw3 and options.webp
# CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ':
options.profileData = list(image.ProfileData.Profiles[options.profile])
options.profileData[1] = list(options.profileData[1])
options.profileData[1][1] = 1200
if options.kindle_scribe_azw3:
options.profileData = list(image.ProfileData.Profiles[options.profile])
options.profileData[1] = list(options.profileData[1])
@@ -1657,12 +1715,16 @@ 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.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
elif options.format == 'PDF':
print(f"{job_progress}Creating PDF file with PyMuPDF...")
# 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.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)
filepath.append(output_pdf)

View File

@@ -98,14 +98,15 @@ class ProfileData:
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.0),
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.0),
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0),
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0),
'KPW6': ("Kindle Paperwhite 6", (1272, 1696), Palette16, 1.0),
'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0),
'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0),
'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0),
'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0),
'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0),
'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0),
}
@@ -193,14 +194,21 @@ class ComicPageParser:
new_image.paste(pageone, (0, 0))
new_image.paste(pagetwo, (0, height))
self.payload.append(['N', self.source, new_image, self.fill])
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
and not self.opt.webtoon and self.opt.splitter == 1:
elif self.opt.webtoon:
self.payload.append(['N', self.source, self.image, self.fill])
# rotate only TODO dead code?
elif (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth and self.opt.splitter == 1:
spread = self.image
if not self.opt.norotate:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
if not self.opt.rotateright:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
else:
spread = spread.rotate(-90, Image.Resampling.BICUBIC, True)
self.payload.append(['R', self.source, spread, self.fill])
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
if self.opt.splitter != 1:
# elif wide enough to split
elif (width > height) != (dstwidth > dstheight) and width / height > 1.16:
# if (split) or (split and rotate)
if self.opt.splitter != 1 and width / height < 1.75:
if width > height:
leftbox = (0, 0, int(width / 2), height)
rightbox = (int(width / 2), 0, width, height)
@@ -215,10 +223,15 @@ class ComicPageParser:
pagetwo = self.image.crop(rightbox)
self.payload.append(['S1', self.source, pageone, self.fill])
self.payload.append(['S2', self.source, pagetwo, self.fill])
if self.opt.splitter > 0:
# if (rotate) or (split and rotate)
if self.opt.splitter > 0 or (self.opt.splitter == 0 and width / height >= 1.75):
spread = self.image
if not self.opt.norotate:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
if not self.opt.rotateright:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
else:
spread = spread.rotate(-90, Image.Resampling.BICUBIC, True)
self.payload.append(['R', self.source, spread, self.fill])
else:
self.payload.append(['N', self.source, self.image, self.fill])
@@ -271,6 +284,8 @@ class ComicPage:
self.original_color_mode = image.mode
# TODO: color check earlier
self.image = image.convert("RGB")
self.color = self.colorCheck()
self.colorOutput = self.color and self.opt.forcecolor
self.fill = fill
self.rotated = False
self.orgPath = os.path.join(path[0], path[1])
@@ -289,8 +304,7 @@ class ComicPage:
if not hasattr(Image, 'Resampling'):
Image.Resampling = Image
@cached_property
def color(self):
def colorCheck(self):
if self.original_color_mode in ("L", "1"):
return False
if self.opt.webtoon:
@@ -399,17 +413,23 @@ class ComicPage:
raise RuntimeError('Cannot save image. ' + str(err))
def save_with_codec(self, image, targetPath):
if self.opt.forcepng:
if self.opt.forcepng and (not self.colorOutput or self.opt.force_png_rgb):
image.info.pop('transparency', None)
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
if self.opt.webp_output:
targetPath += '.webp'
image.save(targetPath, 'WEBP', lossless=True, quality=self.opt.jpegquality)
elif self.opt.kindle_azw3:
targetPath += '.gif'
image.save(targetPath, 'GIF', optimize=1, interlace=False)
else:
targetPath += '.png'
image.save(targetPath, 'PNG', optimize=1)
else:
targetPath += '.jpg'
if self.opt.mozjpeg:
if self.opt.webp_output:
targetPath += '.webp'
image.save(targetPath, 'WEBP', quality=self.opt.jpegquality)
elif self.opt.mozjpeg:
targetPath += '.jpg'
with io.BytesIO() as output:
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
input_jpeg_bytes = output.getvalue()
@@ -417,6 +437,7 @@ class ComicPage:
with open(targetPath, "wb") as output_jpeg_file:
output_jpeg_file.write(output_jpeg_bytes)
else:
targetPath += '.jpg'
image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
return targetPath
@@ -501,7 +522,9 @@ class ComicPage:
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
pass
else: # if image bigger than device resolution or smaller with upscaling
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
if self.opt.profile == 'KDX' and abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD * 3:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
@@ -509,7 +532,7 @@ class ComicPage:
self.image = ImageOps.contain(self.image, self.size, method=method)
def resize_method(self):
if self.image.size[0] < self.size[0] and self.image.size[1] < self.size[1]:
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
return Image.Resampling.BICUBIC
else:
return Image.Resampling.LANCZOS
@@ -553,6 +576,7 @@ class Cover:
self.options = opt
self.source = source
self.image = Image.open(source)
self.smartcover = False
# backwards compatibility for Pillow >9.1.0
if not hasattr(Image, 'Resampling'):
Image.Resampling = Image
@@ -563,7 +587,8 @@ class Cover:
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
if not self.options.forcecolor:
self.image = self.image.convert('L')
self.crop_main_cover()
if not self.options.nosmartcovercrop:
self.crop_main_cover()
size = list(self.options.profileData[1])
if self.options.kindle_scribe_azw3:
@@ -578,17 +603,37 @@ class Cover:
def crop_main_cover(self):
w, h = self.image.size
if w / h > 2:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
else:
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
elif w / h > 1.83:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((w * .19, 0, w * .575, h))
else:
self.image = self.image.crop((w * .425, 0, .81 * w, h))
elif w / h > 1.7:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((w * .2, 0, w * .583, h))
else:
self.image = self.image.crop((w * .417, 0, .8 * w, h))
elif w / h > 1.34:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((0, 0, w/2 - w * 0.03, h))
else:
self.image = self.image.crop((w/2 + w * 0.03, 0, w, h))
elif w / h > 1.0:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((w * .36, 0, w, h))
else:
self.image = self.image.crop((w, 0, .64 * w, h))
def save_to_epub(self, target, tomeid, len_tomes=0):
def save_to_folder(self, target, tomeid, len_tomes=0):
try:
if tomeid == 0:
self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)

View File

@@ -1,4 +1,4 @@
PySide6>6
PySide6<6.10
Pillow>=11.3.0
psutil>=5.9.5
requests>=2.31.0