1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-15 13:38:46 +00:00

Compare commits

...

43 Commits

Author SHA1 Message Date
dependabot[bot]
87acd1d7f7 Bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 01:42:51 +00:00
Alex Xu
b42f05686e bump to 9.5.0 2026-03-08 17:37:47 -07:00
Alex Xu
adf48d24f9 clarify coverfill is not implemented for kindle scribe (#1255) 2026-02-22 11:49:27 -08:00
tom
723fa4c0b8 Add exact cover fit option for device-sized cover cropping (#1254)
* Add exact cover fit option for device-sized cover cropping

* Update README to move cover cropping from FAQ to USAGE

* rename to coverfill

* edit readme
2026-02-22 11:34:10 -08:00
Alex Xu
2632d18e2c Update README.md 2026-02-16 15:15:09 -08:00
Alex Xu
94e4937566 ensure mimetype is first when using 7zip (#1251) 2026-02-15 13:01:43 -08:00
Alex Xu
f7ce1cf271 add demo video 2026-02-14 23:43:34 -08:00
フィルターペーパー
ab93c03838 Preserve file fusion input order with prefix (#1242)
* Preserve file fusion input order with prefix

Prepend a sequence index to temporary directory names to ensure user-specified order is preserved.

* don't sort items in GUI

* Add prefix only if sorted order is different

* Simplified sort prefix that will be removed from chapter name

* Restore adding prefix only when sorted order differs

* simplify prefix code

---------

Co-authored-by: Alex Xu <alexkurosakimh3@gmail.com>
2026-02-09 21:34:11 -08:00
Alex Xu
541b1d876b save jpeg quality value (#1248) 2026-02-09 19:08:26 -08:00
Alex Xu
d189f9909d don't overwrite mobi output with same name (#1246) 2026-02-09 11:30:59 -08:00
Alex Xu
1dce4f8d2c support latest pyside6 6.10.2 (#1245) 2026-02-08 13:43:43 -08:00
Sébastien CHEMIN
58aab0cb65 Lower minimum chunk size to 50 MB, Remarkable chunk size default of 100 MB (#1240)
* accept smaller chunks size on gui

* add default target size for Remarkable to 95

* remove rc changes
2026-02-06 09:46:42 -08:00
Alex Xu
d2dc089c62 add workers crashed restart pc message (#1239) 2026-02-03 09:59:51 -08:00
Alex Xu
3660f2370f add kindlegen error to GUI (#1237) 2026-02-03 08:10:29 -08:00
Alex Xu
87c6e3a35e Update README.md 2026-02-02 09:15:00 -08:00
Alex Xu
981c556550 add new tutorial 2026-02-02 09:14:41 -08:00
Alex Xu
123d603cbd clarify mac can't be opened 2026-02-01 10:10:19 -08:00
Alex Xu
a344dd73bf add OS support to beginning 2026-01-28 10:44:22 -08:00
Alex Xu
095694e9cf use bsdtar on linux (#1234) 2026-01-27 09:03:01 -08:00
Alex Xu
4b4860b976 Bump to 9.4.3 2026-01-26 14:45:28 -08:00
Alex Xu
56e8e24176 fix release notes (#1231) 2026-01-26 14:44:36 -08:00
Alex Xu
b0f8f1c633 fix file fusion bugs (#1230) 2026-01-26 14:39:05 -08:00
Alex Xu
38acc3bf05 skip blanks on image based pdfs (#1228) 2026-01-26 09:56:44 -08:00
Alex Xu
fbd5980b9b add Kindle 1240x1860 profile (#1227) 2026-01-26 09:41:49 -08:00
Alex Xu
667d702b8a Kindle Scribe 2025 warning 2026-01-25 13:45:15 -08:00
Alex Xu
9a4143ce62 Add legacy pdf image extract option (#1225) 2026-01-25 13:41:43 -08:00
Alex Xu
f63387cae4 remove corrupt image checking (#1221)
* remove corrupt image checking

Removed image verification step before copying the image.

* Update image.py
2026-01-20 20:48:18 -08:00
Alex Xu
f5fd2bb7fe fix cropping divide by zero (#1220) 2026-01-20 20:47:56 -08:00
Alex Xu
4baca03214 Bump version to 9.4.2 2026-01-11 13:19:41 -08:00
Alex Xu
7de212dca3 move fixed resolution Kindle profiles down 2026-01-10 20:40:09 -08:00
Carlos Lázaro Costa
c99444b96a docs: Update Profiles (#1218)
* Update README Profiles

Align the README Profiles section with the profile definitions
in image.py.

- Add missing Kindle Scribe 3, Colorsoft and reMarkable profiles
- Update gamma values to default (from 1.8 to 1.0)

* Update profile name KS1860

Fix typo (1920 -> 1860)
2026-01-10 16:53:40 -08:00
Alex Xu
6d7a635c3d Fix Kindle Scribe 2025 resolutions (#1217) 2026-01-09 13:46:16 -08:00
Alex Xu
be86bcbf6a Add macOS 15 unsigned instructions 2026-01-08 20:20:10 -08:00
Alex Xu
5cbc07e65d increase webtoon max height (#1213) 2026-01-08 18:21:45 -08:00
Alex Xu
42d94d8202 increase max Windows path length from 180 to 220 (#1211) 2026-01-08 18:21:32 -08:00
フィルターペーパー
7897627c43 Preserve input file order using list instead of set (#1209)
* Preserve input file order using list instead of set

* Simplify list comprehension and fix append for sources

* Remove redundant list() conversion
2026-01-05 20:52:53 -08:00
Alex Xu
8e42fc1162 remove padding from output folder GUI (#1207) 2026-01-01 22:55:54 -08:00
Alex Xu
d6b0e43d70 Bump version to 9.4.1 2026-01-01 22:26:23 -08:00
Alex Xu
af189ed265 add Kindle 1920 profiles (7.4 Scribe behavior) (#1206) 2026-01-01 22:26:04 -08:00
Alex Xu
aa5f4991dd Bump version to 9.4.0 2025-12-29 10:52:34 -08:00
jaroslawjanas
f9064ef0e4 configurable jpg quality (#1205) 2025-12-29 10:37:56 -08:00
Alex Xu
e14abe1787 default jpg quality of 90 for scribe colorsoft (#1204) 2025-12-28 14:54:12 -08:00
Alex Xu
c58387f4f4 partial support for Kindle Scribe 2025 models (#1203)
* partial Kindle Scribe 2025 support

* make variables better

* remove quad
2025-12-27 17:18:05 -08:00
18 changed files with 1157 additions and 807 deletions

View File

@@ -43,7 +43,7 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository_owner }}/kcc
# Always creates the "latest" tag

View File

@@ -68,7 +68,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
generate_release_notes: false
files: |
LICENSE.txt
*.AppImage*

View File

@@ -89,7 +89,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
generate_release_notes: false
files: |
dist/*.dmg
- name: Clean up keychain and provisioning profile

View File

@@ -60,7 +60,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
generate_release_notes: false
files: |
LICENSE.txt
dist/*.dmg

View File

@@ -73,6 +73,6 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
generate_release_notes: false
files: |
dist/*.exe

View File

@@ -55,6 +55,6 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
prerelease: true
generate_release_notes: true
generate_release_notes: false
files: |
dist/*.exe

View File

@@ -13,6 +13,13 @@ 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 output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
KCC runs on Windows, macOS, and Linux.
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
**WARNING**: Kindle Scribe 2025 support may not be possible. Does not work well currently.
**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
@@ -34,6 +41,7 @@ KCC avoids many common formatting issues (some of which occur [even on the Kindl
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
The GUI looks like this, built in Qt6, with my most commonly used settings:
@@ -46,7 +54,9 @@ You can change the default output directory by holding `Shift` while clicking th
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=IR2Fhcm9658
YouTube tutorial (please subscribe): https://www.youtube.com/watch?v=QQ6zJcMF2Iw
Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658
### 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.
@@ -98,7 +108,7 @@ There are also legacy macOS 10.14+ and Windows 7 experimental versions available
The `c2e` and `c2p` versions are command line tools for power users.
On Mac, right click open to get past the security warning.
On macOS, if you get a `can't be opened` error, follow: https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac
For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation
@@ -177,38 +187,44 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
### Profiles:
```
'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.8),
'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.8),
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.8),
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.8),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
'KV': ("Kindle Voyage, (1072, 1448), Palette16, 1.8),
'KPW34': ("Kindle Paperwhite 3/4/Oasis", (1072, 1448), Palette16, 1.8),
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.8),
'KO': ("Kindle Oasis 2/3/Paperwhite 12/Colorsoft 12", (1264, 1680), Palette16, 1.8),
'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.8),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.8),
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.8),
'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.8),
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.8),
'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.8),
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8),
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.8),
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.8),
'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.8),
'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.8),
'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.8),
'OTHER': ("Other", (0, 0), Palette16, 1.8),
'K1': ("Kindle 1", (600, 670), Palette4, 1.0),
'K2': ("Kindle 2", (600, 670), Palette15, 1.0),
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0),
'K34': ("Kindle Keyboard/Touch", (600, 800), Palette16, 1.0),
'K57': ("Kindle 5/7", (600, 800), Palette16, 1.0),
'K810': ("Kindle 8/10", (600, 800), Palette16, 1.0),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.0),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.0),
'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),
'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),
'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0),
'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.0),
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.0),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.0),
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.0),
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.0),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.0),
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.0),
'KoN': ("Kobo Nia", (758, 1024), Palette16, 1.0),
'KoC': ("Kobo Clara HD/Kobo Clara 2E", (1072, 1448), Palette16, 1.0),
'KoCC': ("Kobo Clara Colour", (1072, 1448), Palette16, 1.0),
'KoL': ("Kobo Libra H2O/Kobo Libra 2", (1264, 1680), Palette16, 1.0),
'KoLC': ("Kobo Libra Colour", (1264, 1680), Palette16, 1.0),
'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.0),
'KoS': ("Kobo Sage", (1440, 1920), Palette16, 1.0),
'KoE': ("Kobo Elipsa", (1404, 1872), Palette16, 1.0),
'Rmk1': ("reMarkable 1", (1404, 1872), Palette16, 1.0),
'Rmk2': ("reMarkable 2", (1404, 1872), Palette16, 1.0),
'RmkPP': ("reMarkable Paper Pro", (1620, 2160), Palette16, 1.0),
'RmkPPMove': ("reMarkable Paper Pro Move", (954, 1696), Palette16, 1.0),
'OTHER': ("Other", (0, 0), Palette16, 1.0),
```
### Standalone `kcc-c2e.py` usage:
@@ -232,6 +248,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.
-u, --upscale Resize images smaller than device's resolution
-s, --stretch Stretch images to device's resolution
-r SPLITTER, --splitter SPLITTER
@@ -252,9 +269,11 @@ 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
--coverfill Center-crop only the cover to fill target device screen
--forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG
--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.

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@ from raven import Client
from tempfile import gettempdir
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
from .comicarchive import SEVENZIP, available_archive_tools
from .comicarchive import SEVENZIP, TAR, available_archive_tools
from . import __version__
from . import comic2ebook
from . import metadata
@@ -327,6 +327,10 @@ class WorkerThread(QThread):
options.maximizestrips = True
if GUI.disableProcessingBox.isChecked():
options.noprocessing = True
if GUI.pdfExtractBox.isChecked():
options.pdfextract = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.metadatatitle = 1
elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked:
@@ -347,6 +351,8 @@ class WorkerThread(QThread):
options.forcepng = True
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
options.mozjpeg = True
if GUI.jpegQualityBox.isChecked():
options.jpegquality = GUI.jpegQualitySpinBox.value()
if GUI.currentMode > 2:
options.customwidth = str(GUI.widthBox.value())
options.customheight = str(GUI.heightBox.value())
@@ -519,6 +525,7 @@ class WorkerThread(QThread):
if os.path.exists(item.replace('.epub', '.mobi')):
os.remove(item.replace('.epub', '.mobi'))
MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False)
MW.addMessage.emit(self.kindlegenErrorCode[1], 'error', False)
MW.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical')
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
MW.showDialog.emit("KindleGen error:\n\n" + self.kindlegenErrorCode[1], 'error')
@@ -742,6 +749,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.croppingWidget.setVisible(False)
self.changeCroppingPower(100) # 1.0
def togglejpegqualityBox(self, value):
if value:
GUI.jpegQualityWidget.setVisible(True)
else:
GUI.jpegQualityWidget.setVisible(False)
def togglewebtoonBox(self, value):
if value:
self.addMessage('You can choose a taller device profile to get taller cuts in webtoon mode.', 'info')
@@ -772,7 +785,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.rotateBox.setEnabled(True)
GUI.borderBox.setEnabled(True)
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if profile['Label'] != 'KS':
if not profile['Label'].startswith('KS'):
GUI.upscaleBox.setEnabled(True)
GUI.croppingBox.setEnabled(True)
GUI.interPanelCropBox.setEnabled(True)
@@ -797,7 +810,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def toggleImageFormatBox(self, value):
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if value == 1:
if profile['Label'] == 'KS':
if profile['Label'].startswith('KS'):
current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
for bad_format in ('MOBI', 'EPUB'):
if bad_format in current_format:
@@ -859,7 +872,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
if profile['Label'] == 'KS':
if profile['Label'].startswith('KS'):
GUI.upscaleBox.setDisabled(True)
else:
if not GUI.webtoonBox.isChecked():
@@ -1024,8 +1037,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'colorBox': GUI.colorBox.checkState(),
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
'coverFillBox': GUI.coverFillBox.checkState(),
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
'mozJpegBox': GUI.mozJpegBox.checkState(),
'jpegQualityBox': GUI.jpegQualityBox.checkState(),
'jpegQuality': GUI.jpegQualitySpinBox.value(),
'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(),
'deleteBox': GUI.deleteBox.checkState(),
@@ -1077,7 +1094,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if message[-1] == '/':
message = message[:-1]
self.handleMessage(message)
GUI.jobList.sortItems()
# sorting may conflict with manual file fusion order
# GUI.jobList.sortItems()
def forceShutdown(self):
self.saveSettings(None)
@@ -1180,9 +1198,24 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KPW34'},
"Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'ForceColor': False, 'Label': 'KV'},
"Kindle Scribe": {
"Kindle 1860x1920": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1860',
},
"Kindle 1920x1920": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1920',
},
"Kindle 1240x1860": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1240',
},
"Kindle Scribe 1/2": {
'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',
},
"Kindle Scribe Colorsoft": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': True, 'Label': 'KSCS',
},
"Kindle 11": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'ForceColor': False, 'Label': 'K11',
},
@@ -1257,9 +1290,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'Label': 'OTHER'},
}
profilesGUI = [
"Kindle Scribe Colorsoft",
"Kindle Scribe 3",
"Kindle Colorsoft",
"Kindle Paperwhite 12",
"Kindle Scribe",
"Kindle Scribe 1/2",
"Kindle Paperwhite 11",
"Kindle 11",
"Kindle Oasis 9/10",
@@ -1279,6 +1314,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Separator",
"Other",
"Separator",
"Kindle 1920x1920",
"Kindle 1860x1920",
"Kindle 1240x1860",
"Kindle 8/10",
"Kindle Oasis 8",
"Kindle Paperwhite 7/10",
@@ -1326,7 +1364,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
'info')
self.tar = 'tar' in available_archive_tools()
self.tar = TAR in available_archive_tools()
self.sevenzip = SEVENZIP in available_archive_tools()
if not any([self.tar, self.sevenzip]):
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
@@ -1346,6 +1384,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
GUI.croppingBox.stateChanged.connect(self.togglecroppingBox)
GUI.croppingPowerSlider.valueChanged.connect(self.changeCroppingPower)
GUI.jpegQualityBox.stateChanged.connect(self.togglejpegqualityBox)
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
GUI.mozJpegBox.stateChanged.connect(self.toggleImageFormatBox)
@@ -1407,6 +1446,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.croppingPowerSlider.setValue(int(self.options[option]))
self.changeCroppingPower(int(self.options[option]))
GUI.preserveMarginBox.setValue(self.options.get('preserveMarginBox', 0))
elif str(option) == "jpegQuality":
GUI.jpegQualitySpinBox.setValue(int(self.options[option]))
elif str(option) == "chunkSizeBox":
GUI.chunkSizeBox.setValue(int(self.options[option]))
else:

View File

@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
def setupUi(self, mainWindow):
if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow")
mainWindow.resize(566, 581)
mainWindow.resize(566, 658)
icon = QIcon()
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
mainWindow.setWindowIcon(icon)
@@ -35,6 +35,253 @@ class Ui_mainWindow(object):
self.gridLayout = QGridLayout(self.centralWidget)
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
self.progressBar = QProgressBar(self.centralWidget)
self.progressBar.setObjectName(u"progressBar")
self.progressBar.setMinimumSize(QSize(0, 30))
font = QFont()
font.setBold(True)
self.progressBar.setFont(font)
self.progressBar.setVisible(False)
self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
self.toolWidget = QWidget(self.centralWidget)
self.toolWidget.setObjectName(u"toolWidget")
self.horizontalLayout = QHBoxLayout(self.toolWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.editorButton = QPushButton(self.toolWidget)
self.editorButton.setObjectName(u"editorButton")
self.editorButton.setMinimumSize(QSize(0, 30))
icon1 = QIcon()
icon1.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.editorButton.setIcon(icon1)
self.horizontalLayout.addWidget(self.editorButton)
self.kofiButton = QPushButton(self.toolWidget)
self.kofiButton.setObjectName(u"kofiButton")
self.kofiButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon()
icon2.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.kofiButton.setIcon(icon2)
self.kofiButton.setIconSize(QSize(19, 16))
self.horizontalLayout.addWidget(self.kofiButton)
self.wikiButton = QPushButton(self.toolWidget)
self.wikiButton.setObjectName(u"wikiButton")
self.wikiButton.setMinimumSize(QSize(0, 30))
icon3 = QIcon()
icon3.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.wikiButton.setIcon(icon3)
self.horizontalLayout.addWidget(self.wikiButton)
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
self.croppingWidget = QWidget(self.centralWidget)
self.croppingWidget.setObjectName(u"croppingWidget")
self.croppingWidget.setVisible(False)
self.gridLayout_5 = QGridLayout(self.croppingWidget)
self.gridLayout_5.setObjectName(u"gridLayout_5")
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
self.croppingPowerSlider = QSlider(self.croppingWidget)
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
self.croppingPowerSlider.setMaximum(300)
self.croppingPowerSlider.setSingleStep(1)
self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1)
self.preserveMarginBox = QSpinBox(self.croppingWidget)
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
self.preserveMarginBox.setSizePolicy(sizePolicy)
self.preserveMarginBox.setMaximum(99)
self.preserveMarginBox.setSingleStep(5)
self.preserveMarginBox.setValue(0)
self.gridLayout_5.addWidget(self.preserveMarginBox, 1, 1, 1, 1)
self.preserveMarginLabel = QLabel(self.croppingWidget)
self.preserveMarginLabel.setObjectName(u"preserveMarginLabel")
self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1)
self.croppingPowerLabel = QLabel(self.croppingWidget)
self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1)
self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2)
self.customWidget = QWidget(self.centralWidget)
self.customWidget.setObjectName(u"customWidget")
self.customWidget.setVisible(False)
self.gridLayout_3 = QGridLayout(self.customWidget)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.hLabel = QLabel(self.customWidget)
self.hLabel.setObjectName(u"hLabel")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
self.hLabel.setSizePolicy(sizePolicy1)
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
self.widthBox = QSpinBox(self.customWidget)
self.widthBox.setObjectName(u"widthBox")
self.widthBox.setMaximum(6000)
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
self.wLabel = QLabel(self.customWidget)
self.wLabel.setObjectName(u"wLabel")
sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
self.wLabel.setSizePolicy(sizePolicy1)
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
self.heightBox = QSpinBox(self.customWidget)
self.heightBox.setObjectName(u"heightBox")
self.heightBox.setMaximum(8000)
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2)
self.buttonWidget = QWidget(self.centralWidget)
self.buttonWidget.setObjectName(u"buttonWidget")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
self.buttonWidget.setSizePolicy(sizePolicy2)
self.gridLayout_4 = QGridLayout(self.buttonWidget)
self.gridLayout_4.setObjectName(u"gridLayout_4")
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.convertButton = QPushButton(self.buttonWidget)
self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30))
self.convertButton.setFont(font)
icon4 = QIcon()
icon4.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.convertButton.setIcon(icon4)
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
self.clearButton = QPushButton(self.buttonWidget)
self.clearButton.setObjectName(u"clearButton")
self.clearButton.setMinimumSize(QSize(0, 30))
icon5 = QIcon()
icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.clearButton.setIcon(icon5)
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
self.deviceBox = QComboBox(self.buttonWidget)
self.deviceBox.setObjectName(u"deviceBox")
self.deviceBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1)
self.fileButton = QPushButton(self.buttonWidget)
self.fileButton.setObjectName(u"fileButton")
self.fileButton.setMinimumSize(QSize(0, 30))
icon6 = QIcon()
icon6.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.fileButton.setIcon(icon6)
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
self.directoryButton = QPushButton(self.buttonWidget)
self.directoryButton.setObjectName(u"directoryButton")
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth())
self.directoryButton.setSizePolicy(sizePolicy3)
icon7 = QIcon()
icon7.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.directoryButton.setIcon(icon7)
self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1)
self.formatBox = QComboBox(self.buttonWidget)
self.formatBox.setObjectName(u"formatBox")
self.formatBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1)
self.clearButton.raise_()
self.deviceBox.raise_()
self.convertButton.raise_()
self.fileButton.raise_()
self.directoryButton.raise_()
self.formatBox.raise_()
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
self.jobList = QListWidget(self.centralWidget)
self.jobList.setObjectName(u"jobList")
self.jobList.setMinimumSize(QSize(0, 150))
self.jobList.setStyleSheet(u"")
self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
self.chunkSizeWidget = QWidget(self.centralWidget)
self.chunkSizeWidget.setObjectName(u"chunkSizeWidget")
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
sizePolicy4.setHorizontalStretch(0)
sizePolicy4.setVerticalStretch(0)
sizePolicy4.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth())
self.chunkSizeWidget.setSizePolicy(sizePolicy4)
self.chunkSizeWidget.setVisible(False)
self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget)
self.horizontalLayout_4.setSpacing(0)
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
sizePolicy5.setHorizontalStretch(0)
sizePolicy5.setVerticalStretch(0)
sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeLabel.setSizePolicy(sizePolicy5)
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
self.chunkSizeBox = QSpinBox(self.chunkSizeWidget)
self.chunkSizeBox.setObjectName(u"chunkSizeBox")
self.chunkSizeBox.setMinimum(50)
self.chunkSizeBox.setMaximum(600)
self.chunkSizeBox.setValue(400)
self.horizontalLayout_4.addWidget(self.chunkSizeBox)
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5)
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
self.optionWidget = QWidget(self.centralWidget)
self.optionWidget.setObjectName(u"optionWidget")
self.gridLayout_2 = QGridLayout(self.optionWidget)
@@ -42,11 +289,8 @@ class Ui_mainWindow(object):
self.gridLayout_2.setContentsMargins(0, 0, 0, 0)
self.titleEdit = QLineEdit(self.optionWidget)
self.titleEdit.setObjectName(u"titleEdit")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth())
self.titleEdit.setSizePolicy(sizePolicy)
sizePolicy4.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth())
self.titleEdit.setSizePolicy(sizePolicy4)
self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
self.titleEdit.setClearButtonEnabled(False)
@@ -148,8 +392,8 @@ class Ui_mainWindow(object):
self.authorEdit = QLineEdit(self.optionWidget)
self.authorEdit.setObjectName(u"authorEdit")
sizePolicy.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
self.authorEdit.setSizePolicy(sizePolicy)
sizePolicy4.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
self.authorEdit.setSizePolicy(sizePolicy4)
self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
self.authorEdit.setClearButtonEnabled(False)
@@ -194,13 +438,11 @@ class Ui_mainWindow(object):
self.outputFolderWidget.setObjectName(u"outputFolderWidget")
self.horizontalLayout_3 = QHBoxLayout(self.outputFolderWidget)
self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.defaultOutputFolderBox = QCheckBox(self.outputFolderWidget)
self.defaultOutputFolderBox.setObjectName(u"defaultOutputFolderBox")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
self.defaultOutputFolderBox.setSizePolicy(sizePolicy1)
sizePolicy.setHeightForWidth(self.defaultOutputFolderBox.sizePolicy().hasHeightForWidth())
self.defaultOutputFolderBox.setSizePolicy(sizePolicy)
self.defaultOutputFolderBox.setTristate(True)
self.horizontalLayout_3.addWidget(self.defaultOutputFolderBox)
@@ -208,66 +450,31 @@ class Ui_mainWindow(object):
self.defaultOutputFolderButton = QPushButton(self.outputFolderWidget)
self.defaultOutputFolderButton.setObjectName(u"defaultOutputFolderButton")
self.defaultOutputFolderButton.setMinimumSize(QSize(0, 30))
icon1 = QIcon()
icon1.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.defaultOutputFolderButton.setIcon(icon1)
self.defaultOutputFolderButton.setIcon(icon7)
self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton)
self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1)
self.jpegQualityBox = QCheckBox(self.optionWidget)
self.jpegQualityBox.setObjectName(u"jpegQualityBox")
self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1)
self.pdfExtractBox = QCheckBox(self.optionWidget)
self.pdfExtractBox.setObjectName(u"pdfExtractBox")
self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1)
self.coverFillBox = QCheckBox(self.optionWidget)
self.coverFillBox.setObjectName(u"coverFillBox")
self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1)
self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
self.jobList = QListWidget(self.centralWidget)
self.jobList.setObjectName(u"jobList")
self.jobList.setMinimumSize(QSize(0, 150))
self.jobList.setStyleSheet(u"")
self.jobList.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.jobList.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.jobList.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
self.customWidget = QWidget(self.centralWidget)
self.customWidget.setObjectName(u"customWidget")
self.customWidget.setVisible(False)
self.gridLayout_3 = QGridLayout(self.customWidget)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.gridLayout_3.setContentsMargins(0, 0, 0, 0)
self.hLabel = QLabel(self.customWidget)
self.hLabel.setObjectName(u"hLabel")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
self.hLabel.setSizePolicy(sizePolicy2)
self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
self.widthBox = QSpinBox(self.customWidget)
self.widthBox.setObjectName(u"widthBox")
self.widthBox.setMaximum(6000)
self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
self.wLabel = QLabel(self.customWidget)
self.wLabel.setObjectName(u"wLabel")
sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
self.wLabel.setSizePolicy(sizePolicy2)
self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
self.heightBox = QSpinBox(self.customWidget)
self.heightBox.setObjectName(u"heightBox")
self.heightBox.setMaximum(8000)
self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2)
self.gammaWidget = QWidget(self.centralWidget)
self.gammaWidget.setObjectName(u"gammaWidget")
self.gammaWidget.setVisible(False)
@@ -290,196 +497,29 @@ class Ui_mainWindow(object):
self.gridLayout.addWidget(self.gammaWidget, 7, 0, 1, 2)
self.toolWidget = QWidget(self.centralWidget)
self.toolWidget.setObjectName(u"toolWidget")
self.horizontalLayout = QHBoxLayout(self.toolWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.editorButton = QPushButton(self.toolWidget)
self.editorButton.setObjectName(u"editorButton")
self.editorButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon()
icon2.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.editorButton.setIcon(icon2)
self.jpegQualityWidget = QWidget(self.centralWidget)
self.jpegQualityWidget.setObjectName(u"jpegQualityWidget")
sizePolicy1.setHeightForWidth(self.jpegQualityWidget.sizePolicy().hasHeightForWidth())
self.jpegQualityWidget.setSizePolicy(sizePolicy1)
self.jpegQualityWidget.setVisible(False)
self.horizontalLayout_12 = QHBoxLayout(self.jpegQualityWidget)
self.horizontalLayout_12.setObjectName(u"horizontalLayout_12")
self.horizontalLayout_12.setContentsMargins(0, 0, 0, 0)
self.jpegQualityLabel = QLabel(self.jpegQualityWidget)
self.jpegQualityLabel.setObjectName(u"jpegQualityLabel")
self.horizontalLayout.addWidget(self.editorButton)
self.horizontalLayout_12.addWidget(self.jpegQualityLabel)
self.kofiButton = QPushButton(self.toolWidget)
self.kofiButton.setObjectName(u"kofiButton")
self.kofiButton.setMinimumSize(QSize(0, 30))
icon3 = QIcon()
icon3.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.kofiButton.setIcon(icon3)
self.kofiButton.setIconSize(QSize(19, 16))
self.jpegQualitySpinBox = QSpinBox(self.jpegQualityWidget)
self.jpegQualitySpinBox.setObjectName(u"jpegQualitySpinBox")
self.jpegQualitySpinBox.setMaximum(95)
self.jpegQualitySpinBox.setSingleStep(5)
self.jpegQualitySpinBox.setValue(85)
self.horizontalLayout.addWidget(self.kofiButton)
self.wikiButton = QPushButton(self.toolWidget)
self.wikiButton.setObjectName(u"wikiButton")
self.wikiButton.setMinimumSize(QSize(0, 30))
icon4 = QIcon()
icon4.addFile(u":/Other/icons/wiki.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.wikiButton.setIcon(icon4)
self.horizontalLayout.addWidget(self.wikiButton)
self.horizontalLayout_12.addWidget(self.jpegQualitySpinBox)
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
self.progressBar = QProgressBar(self.centralWidget)
self.progressBar.setObjectName(u"progressBar")
self.progressBar.setMinimumSize(QSize(0, 30))
font = QFont()
font.setBold(True)
self.progressBar.setFont(font)
self.progressBar.setVisible(False)
self.progressBar.setAlignment(Qt.AlignmentFlag.AlignJustify|Qt.AlignmentFlag.AlignVCenter)
self.gridLayout.addWidget(self.progressBar, 1, 0, 1, 2)
self.croppingWidget = QWidget(self.centralWidget)
self.croppingWidget.setObjectName(u"croppingWidget")
self.croppingWidget.setVisible(False)
self.gridLayout_5 = QGridLayout(self.croppingWidget)
self.gridLayout_5.setObjectName(u"gridLayout_5")
self.gridLayout_5.setContentsMargins(0, 0, 0, 0)
self.preserveMarginLabel = QLabel(self.croppingWidget)
self.preserveMarginLabel.setObjectName(u"preserveMarginLabel")
self.gridLayout_5.addWidget(self.preserveMarginLabel, 1, 0, 1, 1)
self.croppingPowerLabel = QLabel(self.croppingWidget)
self.croppingPowerLabel.setObjectName(u"croppingPowerLabel")
self.gridLayout_5.addWidget(self.croppingPowerLabel, 0, 0, 1, 1)
self.croppingPowerSlider = QSlider(self.croppingWidget)
self.croppingPowerSlider.setObjectName(u"croppingPowerSlider")
self.croppingPowerSlider.setMaximum(300)
self.croppingPowerSlider.setSingleStep(1)
self.croppingPowerSlider.setOrientation(Qt.Orientation.Horizontal)
self.gridLayout_5.addWidget(self.croppingPowerSlider, 0, 1, 1, 1)
self.preserveMarginBox = QSpinBox(self.croppingWidget)
self.preserveMarginBox.setObjectName(u"preserveMarginBox")
sizePolicy1.setHeightForWidth(self.preserveMarginBox.sizePolicy().hasHeightForWidth())
self.preserveMarginBox.setSizePolicy(sizePolicy1)
self.preserveMarginBox.setMaximum(99)
self.preserveMarginBox.setSingleStep(5)
self.preserveMarginBox.setValue(0)
self.gridLayout_5.addWidget(self.preserveMarginBox, 1, 1, 1, 1)
self.gridLayout.addWidget(self.croppingWidget, 9, 0, 1, 2)
self.buttonWidget = QWidget(self.centralWidget)
self.buttonWidget.setObjectName(u"buttonWidget")
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
self.buttonWidget.setSizePolicy(sizePolicy3)
self.gridLayout_4 = QGridLayout(self.buttonWidget)
self.gridLayout_4.setObjectName(u"gridLayout_4")
self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.convertButton = QPushButton(self.buttonWidget)
self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30))
self.convertButton.setFont(font)
icon5 = QIcon()
icon5.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.convertButton.setIcon(icon5)
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
self.clearButton = QPushButton(self.buttonWidget)
self.clearButton.setObjectName(u"clearButton")
self.clearButton.setMinimumSize(QSize(0, 30))
icon6 = QIcon()
icon6.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.clearButton.setIcon(icon6)
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
self.deviceBox = QComboBox(self.buttonWidget)
self.deviceBox.setObjectName(u"deviceBox")
self.deviceBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1)
self.fileButton = QPushButton(self.buttonWidget)
self.fileButton.setObjectName(u"fileButton")
self.fileButton.setMinimumSize(QSize(0, 30))
icon7 = QIcon()
icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.fileButton.setIcon(icon7)
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
self.directoryButton = QPushButton(self.buttonWidget)
self.directoryButton.setObjectName(u"directoryButton")
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
sizePolicy4.setHorizontalStretch(0)
sizePolicy4.setVerticalStretch(0)
sizePolicy4.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth())
self.directoryButton.setSizePolicy(sizePolicy4)
self.directoryButton.setIcon(icon1)
self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1)
self.formatBox = QComboBox(self.buttonWidget)
self.formatBox.setObjectName(u"formatBox")
self.formatBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.formatBox, 1, 4, 1, 1)
self.clearButton.raise_()
self.deviceBox.raise_()
self.convertButton.raise_()
self.fileButton.raise_()
self.directoryButton.raise_()
self.formatBox.raise_()
self.gridLayout.addWidget(self.buttonWidget, 3, 0, 1, 2)
self.chunkSizeWidget = QWidget(self.centralWidget)
self.chunkSizeWidget.setObjectName(u"chunkSizeWidget")
sizePolicy.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth())
self.chunkSizeWidget.setSizePolicy(sizePolicy)
self.chunkSizeWidget.setVisible(False)
self.horizontalLayout_4 = QHBoxLayout(self.chunkSizeWidget)
self.horizontalLayout_4.setSpacing(0)
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.chunkSizeLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeLabel.setObjectName(u"chunkSizeLabel")
sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
sizePolicy5.setHorizontalStretch(0)
sizePolicy5.setVerticalStretch(0)
sizePolicy5.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeLabel.setSizePolicy(sizePolicy5)
self.horizontalLayout_4.addWidget(self.chunkSizeLabel)
self.chunkSizeBox = QSpinBox(self.chunkSizeWidget)
self.chunkSizeBox.setObjectName(u"chunkSizeBox")
self.chunkSizeBox.setMinimum(100)
self.chunkSizeBox.setMaximum(600)
self.chunkSizeBox.setValue(400)
self.horizontalLayout_4.addWidget(self.chunkSizeBox)
self.chunkSizeWarnLabel = QLabel(self.chunkSizeWidget)
self.chunkSizeWarnLabel.setObjectName(u"chunkSizeWarnLabel")
sizePolicy5.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
self.chunkSizeWarnLabel.setSizePolicy(sizePolicy5)
self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
self.gridLayout.addWidget(self.jpegQualityWidget, 10, 0, 1, 1)
mainWindow.setCentralWidget(self.centralWidget)
self.statusBar = QStatusBar(mainWindow)
@@ -514,7 +554,8 @@ class Ui_mainWindow(object):
QWidget.setTabOrder(self.fileFusionBox, self.noRotateBox)
QWidget.setTabOrder(self.noRotateBox, self.interPanelCropBox)
QWidget.setTabOrder(self.interPanelCropBox, self.metadataTitleBox)
QWidget.setTabOrder(self.metadataTitleBox, self.chunkSizeCheckBox)
QWidget.setTabOrder(self.metadataTitleBox, self.coverFillBox)
QWidget.setTabOrder(self.coverFillBox, self.chunkSizeCheckBox)
QWidget.setTabOrder(self.chunkSizeCheckBox, self.chunkSizeBox)
QWidget.setTabOrder(self.chunkSizeBox, self.eraseRainbowBox)
QWidget.setTabOrder(self.eraseRainbowBox, self.rotateFirstBox)
@@ -531,6 +572,58 @@ class Ui_mainWindow(object):
def retranslateUi(self, mainWindow):
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", 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.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None))
#if QT_CONFIG(tooltip)
self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>After calculating the cropping boundaries, &quot;back up&quot; a specified percentage amount.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None))
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
#if QT_CONFIG(tooltip)
self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None))
#if QT_CONFIG(tooltip)
self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None))
#if QT_CONFIG(tooltip)
self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None))
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
#if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None))
#if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None))
#if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#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))
#endif // QT_CONFIG(tooltip)
self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None))
self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None))
#if QT_CONFIG(tooltip)
self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Default Title</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
@@ -644,57 +737,25 @@ class Ui_mainWindow(object):
#endif // QT_CONFIG(tooltip)
self.defaultOutputFolderButton.setText("")
#if QT_CONFIG(tooltip)
self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>", None))
self.jpegQualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"The JPEG quality, on a scale from 0 (worst) to 95 (best). \n"
"\n"
"Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90.\n"
"\n"
"Higher values are larger and higher quality, and may resolve blank page issues.", None))
#endif // QT_CONFIG(tooltip)
self.jpegQualityBox.setText(QCoreApplication.translate("mainWindow", u"Custom JPEG Quality", None))
#if QT_CONFIG(tooltip)
self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
self.pdfExtractBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use the PDF image extraction method from KCC 8 and earlier.\n"
"\n"
"Useful for really weird PDFs.", None))
#endif // QT_CONFIG(tooltip)
self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None))
self.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None))
#if QT_CONFIG(tooltip)
self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.wLabel.setText(QCoreApplication.translate("mainWindow", u"Custom width:", None))
#if QT_CONFIG(tooltip)
self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
self.coverFillBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resize cover to exact device resolution by center-cropping to aspect ratio first.\n"
"May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.", None))
#endif // QT_CONFIG(tooltip)
self.coverFillBox.setText(QCoreApplication.translate("mainWindow", u"Cover Fill", None))
self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", 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.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
self.wikiButton.setText(QCoreApplication.translate("mainWindow", u"Wiki", None))
#if QT_CONFIG(tooltip)
self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>After calculating the cropping boundaries, &quot;back up&quot; a specified percentage amount.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None))
self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
#if QT_CONFIG(tooltip)
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None))
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
#if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None))
#if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None))
#if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#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))
#endif // QT_CONFIG(tooltip)
self.chunkSizeLabel.setText(QCoreApplication.translate("mainWindow", u"Chunk size MB:", None))
self.chunkSizeWarnLabel.setText(QCoreApplication.translate("mainWindow", u"Greater than default may cause performance issues on older ereaders.", None))
self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None))
# retranslateUi

View File

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

View File

@@ -48,6 +48,7 @@ from .comicarchive import SEVENZIP, available_archive_tools
from . import comic2panel
from . import image
from . import comicarchive
from . import pdfjpgextract
from . import dualmetafix
from . import metadata
from . import kindle
@@ -65,16 +66,16 @@ def main(argv=None):
parser.print_help()
return 0
if sys.platform.startswith('win'):
sources = set([source for option in options.input for source in glob(escape(option))])
sources = [source for option in options.input for source in glob(escape(option))]
else:
sources = set(options.input)
sources = options.input
if len(sources) == 0:
print('No matching files found.')
return 1
if options.filefusion:
fusion_path = makeFusion(list(sources))
sources.clear()
sources.add(fusion_path)
sources.append(fusion_path)
for source in sources:
source = source.rstrip('\\').rstrip('/')
options = copy(args)
@@ -659,7 +660,7 @@ def imgDirectoryProcessing(path, job_progress=''):
raise UserWarning("Conversion interrupted.")
if len(workerOutput) > 0:
rmtree(os.path.join(path, '..', '..'), True)
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
raise RuntimeError("One of workers crashed. Maybe restart PC. Cause: " + workerOutput[0][0], workerOutput[0][1])
else:
rmtree(os.path.join(path, '..', '..'), True)
raise UserWarning("C2E: Source directory is empty.")
@@ -800,9 +801,7 @@ def extract_page(vector):
if len(image_list) > 1:
raise UserWarning("mupdf_pdf_extract_page_image() function can be used only with single image pages.")
if not image_list:
width, height = int(page.rect.width), int(page.rect.height)
blank_page = Image.new("RGB", (width, height), "white")
blank_page.save(output_path)
continue
else:
xref = image_list[0][0]
d = doc.extract_image(xref)
@@ -854,6 +853,7 @@ 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))
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
else:
fullPath = workdir
@@ -875,6 +875,12 @@ def getWorkFolder(afile, workdir=None):
os.makedirs(fullPath)
path = workdir
sanitizePermissions(path)
if options.pdfextract:
pdf = pdfjpgextract.PdfJpgExtract(afile, fullPath)
njpg = pdf.extract()
if njpg == 0:
raise UserWarning("Failed to extract images from PDF file.")
return workdir
target_height = options.profileData[1][1]
if options.cropping == 1:
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
@@ -957,6 +963,13 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
while os.path.isfile(basename + '_kcc' + str(counter) + ext):
counter += 1
filename = basename + '_kcc' + str(counter) + ext
elif options.format == 'MOBI' and ext == '.epub':
counter = 0
basename = os.path.splitext(filename)[0]
if os.path.isfile(basename + '.mobi'):
while os.path.isfile(basename + '_kcc' + str(counter) + '.mobi'):
counter += 1
filename = basename + '_kcc' + str(counter) + ext
return filename
@@ -1066,7 +1079,7 @@ def removeNonImages(filetree):
raise UserWarning('No images detected, nested archives are not supported.')
def sanitizeTree(filetree):
def sanitizeTree(filetree, prefix='kcc'):
chapterNames = {}
page = 1
cover_path = None
@@ -1076,7 +1089,7 @@ def sanitizeTree(filetree):
_, ext = getImageFileName(name)
# 9999 page limit
unique_name = f'kcc-{page:04}'
unique_name = f'{prefix}-{page:04}'
page += 1
newKey = os.path.join(root, unique_name + ext)
@@ -1125,7 +1138,7 @@ def chunk_directory(path):
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
for f in files:
# Windows MAX_LEN = 260 plus some buffer
if os.name == 'nt' and len(os.path.join(root, f)) > 180:
if os.name == 'nt' and len(os.path.join(root, f)) > 220:
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
level = 1
break
@@ -1237,7 +1250,7 @@ def detectSuboptimalProcessing(tmppath, orgpath):
GUI.addMessage.emit('Source files are probably created by KCC. The second conversion will decrease quality.'
, 'warning', False)
GUI.addMessage.emit('', '', False)
if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch and options.profile != 'KS':
if imageSmaller > imageNumber * 0.25 and not options.upscale and not options.stretch and not options.profile.startswith('KS'):
print("WARNING: More than 25% of images are smaller than target device resolution. "
"Consider enabling stretching or upscaling to improve readability.")
if GUI:
@@ -1269,10 +1282,13 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
zipfilename = os.path.abspath(zipfilename) + '.zip'
if SEVENZIP in available_archive_tools():
if isepub:
mimetypeFile = open(os.path.join(basedir, 'mimetype'), 'w')
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)
# 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)
if isepub:
@@ -1342,6 +1358,10 @@ def makeParser():
processing_options.add_argument("-n", "--noprocessing", action="store_true", dest="noprocessing", default=False,
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("--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,
help="Resize images smaller than device's resolution")
processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False,
@@ -1380,6 +1400,8 @@ def makeParser():
help="Create PNG files instead JPEG")
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",
help="The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices.")
processing_options.add_argument("--maximizestrips", action="store_true", dest="maximizestrips", default=False,
help="Turn 1x4 strips to 2x2 strips")
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
@@ -1413,6 +1435,8 @@ def checkOptions(options):
options.format = 'MOBI'
if options.batchsplit != 2:
options.batchsplit = 1
if not options.targetsize and options.profile.startswith('Rmk'):
options.targetsize = 95
if options.format == 'MOBI+EPUB':
options.keep_epub = True
options.format = 'MOBI'
@@ -1479,6 +1503,17 @@ def checkOptions(options):
image.ProfileData.Profiles["Custom"] = newProfile
options.profile = "Custom"
options.profileData = image.ProfileData.Profiles[options.profile]
if not options.jpegquality:
if options.profile.startswith('KS') or options.profile == 'KCS':
options.jpegquality = 90
else:
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
if options.kindle_scribe_azw3:
options.profileData = list(image.ProfileData.Profiles[options.profile])
options.profileData[1] = list(options.profileData[1])
options.profileData[1][0] = min(1920, options.profileData[1][0])
return options
@@ -1526,17 +1561,26 @@ def makeFusion(sources: List[str]):
fusion_path = first_path.parent.joinpath(first_path.name + ' [fused]')
print("Running Fusion")
for source in sources:
# Check if prefix is needed when user-specified ordering differs from OS natural sorting
path_names = [Path(s).stem if Path(s).is_file() else Path(s).name for s in sources]
needs_prefix = os_sorted(path_names) != path_names
for index, source in enumerate(sources, start=1):
print(f"Processing {source}...")
checkPre(source)
print("Checking images...")
source_path = Path(source)
# Add the fusion_0001_ prefix to maintain user-specified order if needed
prefix = ''
if needs_prefix:
prefix = f'fusion_{index:04d}_'
if source_path.is_file():
targetpath = fusion_path.joinpath(source_path.stem)
targetpath = fusion_path.joinpath(f'{prefix}{source_path.stem}')
else:
targetpath = fusion_path.joinpath(source_path.name)
targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}')
getWorkFolder(source, str(targetpath))
sanitizeTree(targetpath)
sanitizeTree(targetpath, prefix='fusion')
# TODO: remove flattenTree when subchapters are supported
flattenTree(targetpath)
@@ -1555,8 +1599,6 @@ def makeBook(source, qtgui=None, job_progress=''):
GUI.progressBarTick.emit('1')
else:
checkTools(source)
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile == 'KS' and ('MOBI' in options.format or 'EPUB' in options.format)
checkPre(source)
print(f"{job_progress}Preparing source images...")
path = getWorkFolder(source)
@@ -1565,6 +1607,9 @@ def makeBook(source, qtgui=None, job_progress=''):
removeNonImages(os.path.join(path, "OEBPS", "Images"))
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
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()}
cover = None
if not options.webtoon:
cover = image.Cover(cover_path, options)
@@ -1756,4 +1801,3 @@ def makeMOBI(work, qtgui=None):
makeMOBIWorkerPool.close()
makeMOBIWorkerPool.join()
return makeMOBIWorkerOutput

View File

@@ -62,7 +62,7 @@ def mergeDirectory(work):
imagesValid.append(i[0])
# Silently drop directories that contain too many images
# 131072 = GIMP_MAX_IMAGE_SIZE / 4
if targetHeight > 131072 * 3:
if targetHeight > 131072 * 4:
raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.')
result = Image.new('RGB', (targetWidth, targetHeight))
y = 0

View File

@@ -30,6 +30,7 @@ from .shared import IMAGE_TYPES, subprocess_run
EXTRACTION_ERROR = 'Failed to extract archive. Try extracting file outside of KCC.'
SEVENZIP = '7zz' if platform.system() == 'Darwin' else '7z'
TAR = 'bsdtar' if platform.system() == 'Linux' else 'tar'
class ComicArchive:
@@ -73,7 +74,7 @@ class ComicArchive:
missing = []
extraction_commands = [
['tar', '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.basename, '-C', targetdir],
[TAR, '--exclude', '__MACOSX', '--exclude', '.DS_Store', '--exclude', 'thumbs.db', '--exclude', 'Thumbs.db', '-xf', self.basename, '-C', targetdir],
[SEVENZIP, 'x', '-y', '-xr!__MACOSX', '-xr!.DS_Store', '-xr!thumbs.db', '-xr!Thumbs.db', '-o' + targetdir, self.basename],
]
@@ -125,7 +126,7 @@ class ComicArchive:
def available_archive_tools():
available = []
for tool in ['tar', SEVENZIP, 'unar', 'unrar']:
for tool in [TAR, SEVENZIP, 'unar', 'unrar']:
try:
subprocess_run([tool], stdout=PIPE, stderr=STDOUT)
available.append(tool)

View File

@@ -101,8 +101,13 @@ class ProfileData:
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
'K11': ("Kindle 11", (1072, 1448), Palette16, 1.0),
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0),
'KS': ("Kindle Scribe", (1860, 2480), 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),
'KS3': ("Kindle Scribe 3", (1986, 2648), Palette16, 1.0),
'KSCS': ("Kindle Scribe Colorsoft", (1986, 2648), Palette16, 1.0),
}
ProfilesKindle = {
@@ -153,7 +158,7 @@ class ComicPageParser:
# Detect corruption in source image, let caller catch any exceptions triggered.
srcImgPath = os.path.join(source[0], source[1])
Image.open(srcImgPath).verify()
# Image.open(srcImgPath).verify()
with Image.open(srcImgPath) as im:
self.image = im.copy()
@@ -406,13 +411,13 @@ class ComicPage:
targetPath += '.jpg'
if self.opt.mozjpeg:
with io.BytesIO() as output:
image.save(output, format="JPEG", optimize=1, quality=85)
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
input_jpeg_bytes = output.getvalue()
output_jpeg_bytes = mozjpeg_lossless_optimization.optimize(input_jpeg_bytes)
with open(targetPath, "wb") as output_jpeg_file:
output_jpeg_file.write(output_jpeg_bytes)
else:
image.save(targetPath, 'JPEG', optimize=1, quality=85)
image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
return targetPath
def gammaCorrectImage(self):
@@ -562,8 +567,13 @@ class Cover:
size = list(self.options.profileData[1])
if self.options.kindle_scribe_azw3:
size[0] = min(size[0], 1920)
size[1] = min(size[1], 1920)
self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS)
if self.options.coverfill and not self.options.kindle_scribe_azw3:
# TODO: Kindle Scribe case
self.image = ImageOps.fit(self.image, tuple(size), Image.Resampling.LANCZOS, centering=(0.5, 0.5))
else:
self.image.thumbnail(tuple(size), Image.Resampling.LANCZOS)
def crop_main_cover(self):
w, h = self.image.size
@@ -581,7 +591,7 @@ class Cover:
def save_to_epub(self, target, tomeid, len_tomes=0):
try:
if tomeid == 0:
self.image.save(target, "JPEG", optimize=1, quality=85)
self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)
else:
copy = self.image.copy()
draw = ImageDraw.Draw(copy)
@@ -595,7 +605,7 @@ class Cover:
stroke_fill=0,
stroke_width=25
)
copy.save(target, "JPEG", optimize=1, quality=85)
copy.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)
except IOError:
raise RuntimeError('Failed to save cover.')
@@ -603,6 +613,6 @@ class Cover:
self.image = ImageOps.contain(self.image, (300, 470), Image.Resampling.LANCZOS)
try:
self.image.save(os.path.join(kindle.path.split('documents')[0], 'system', 'thumbnails',
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=85)
'thumbnail_' + asin + '_EBOK_portrait.jpg'), 'JPEG', optimize=1, quality=self.options.jpegquality)
except IOError:
raise RuntimeError('Failed to upload cover.')

View File

@@ -160,6 +160,8 @@ def ignore_pixels_near_edge(bw_img):
for box in edge_bbox:
edge = bw_img.crop(box)
h = edge.histogram()
if not edge.height or not edge.width:
continue
imperfections = h[255] / (edge.height * edge.width)
if imperfections > 0 and imperfections < .02:
bw_img.paste(im=0, box=box)

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2019 Pawel Jastrzebski <pawelj@iosphe.re>
#
# Based upon the code snippet by Ned Batchelder
# (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html)
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
import os
# skip stray images a few pixels in size in some PDFs
# typical images are many thousands in length
# https://github.com/ciromattia/kcc/pull/546
STRAY_IMAGE_LENGTH_THRESHOLD = 300
class PdfJpgExtract:
def __init__(self, fname, fullPath):
self.fname = fname
self.path = fullPath
def getPath(self):
return self.path
def extract(self):
pdf = open(self.fname, "rb").read()
startmark = b"\xff\xd8"
startfix = 0
endmark = b"\xff\xd9"
endfix = 2
i = 0
njpg = 0
while True:
istream = pdf.find(b"stream", i)
if istream < 0:
break
istart = pdf.find(startmark, istream, istream + 20)
if istart < 0:
i = istream + 20
continue
iend = pdf.find(b"endstream", istart)
if iend < 0:
raise Exception("Didn't find end of stream!")
iend = pdf.find(endmark, iend - 20)
if iend < 0:
raise Exception("Didn't find end of JPG!")
istart += startfix
iend += endfix
i = iend
if iend - istart < STRAY_IMAGE_LENGTH_THRESHOLD:
continue
jpg = pdf[istart:iend]
jpgfile = open(os.path.join(self.path, "jpg%d.jpg" % njpg), "wb")
jpgfile.write(jpg)
jpgfile.close()
njpg += 1
return njpg

View File

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