diff --git a/.github/workflows/package-linux.yml b/.github/workflows/package-linux.yml
index 72b95a4..b1d22d8 100644
--- a/.github/workflows/package-linux.yml
+++ b/.github/workflows/package-linux.yml
@@ -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: |
@@ -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*
diff --git a/.github/workflows/package-macos.yml b/.github/workflows/package-macos.yml
index 66187a6..7d56f76 100644
--- a/.github/workflows/package-macos.yml
+++ b/.github/workflows/package-macos.yml
@@ -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
@@ -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
diff --git a/.github/workflows/package-osx-legacy.yml b/.github/workflows/package-osx-legacy.yml
index 086e7f4..8094d3b 100644
--- a/.github/workflows/package-osx-legacy.yml
+++ b/.github/workflows/package-osx-legacy.yml
@@ -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
@@ -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
diff --git a/.github/workflows/package-windows.yml b/.github/workflows/package-windows.yml
index 34148a9..7653218 100644
--- a/.github/workflows/package-windows.yml
+++ b/.github/workflows/package-windows.yml
@@ -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
@@ -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
diff --git a/.github/workflows/package-windows7.yml b/.github/workflows/package-windows7.yml
index 9ea0782..5e55c27 100644
--- a/.github/workflows/package-windows7.yml
+++ b/.github/workflows/package-windows7.yml
@@ -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,11 +50,22 @@ 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/')
with:
prerelease: true
- generate_release_notes: true
+ generate_release_notes: false
files: |
dist/*.exe
diff --git a/Dockerfile b/Dockerfile
index 76b8786..398d417 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -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}
diff --git a/README.md b/README.md
index 0055e6c..7624d58 100644
--- a/README.md
+++ b/README.md
@@ -11,8 +11,15 @@
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.
+
+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,22 +108,21 @@ 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
## 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?
@@ -127,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.
@@ -177,38 +183,47 @@ 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),
+ '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),
+ 'KS1240': ("Kindle 1240", (1240, 1860), Palette16, 1.0),
+ 'KS1324': ("Kindle 1324", (1324, 1986), 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 +247,8 @@ 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
@@ -252,11 +269,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
+ --smartcovercrop 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
@@ -273,6 +298,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
diff --git a/environment.yml b/environment.yml
deleted file mode 100644
index 0a9ba52..0000000
--- a/environment.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-name: kcc
-channels:
- - conda-forge
- - defaults
-dependencies:
- - python=3.11
- - Pillow>=11.3.0
- - psutil>=5.9.5
- - python-slugify>=1.2.1
- - raven>=6.0.0
- - distro
- - natsort>=8.4.0
- - pip
- - pip:
- - mozjpeg-lossless-optimization>=1.1.2
- - pyside6>=6.5.1
diff --git a/gui/KCC.ui b/gui/KCC.ui
index 3bc01a5..9cc41d3 100644
--- a/gui/KCC.ui
+++ b/gui/KCC.ui
@@ -7,7 +7,7 @@
0
0
566
- 573
+ 671
@@ -22,6 +22,362 @@
5
+ -
+
+
+ false
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 300
+
+
+ 1
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 99
+
+
+ 5
+
+
+ 0
+
+
+
+ -
+
+
+ <html><head/><body><p>After calculating the cropping boundaries, "back up" a specified percentage amount.</p></body></html>
+
+
+ Preserve Margin %
+
+
+
+ -
+
+
+ Cropping power:
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+
+ true
+
+
+
+ false
+
+
+ Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ JPEG Quality:
+
+
+
+ -
+
+
+ 95
+
+
+ 5
+
+
+ 85
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ <html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Chunk size MB:
+
+
+
+ -
+
+
+ 50
+
+
+ 600
+
+
+ 400
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Greater than default may cause performance issues on older ereaders.
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 150
+
+
+
+ <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>
+
+
+
+
+
+ QAbstractItemView::SelectionMode::NoSelection
+
+
+ QAbstractItemView::ScrollMode::ScrollPerPixel
+
+
+ QAbstractItemView::ScrollMode::ScrollPerPixel
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 30
+
+
+
+ <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>
+
+
+ Metadata Editor
+
+
+
+ :/Other/icons/editor.png:/Other/icons/editor.png
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+ Support me on Ko-fi
+
+
+
+ :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png
+
+
+
+ 19
+ 16
+
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+ Wiki
+
+
+
+ :/Other/icons/wiki.png:/Other/icons/wiki.png
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+ YouTube
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+ Humble Bundle Referral
+
+
+
+ :/Brand/icons/Humble_H-Red.png:/Brand/icons/Humble_H-Red.png
+
+
+
+ -
+
+
+
+ 0
+ 30
+
+
+
+ Discord
+
+
+
+
+
+
-
@@ -37,6 +393,65 @@
0
+
-
+
+
+ Do not rotate double page spreads in spread splitter option.
+
+
+ No rotate
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>
+
+
+ 1x4 to 2x2 strips
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>
+
+
+ Spread splitter
+
+
+ true
+
+
+
+ -
+
+
+ false
+
+
+ Use a more compatible 8 bit PNG instead of 4 bit.
+
+
+ PNG Legacy Mode
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>
+
+
+ Inter-panel crop
+
+
+ true
+
+
+
-
@@ -59,198 +474,6 @@
- -
-
-
- <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>
-
-
- Right-to-left (manga)
-
-
-
- -
-
-
- <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>
-
-
- Webtoon mode
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>
-
-
- Cropping mode
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - 1x4<br/></span>Keep format 1x4 panels strips.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 2x2<br/></span>Turn 1x4 strips to 2x2 to maximize screen usage.</p></body></html>
-
-
- 1x4 to 2x2 strips
-
-
-
- -
-
-
- Delete input file(s) or directory. It's not recoverable!
-
-
- Delete input
-
-
-
- -
-
-
- <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>
-
-
- Rotate First
-
-
-
- -
-
-
- <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>
-
-
- Output split
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>
-
-
- W/B margins
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>< 1.0 makes the image brighter.<br/>> 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html>
-
-
- Custom gamma
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>
-
-
- Stretch/Upscale
-
-
- true
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in "Chunk size MB" before split occurs.</p></body></html>
-
-
- Chunk size
-
-
-
- -
-
-
- <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>
-
-
- Color mode
-
-
-
- -
-
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Split and rotate<br/></span>Double page spreads will be displayed twice. First split and then rotated. </p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>
-
-
- Spread splitter
-
-
- true
-
-
-
- -
-
-
- Shift first page to opposite side in landscape for two page spread alignment
-
-
- Spread shift
-
-
-
- -
-
-
- <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>
-
-
- Disable processing
-
-
-
- -
-
-
- Erase rainbow effect on color eink screen by attenuating interfering frequencies
-
-
- Rainbow eraser
-
-
-
- -
-
-
- Do not rotate double page spreads in spread splitter option.
-
-
- No rotate
-
-
-
- -
-
-
- <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>
-
-
- File Fusion
-
-
-
-
@@ -273,29 +496,148 @@
- -
-
+
-
+
- <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>
+ <html><head/><body><p style='white-space:pre'>Enable special parsing mode for Korean Webtoons.</p></body></html>
- Panel View 4/2/HQ
+ Webtoon mode
+
+
+
+ -
+
+
+ <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html>
+
+
+ File Fusion
+
+
+
+ -
+
+
+ Delete input file(s) or directory. It's not recoverable!
+
+
+ Delete input
+
+
+
+ -
+
+
+ <html><head/><body><p>Set a custom gamma correction.</p><p>1.0 is default (disabled).<br/>< 1.0 makes the image brighter.<br/>> 1.0 makes the image darker. </p><p>1.8 was the default in KCC 9.1.0 and earlier.</p><p>Use if you want to make midtones darker.</p></body></html>
+
+
+ Custom gamma
+
+
+
+ -
+
+
+ false
+
+
+ Don't quantize PNG images to 16 colors (4 bit)
+
+This will double file size but preserve all 256 colors (8 bit).
+
+Eink only has 16 shades of gray so you probably don't want this.
+
+
+ No Quantize
+
+
+
+ -
+
+
+ Erase rainbow effect on color eink screen by attenuating interfering frequencies
+
+
+ Rainbow eraser
+
+
+
+ -
+
+
+ Resize cover to exact device resolution by center-cropping to aspect ratio first.
+May crop top/bottom or left/right depending on source aspect ratio. Not implemented for Kindle Scribe.
+
+
+ Cover Fill
+
+
+
+ -
+
+
+ Rotate 2 page spreads in opposite direction than normal.
+
+
+ Rotate Right
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'>Enable right-to-left reading.</p></body></html>
+
+
+ Right-to-left (manga)
+
+
+
+ -
+
+
+ Shift first page to opposite side in landscape for two page spread alignment
+
+
+ Spread shift
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled</span></p><p>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Margins<br/></span>Margins</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Margins + page numbers<br/></span>Margins +page numbers</p></body></html>
+
+
+ Cropping mode
true
- -
-
+
-
+
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Disabled<br/></span>Disabled</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Horizontal<br/></span>Crop empty horizontal lines.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Both<br/></span>Crop empty horizontal and vertical lines.</p></body></html>
+ The JPEG quality, on a scale from 0 (worst) to 95 (best).
+
+Default is 85 for most devices besides Kindle Scribe and Colorsoft, which are 90.
+
+Higher values are larger and higher quality, and may resolve blank page issues.
- Inter-panel crop
+ Custom JPEG Quality
-
- true
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - Automatic mode<br/></span>The output will be split automatically.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Checked - Volume mode<br/></span>Every subdirectory will be considered as a separate volume.</p></body></html>
+
+
+ Output split
@@ -312,10 +654,30 @@
+ -
+
+
+ <html><head/><body><p>Attempt to crop main cover from wide image.</p></body></html>
+
+
+ Smart Cover Crop
+
+
+
+ -
+
+
+ <html><head/><body><p>When the spread splitter option is partially checked,</p><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Rotate Last<br/></span>Put the rotated 2 page spread after the split spreads.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Rotate First<br/></span>Put the rotated 2 page spread before the split spreads.</p></body></html>
+
+
+ Rotate First
+
+
+
-
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - JPEG<br/></span>Use JPEG files</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - force PNG<br/></span>Create PNG files instead JPEG for black and white images</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - mozJpeg<br/></span>10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2</p></body></html>
JPEG/PNG/mozJpeg
@@ -335,22 +697,117 @@
- -
-
+
-
+
+
+ false
+
- <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>
+ Force full color images to be saved in lossless PNG format, dramatically increases the filesize.
- Autocontrast
+ Force PNG RGB
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>
+
+
+ Stretch/Upscale
true
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Autodetection<br/></span>The color of margins fill will be detected automatically.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - White<br/></span>Margins will be untouched.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>
+
+
+ W/B margins
+
+
+ true
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style='white-space:pre'><span style=" font-weight:600; text-decoration: underline;">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - 4 high-quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>
+
+
+ Panel View 4/2/HQ
+
+
+ true
+
+
+
+ -
+
+
+ Use the PDF image extraction method from KCC 8 and earlier.
+
+Useful for really weird PDFs.
+
+
+ PDF Legacy Extract
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'>Disable conversion to grayscale.</p></body></html>
+
+
+ Color mode
+
+
+
+ -
+
+
+ Render vector PDFs to device width instead of height.
+
+Useful if you plan to crop a little off the top and bottom to fill screen.
+
+
+ PDF Width Render
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'>Do not process any image, ignore profile and processing options.</p></body></html>
+
+
+ Disable processing
+
+
+
-
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
-
@@ -393,311 +850,48 @@
-
-
-
- -
-
-
-
- 0
- 150
-
-
-
- <html><head/><body><p>Double click on source to open it in metadata editor.</p></body></html>
-
-
-
-
-
- QAbstractItemView::SelectionMode::NoSelection
-
-
- QAbstractItemView::ScrollMode::ScrollPerPixel
-
-
- QAbstractItemView::ScrollMode::ScrollPerPixel
-
-
-
- -
-
-
- false
-
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
-
-
- 0
- 0
-
-
+
-
+
- <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+ <html><head/><body><p><span style=" font-weight:700; text-decoration: underline;">Unchecked<br/></span>Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.</p><p><span style=" font-weight:700; text-decoration: underline;">Checked</span><br/>Output file size specified in "Chunk size MB" before split occurs.</p></body></html>
- Custom height:
+ Chunk size
- -
-
+
-
+
- <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>
-
- 6000
+
+ Custom Autocontrast
+
+
+ true
- -
-
-
-
- 0
- 0
-
-
+
-
+
- <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+ Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.
+
+Ignored for Kindle EPUB/MOBI and all PDF.
- Custom width:
+ WebP (experimental)
- -
-
+
-
+
- <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
-
-
- 8000
-
-
-
-
-
-
- -
-
-
- false
-
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
-
- Gamma: Auto
-
-
-
- -
-
-
- 250
-
-
- 5
-
-
- Qt::Orientation::Horizontal
-
-
-
-
-
-
- -
-
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
-
-
- 0
- 30
-
-
-
- <html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>
+ <html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Main Drive<br/></span>Use dedicated temporary directory on main OS drive.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Source File Drive<br/></span>Create temporary file directory on source file drive.</p></body></html>
- Metadata Editor
-
-
-
- :/Other/icons/editor.png:/Other/icons/editor.png
-
-
-
- -
-
-
-
- 0
- 30
-
-
-
- Support me on Ko-fi
-
-
-
- :/Brand/icons/kofi_symbol.png:/Brand/icons/kofi_symbol.png
-
-
-
- 19
- 16
-
-
-
-
- -
-
-
-
- 0
- 30
-
-
-
- Wiki
-
-
-
- :/Other/icons/wiki.png:/Other/icons/wiki.png
-
-
-
-
-
-
- -
-
-
-
- 0
- 30
-
-
-
-
- true
-
-
-
- false
-
-
- Qt::AlignmentFlag::AlignJustify|Qt::AlignmentFlag::AlignVCenter
-
-
-
- -
-
-
- false
-
-
-
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
-
- <html><head/><body><p>After calculating the cropping boundaries, "back up" a specified percentage amount.</p></body></html>
-
-
- Preserve Margin %
-
-
-
- -
-
-
- Cropping power:
-
-
-
- -
-
-
- 300
-
-
- 1
-
-
- Qt::Orientation::Horizontal
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- 99
-
-
- 5
-
-
- 0
+ Temp Directory
@@ -802,6 +996,12 @@
-
+
+
+ 0
+ 0
+
+
<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>
@@ -836,24 +1036,12 @@
formatBox
- -
-
-
-
- 0
- 0
-
-
+
-
+
false
-
- <html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>
-
-
-
- 0
-
+
0
@@ -867,41 +1055,95 @@
0
-
-
-
-
- 0
- 0
-
-
+
- Chunk size MB:
+ Gamma: Auto
-
-
-
- 100
+
+
+ 250
+
+
+ 5
+
+
+ Qt::Orientation::Horizontal
+
+
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+
+
+ Custom height:
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
- 600
-
-
- 400
+ 6000
- -
-
+
-
+
-
+
0
0
+
+ <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+
- Greater than default may cause performance issues on older ereaders.
+ Custom width:
+
+
+
+ -
+
+
+ <html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>
+
+
+ 8000
@@ -946,6 +1188,7 @@
noRotateBox
interPanelCropBox
metadataTitleBox
+ coverFillBox
chunkSizeCheckBox
chunkSizeBox
eraseRainbowBox
diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py
index 5943979..bc24c60 100644
--- a/kindlecomicconverter/KCC_gui.py
+++ b/kindlecomicconverter/KCC_gui.py
@@ -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
@@ -195,7 +195,7 @@ class VersionThread(QThread):
icon = 'bindle'
if category == 'kofi':
icon = 'kofi'
- message = f"{payload.get('name')}"
+ message = f"{payload.get('name')}"
if payload.get('link'):
message = '{}'.format(payload.get('link'), payload.get('name'))
if payload.get('showDeadline'):
@@ -327,12 +327,22 @@ class WorkerThread(QThread):
options.maximizestrips = True
if GUI.disableProcessingBox.isChecked():
options.noprocessing = True
+ if GUI.pdfExtractBox.isChecked():
+ options.pdfextract = True
+ if GUI.pdfWidthBox.isChecked():
+ options.pdfwidth = True
+ if GUI.smartCoverCropBox.isChecked():
+ options.smartcovercrop = 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:
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():
@@ -341,12 +351,24 @@ 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:
options.customwidth = str(GUI.widthBox.value())
options.customheight = str(GUI.heightBox.value())
@@ -519,6 +541,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')
@@ -693,6 +716,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
@@ -760,6 +795,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,8 +813,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.rotateBox.setChecked(False)
GUI.borderBox.setEnabled(False)
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
- GUI.upscaleBox.setEnabled(False)
- GUI.upscaleBox.setChecked(False)
+ # GUI.upscaleBox.setEnabled(False)
+ # GUI.upscaleBox.setChecked(False)
GUI.croppingBox.setEnabled(False)
GUI.croppingBox.setChecked(False)
GUI.interPanelCropBox.setEnabled(False)
@@ -790,7 +831,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') or True:
GUI.upscaleBox.setEnabled(True)
GUI.croppingBox.setEnabled(True)
GUI.interPanelCropBox.setEnabled(True)
@@ -815,12 +856,20 @@ 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:
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)
@@ -877,10 +926,10 @@ 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') and False:
GUI.upscaleBox.setDisabled(True)
else:
- if not GUI.webtoonBox.isChecked():
+ if not GUI.webtoonBox.isChecked() or True:
GUI.upscaleBox.setEnabled(True)
if profile['Label'] == 'KCS':
current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
@@ -888,6 +937,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':
@@ -911,10 +964,16 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI+EPUB-200MB'):
GUI.chunkSizeCheckBox.setEnabled(False)
GUI.chunkSizeCheckBox.setChecked(False)
+ elif GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'KFX':
+ GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
+ GUI.upscaleBox.setChecked(True)
elif not GUI.webtoonBox.isChecked():
GUI.chunkSizeCheckBox.setEnabled(True)
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
- 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')
+ GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
+ else:
+ GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
def stripTags(self, html):
s = HTMLStripper()
@@ -1042,15 +1101,27 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'colorBox': GUI.colorBox.checkState(),
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
+ 'pdfExtractBox': GUI.pdfExtractBox.checkState(),
+ 'pdfWidthBox': GUI.pdfWidthBox.checkState(),
+ 'smartCoverCropBox': GUI.smartCoverCropBox.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,
@@ -1095,7 +1166,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)
@@ -1172,7 +1244,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,7 +1254,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
- "KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
+ "PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
+ "KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
"MOBI + EPUB (200MB limit)": {'icon': 'MOBI', 'format': 'MOBI+EPUB-200MB'},
@@ -1198,9 +1271,27 @@ 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 1324x1986": {
+ 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS1324',
+ },
+ "Kindle Scribe 1/2": {
'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS',
},
+ "Kindle Scribe 3": {
+ 'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 3, 'DefaultUpscale': False, 'ForceColor': False, 'Label': 'KS3',
+ },
+ "Kindle Scribe Colorsoft": {
+ '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',
},
@@ -1208,7 +1299,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',
@@ -1275,9 +1366,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",
@@ -1297,6 +1390,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"Separator",
"Other",
"Separator",
+ "Kindle 1324x1986",
+ "Kindle 1920x1920",
+ "Kindle 1860x1920",
+ "Kindle 1240x1860",
"Kindle 8/10",
"Kindle Oasis 8",
"Kindle Paperwhite 7/10",
@@ -1344,7 +1441,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'important tips.',
'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('Install 7z (link)'
@@ -1359,11 +1456,15 @@ 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)
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)
@@ -1425,6 +1526,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:
diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py
index 36f09d6..62e9d29 100644
--- a/kindlecomicconverter/KCC_ui.py
+++ b/kindlecomicconverter/KCC_ui.py
@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
def setupUi(self, mainWindow):
if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow")
- mainWindow.resize(566, 573)
+ mainWindow.resize(566, 671)
icon = QIcon()
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
mainWindow.setWindowIcon(icon)
@@ -35,131 +35,217 @@ class Ui_mainWindow(object):
self.gridLayout = QGridLayout(self.centralWidget)
self.gridLayout.setObjectName(u"gridLayout")
self.gridLayout.setContentsMargins(-1, -1, -1, 5)
+ 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.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.jpegQualityWidget = QWidget(self.centralWidget)
+ self.jpegQualityWidget.setObjectName(u"jpegQualityWidget")
+ sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
+ sizePolicy1.setHorizontalStretch(0)
+ sizePolicy1.setVerticalStretch(0)
+ 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_12.addWidget(self.jpegQualityLabel)
+
+ self.jpegQualitySpinBox = QSpinBox(self.jpegQualityWidget)
+ self.jpegQualitySpinBox.setObjectName(u"jpegQualitySpinBox")
+ self.jpegQualitySpinBox.setMaximum(95)
+ self.jpegQualitySpinBox.setSingleStep(5)
+ self.jpegQualitySpinBox.setValue(85)
+
+ self.horizontalLayout_12.addWidget(self.jpegQualitySpinBox)
+
+
+ self.gridLayout.addWidget(self.jpegQualityWidget, 10, 0, 1, 1)
+
+ self.chunkSizeWidget = QWidget(self.centralWidget)
+ self.chunkSizeWidget.setObjectName(u"chunkSizeWidget")
+ sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
+ sizePolicy2.setHorizontalStretch(0)
+ sizePolicy2.setVerticalStretch(0)
+ sizePolicy2.setHeightForWidth(self.chunkSizeWidget.sizePolicy().hasHeightForWidth())
+ self.chunkSizeWidget.setSizePolicy(sizePolicy2)
+ 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")
+ sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
+ sizePolicy3.setHorizontalStretch(0)
+ sizePolicy3.setVerticalStretch(0)
+ sizePolicy3.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
+ self.chunkSizeLabel.setSizePolicy(sizePolicy3)
+
+ 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")
+ sizePolicy3.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
+ self.chunkSizeWarnLabel.setSizePolicy(sizePolicy3)
+
+ self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
+
+
+ self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
+
+ 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.toolWidget = QWidget(self.centralWidget)
+ self.toolWidget.setObjectName(u"toolWidget")
+ self.gridLayout_6 = QGridLayout(self.toolWidget)
+ self.gridLayout_6.setObjectName(u"gridLayout_6")
+ self.gridLayout_6.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.gridLayout_6.addWidget(self.editorButton, 0, 0, 1, 1)
+
+ 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.gridLayout_6.addWidget(self.kofiButton, 0, 1, 1, 1)
+
+ 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.gridLayout_6.addWidget(self.wikiButton, 0, 2, 1, 1)
+
+ self.youtubeButton = QPushButton(self.toolWidget)
+ self.youtubeButton.setObjectName(u"youtubeButton")
+ self.youtubeButton.setMinimumSize(QSize(0, 30))
+
+ self.gridLayout_6.addWidget(self.youtubeButton, 1, 0, 1, 1)
+
+ self.humbleButton = QPushButton(self.toolWidget)
+ self.humbleButton.setObjectName(u"humbleButton")
+ self.humbleButton.setMinimumSize(QSize(0, 30))
+ icon4 = QIcon()
+ icon4.addFile(u":/Brand/icons/Humble_H-Red.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.humbleButton.setIcon(icon4)
+
+ self.gridLayout_6.addWidget(self.humbleButton, 1, 1, 1, 1)
+
+ self.discordButton = QPushButton(self.toolWidget)
+ self.discordButton.setObjectName(u"discordButton")
+ self.discordButton.setMinimumSize(QSize(0, 30))
+
+ self.gridLayout_6.addWidget(self.discordButton, 1, 2, 1, 1)
+
+
+ self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
+
self.optionWidget = QWidget(self.centralWidget)
self.optionWidget.setObjectName(u"optionWidget")
self.gridLayout_2 = QGridLayout(self.optionWidget)
self.gridLayout_2.setObjectName(u"gridLayout_2")
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)
- self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
- self.titleEdit.setClearButtonEnabled(False)
+ self.noRotateBox = QCheckBox(self.optionWidget)
+ self.noRotateBox.setObjectName(u"noRotateBox")
- self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1)
-
- self.mangaBox = QCheckBox(self.optionWidget)
- self.mangaBox.setObjectName(u"mangaBox")
-
- self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
-
- self.webtoonBox = QCheckBox(self.optionWidget)
- self.webtoonBox.setObjectName(u"webtoonBox")
-
- self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
-
- self.croppingBox = QCheckBox(self.optionWidget)
- self.croppingBox.setObjectName(u"croppingBox")
- self.croppingBox.setTristate(True)
-
- self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
+ self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
self.maximizeStrips = QCheckBox(self.optionWidget)
self.maximizeStrips.setObjectName(u"maximizeStrips")
self.gridLayout_2.addWidget(self.maximizeStrips, 4, 1, 1, 1)
- self.deleteBox = QCheckBox(self.optionWidget)
- self.deleteBox.setObjectName(u"deleteBox")
-
- self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
-
- self.rotateFirstBox = QCheckBox(self.optionWidget)
- self.rotateFirstBox.setObjectName(u"rotateFirstBox")
-
- self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
-
- self.outputSplit = QCheckBox(self.optionWidget)
- self.outputSplit.setObjectName(u"outputSplit")
-
- self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
-
- self.borderBox = QCheckBox(self.optionWidget)
- self.borderBox.setObjectName(u"borderBox")
- self.borderBox.setTristate(True)
-
- self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
-
- self.gammaBox = QCheckBox(self.optionWidget)
- self.gammaBox.setObjectName(u"gammaBox")
-
- self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
-
- self.upscaleBox = QCheckBox(self.optionWidget)
- self.upscaleBox.setObjectName(u"upscaleBox")
- self.upscaleBox.setTristate(True)
-
- self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
-
- self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
- self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
-
- self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
-
- self.colorBox = QCheckBox(self.optionWidget)
- self.colorBox.setObjectName(u"colorBox")
-
- self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
-
self.rotateBox = QCheckBox(self.optionWidget)
self.rotateBox.setObjectName(u"rotateBox")
self.rotateBox.setTristate(True)
self.gridLayout_2.addWidget(self.rotateBox, 1, 1, 1, 1)
- self.spreadShiftBox = QCheckBox(self.optionWidget)
- self.spreadShiftBox.setObjectName(u"spreadShiftBox")
+ self.pngLegacyBox = QCheckBox(self.optionWidget)
+ self.pngLegacyBox.setObjectName(u"pngLegacyBox")
+ self.pngLegacyBox.setEnabled(False)
- self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
-
- self.disableProcessingBox = QCheckBox(self.optionWidget)
- self.disableProcessingBox.setObjectName(u"disableProcessingBox")
-
- self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
-
- self.eraseRainbowBox = QCheckBox(self.optionWidget)
- self.eraseRainbowBox.setObjectName(u"eraseRainbowBox")
-
- self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1)
-
- self.noRotateBox = QCheckBox(self.optionWidget)
- self.noRotateBox.setObjectName(u"noRotateBox")
-
- self.gridLayout_2.addWidget(self.noRotateBox, 6, 1, 1, 1)
-
- self.fileFusionBox = QCheckBox(self.optionWidget)
- self.fileFusionBox.setObjectName(u"fileFusionBox")
-
- self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
-
- self.authorEdit = QLineEdit(self.optionWidget)
- self.authorEdit.setObjectName(u"authorEdit")
- sizePolicy.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
- self.authorEdit.setSizePolicy(sizePolicy)
- self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
- self.authorEdit.setClearButtonEnabled(False)
-
- self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1)
-
- self.qualityBox = QCheckBox(self.optionWidget)
- self.qualityBox.setObjectName(u"qualityBox")
- self.qualityBox.setTristate(True)
-
- self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
+ self.gridLayout_2.addWidget(self.pngLegacyBox, 11, 0, 1, 1)
self.interPanelCropBox = QCheckBox(self.optionWidget)
self.interPanelCropBox.setObjectName(u"interPanelCropBox")
@@ -167,12 +253,107 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.interPanelCropBox, 6, 2, 1, 1)
+ self.titleEdit = QLineEdit(self.optionWidget)
+ self.titleEdit.setObjectName(u"titleEdit")
+ sizePolicy2.setHeightForWidth(self.titleEdit.sizePolicy().hasHeightForWidth())
+ self.titleEdit.setSizePolicy(sizePolicy2)
+ self.titleEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
+ self.titleEdit.setClearButtonEnabled(False)
+
+ self.gridLayout_2.addWidget(self.titleEdit, 0, 0, 1, 1)
+
+ self.authorEdit = QLineEdit(self.optionWidget)
+ self.authorEdit.setObjectName(u"authorEdit")
+ sizePolicy2.setHeightForWidth(self.authorEdit.sizePolicy().hasHeightForWidth())
+ self.authorEdit.setSizePolicy(sizePolicy2)
+ self.authorEdit.setFocusPolicy(Qt.FocusPolicy.ClickFocus)
+ self.authorEdit.setClearButtonEnabled(False)
+
+ self.gridLayout_2.addWidget(self.authorEdit, 0, 1, 1, 1)
+
+ self.webtoonBox = QCheckBox(self.optionWidget)
+ self.webtoonBox.setObjectName(u"webtoonBox")
+
+ self.gridLayout_2.addWidget(self.webtoonBox, 2, 0, 1, 1)
+
+ self.fileFusionBox = QCheckBox(self.optionWidget)
+ self.fileFusionBox.setObjectName(u"fileFusionBox")
+
+ self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1)
+
+ self.deleteBox = QCheckBox(self.optionWidget)
+ self.deleteBox.setObjectName(u"deleteBox")
+
+ self.gridLayout_2.addWidget(self.deleteBox, 5, 1, 1, 1)
+
+ self.gammaBox = QCheckBox(self.optionWidget)
+ self.gammaBox.setObjectName(u"gammaBox")
+
+ self.gridLayout_2.addWidget(self.gammaBox, 2, 2, 1, 1)
+
+ self.noQuantizeBox = QCheckBox(self.optionWidget)
+ self.noQuantizeBox.setObjectName(u"noQuantizeBox")
+ self.noQuantizeBox.setEnabled(False)
+
+ self.gridLayout_2.addWidget(self.noQuantizeBox, 10, 2, 1, 1)
+
+ self.eraseRainbowBox = QCheckBox(self.optionWidget)
+ self.eraseRainbowBox.setObjectName(u"eraseRainbowBox")
+
+ self.gridLayout_2.addWidget(self.eraseRainbowBox, 7, 2, 1, 1)
+
+ self.coverFillBox = QCheckBox(self.optionWidget)
+ self.coverFillBox.setObjectName(u"coverFillBox")
+
+ self.gridLayout_2.addWidget(self.coverFillBox, 9, 1, 1, 1)
+
+ self.rotateRightBox = QCheckBox(self.optionWidget)
+ self.rotateRightBox.setObjectName(u"rotateRightBox")
+
+ self.gridLayout_2.addWidget(self.rotateRightBox, 10, 1, 1, 1)
+
+ self.mangaBox = QCheckBox(self.optionWidget)
+ self.mangaBox.setObjectName(u"mangaBox")
+
+ self.gridLayout_2.addWidget(self.mangaBox, 1, 0, 1, 1)
+
+ self.spreadShiftBox = QCheckBox(self.optionWidget)
+ self.spreadShiftBox.setObjectName(u"spreadShiftBox")
+
+ self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1)
+
+ self.croppingBox = QCheckBox(self.optionWidget)
+ self.croppingBox.setObjectName(u"croppingBox")
+ self.croppingBox.setTristate(True)
+
+ self.gridLayout_2.addWidget(self.croppingBox, 4, 2, 1, 1)
+
+ self.jpegQualityBox = QCheckBox(self.optionWidget)
+ self.jpegQualityBox.setObjectName(u"jpegQualityBox")
+
+ self.gridLayout_2.addWidget(self.jpegQualityBox, 8, 0, 1, 1)
+
+ self.outputSplit = QCheckBox(self.optionWidget)
+ self.outputSplit.setObjectName(u"outputSplit")
+
+ self.gridLayout_2.addWidget(self.outputSplit, 3, 1, 1, 1)
+
self.metadataTitleBox = QCheckBox(self.optionWidget)
self.metadataTitleBox.setObjectName(u"metadataTitleBox")
self.metadataTitleBox.setTristate(True)
self.gridLayout_2.addWidget(self.metadataTitleBox, 7, 0, 1, 1)
+ self.smartCoverCropBox = QCheckBox(self.optionWidget)
+ self.smartCoverCropBox.setObjectName(u"smartCoverCropBox")
+
+ self.gridLayout_2.addWidget(self.smartCoverCropBox, 11, 1, 1, 1)
+
+ self.rotateFirstBox = QCheckBox(self.optionWidget)
+ self.rotateFirstBox.setObjectName(u"rotateFirstBox")
+
+ self.gridLayout_2.addWidget(self.rotateFirstBox, 8, 1, 1, 1)
+
self.mozJpegBox = QCheckBox(self.optionWidget)
self.mozJpegBox.setObjectName(u"mozJpegBox")
self.mozJpegBox.setTristate(True)
@@ -184,23 +365,59 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.autoLevelBox, 8, 2, 1, 1)
- self.autocontrastBox = QCheckBox(self.optionWidget)
- self.autocontrastBox.setObjectName(u"autocontrastBox")
- self.autocontrastBox.setTristate(True)
+ self.forcePngRgbBox = QCheckBox(self.optionWidget)
+ self.forcePngRgbBox.setObjectName(u"forcePngRgbBox")
+ self.forcePngRgbBox.setEnabled(False)
- self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
+ self.gridLayout_2.addWidget(self.forcePngRgbBox, 11, 2, 1, 1)
+
+ self.upscaleBox = QCheckBox(self.optionWidget)
+ self.upscaleBox.setObjectName(u"upscaleBox")
+ self.upscaleBox.setTristate(True)
+
+ self.gridLayout_2.addWidget(self.upscaleBox, 2, 1, 1, 1)
+
+ self.borderBox = QCheckBox(self.optionWidget)
+ self.borderBox.setObjectName(u"borderBox")
+ self.borderBox.setTristate(True)
+
+ self.gridLayout_2.addWidget(self.borderBox, 3, 0, 1, 1)
+
+ self.qualityBox = QCheckBox(self.optionWidget)
+ self.qualityBox.setObjectName(u"qualityBox")
+ self.qualityBox.setTristate(True)
+
+ self.gridLayout_2.addWidget(self.qualityBox, 1, 2, 1, 1)
+
+ self.pdfExtractBox = QCheckBox(self.optionWidget)
+ self.pdfExtractBox.setObjectName(u"pdfExtractBox")
+
+ self.gridLayout_2.addWidget(self.pdfExtractBox, 9, 0, 1, 1)
+
+ self.colorBox = QCheckBox(self.optionWidget)
+ self.colorBox.setObjectName(u"colorBox")
+
+ self.gridLayout_2.addWidget(self.colorBox, 3, 2, 1, 1)
+
+ self.pdfWidthBox = QCheckBox(self.optionWidget)
+ self.pdfWidthBox.setObjectName(u"pdfWidthBox")
+
+ self.gridLayout_2.addWidget(self.pdfWidthBox, 10, 0, 1, 1)
+
+ self.disableProcessingBox = QCheckBox(self.optionWidget)
+ self.disableProcessingBox.setObjectName(u"disableProcessingBox")
+
+ self.gridLayout_2.addWidget(self.disableProcessingBox, 5, 2, 1, 1)
self.outputFolderWidget = QWidget(self.optionWidget)
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,65 +425,108 @@ 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)
+ icon5 = QIcon()
+ icon5.addFile(u":/Other/icons/folder_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.defaultOutputFolderButton.setIcon(icon5)
self.horizontalLayout_3.addWidget(self.defaultOutputFolderButton)
self.gridLayout_2.addWidget(self.outputFolderWidget, 0, 2, 1, 1)
+ self.chunkSizeCheckBox = QCheckBox(self.optionWidget)
+ self.chunkSizeCheckBox.setObjectName(u"chunkSizeCheckBox")
+
+ self.gridLayout_2.addWidget(self.chunkSizeCheckBox, 7, 1, 1, 1)
+
+ self.autocontrastBox = QCheckBox(self.optionWidget)
+ self.autocontrastBox.setObjectName(u"autocontrastBox")
+ self.autocontrastBox.setTristate(True)
+
+ self.gridLayout_2.addWidget(self.autocontrastBox, 9, 2, 1, 1)
+
+ self.webpBox = QCheckBox(self.optionWidget)
+ self.webpBox.setObjectName(u"webpBox")
+
+ self.gridLayout_2.addWidget(self.webpBox, 12, 0, 1, 1)
+
+ self.tempDirBox = QCheckBox(self.optionWidget)
+ self.tempDirBox.setObjectName(u"tempDirBox")
+
+ self.gridLayout_2.addWidget(self.tempDirBox, 12, 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.buttonWidget = QWidget(self.centralWidget)
+ self.buttonWidget.setObjectName(u"buttonWidget")
+ sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
+ sizePolicy4.setHorizontalStretch(0)
+ sizePolicy4.setVerticalStretch(0)
+ sizePolicy4.setHeightForWidth(self.buttonWidget.sizePolicy().hasHeightForWidth())
+ self.buttonWidget.setSizePolicy(sizePolicy4)
+ 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)
+ icon6 = QIcon()
+ icon6.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.convertButton.setIcon(icon6)
- self.gridLayout.addWidget(self.jobList, 2, 0, 1, 2)
+ self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
- 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.clearButton = QPushButton(self.buttonWidget)
+ self.clearButton.setObjectName(u"clearButton")
+ self.clearButton.setMinimumSize(QSize(0, 30))
+ icon7 = QIcon()
+ icon7.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.clearButton.setIcon(icon7)
- self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
+ self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
- self.widthBox = QSpinBox(self.customWidget)
- self.widthBox.setObjectName(u"widthBox")
- self.widthBox.setMaximum(6000)
+ self.deviceBox = QComboBox(self.buttonWidget)
+ self.deviceBox.setObjectName(u"deviceBox")
+ self.deviceBox.setMinimumSize(QSize(0, 28))
- self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
+ self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1)
- self.wLabel = QLabel(self.customWidget)
- self.wLabel.setObjectName(u"wLabel")
- sizePolicy2.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
- self.wLabel.setSizePolicy(sizePolicy2)
+ self.fileButton = QPushButton(self.buttonWidget)
+ self.fileButton.setObjectName(u"fileButton")
+ self.fileButton.setMinimumSize(QSize(0, 30))
+ icon8 = QIcon()
+ icon8.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
+ self.fileButton.setIcon(icon8)
- self.gridLayout_3.addWidget(self.wLabel, 0, 0, 1, 1)
+ self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
- self.heightBox = QSpinBox(self.customWidget)
- self.heightBox.setObjectName(u"heightBox")
- self.heightBox.setMaximum(8000)
+ self.directoryButton = QPushButton(self.buttonWidget)
+ self.directoryButton.setObjectName(u"directoryButton")
+ sizePolicy5 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
+ sizePolicy5.setHorizontalStretch(0)
+ sizePolicy5.setVerticalStretch(0)
+ sizePolicy5.setHeightForWidth(self.directoryButton.sizePolicy().hasHeightForWidth())
+ self.directoryButton.setSizePolicy(sizePolicy5)
+ self.directoryButton.setIcon(icon5)
- self.gridLayout_3.addWidget(self.heightBox, 0, 3, 1, 1)
+ 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.addWidget(self.customWidget, 8, 0, 1, 2)
+ 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.gammaWidget = QWidget(self.centralWidget)
self.gammaWidget.setObjectName(u"gammaWidget")
@@ -290,191 +550,40 @@ 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.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.setHeightForWidth(self.hLabel.sizePolicy().hasHeightForWidth())
+ self.hLabel.setSizePolicy(sizePolicy1)
- self.horizontalLayout.addWidget(self.editorButton)
+ self.gridLayout_3.addWidget(self.hLabel, 0, 2, 1, 1)
- 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.widthBox = QSpinBox(self.customWidget)
+ self.widthBox.setObjectName(u"widthBox")
+ self.widthBox.setMaximum(6000)
- self.horizontalLayout.addWidget(self.kofiButton)
+ self.gridLayout_3.addWidget(self.widthBox, 0, 1, 1, 1)
- 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.wLabel = QLabel(self.customWidget)
+ self.wLabel.setObjectName(u"wLabel")
+ sizePolicy1.setHeightForWidth(self.wLabel.sizePolicy().hasHeightForWidth())
+ self.wLabel.setSizePolicy(sizePolicy1)
- self.horizontalLayout.addWidget(self.wikiButton)
+ 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.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")
- 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")
- sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
- sizePolicy4.setHorizontalStretch(0)
- sizePolicy4.setVerticalStretch(0)
- sizePolicy4.setHeightForWidth(self.chunkSizeLabel.sizePolicy().hasHeightForWidth())
- self.chunkSizeLabel.setSizePolicy(sizePolicy4)
-
- 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")
- sizePolicy4.setHeightForWidth(self.chunkSizeWarnLabel.sizePolicy().hasHeightForWidth())
- self.chunkSizeWarnLabel.setSizePolicy(sizePolicy4)
-
- self.horizontalLayout_4.addWidget(self.chunkSizeWarnLabel)
-
-
- self.gridLayout.addWidget(self.chunkSizeWidget, 6, 0, 1, 1)
+ self.gridLayout.addWidget(self.customWidget, 8, 0, 1, 2)
mainWindow.setCentralWidget(self.centralWidget)
self.statusBar = QStatusBar(mainWindow)
@@ -509,7 +618,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)
@@ -527,99 +637,131 @@ class Ui_mainWindow(object):
def retranslateUi(self, mainWindow):
mainWindow.setWindowTitle(QCoreApplication.translate("mainWindow", u"Kindle Comic Converter", None))
#if QT_CONFIG(tooltip)
- self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Title
", None))
+ self.preserveMarginLabel.setToolTip(QCoreApplication.translate("mainWindow", u" After calculating the cropping boundaries, "back up" a specified percentage amount.
", None))
#endif // QT_CONFIG(tooltip)
- self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None))
+ self.preserveMarginLabel.setText(QCoreApplication.translate("mainWindow", u"Preserve Margin %", None))
+ self.croppingPowerLabel.setText(QCoreApplication.translate("mainWindow", u"Cropping power:", None))
+ self.jpegQualityLabel.setText(QCoreApplication.translate("mainWindow", u"JPEG Quality:", None))
#if QT_CONFIG(tooltip)
- self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"Enable right-to-left reading.
", None))
+ self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"Warning: chunk size greater than default may cause
performance/battery issues, especially on older devices.
", None))
#endif // QT_CONFIG(tooltip)
- self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None))
+ 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.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u" Enable special parsing mode for Korean Webtoons.
", None))
+ self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"Double click on source to open it in metadata editor.
", None))
#endif // QT_CONFIG(tooltip)
- self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
#if QT_CONFIG(tooltip)
- self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"
Unchecked - Disabled
Disabled
Indeterminate - Margins
Margins
Checked - Margins + page numbers
Margins +page numbers
", None))
+ self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"Shift+Click to edit directory.
", None))
#endif // QT_CONFIG(tooltip)
- self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
-#if QT_CONFIG(tooltip)
- self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - 1x4
Keep format 1x4 panels strips.
Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.
", None))
-#endif // QT_CONFIG(tooltip)
- self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
-#if QT_CONFIG(tooltip)
- self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
-#endif // QT_CONFIG(tooltip)
- self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
-#if QT_CONFIG(tooltip)
- self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"When the spread splitter option is partially checked,
Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.
Checked - Rotate First
Put the rotated 2 page spread before the split spreads.
", None))
-#endif // QT_CONFIG(tooltip)
- self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
-#if QT_CONFIG(tooltip)
- self.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Automatic mode
The output will be split automatically.
Checked - Volume mode
Every subdirectory will be considered as a separate volume.
", None))
-#endif // QT_CONFIG(tooltip)
- self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
-#if QT_CONFIG(tooltip)
- self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Autodetection
The color of margins fill will be detected automatically.
Indeterminate - White
Margins will be untouched.
Checked - Black
Margins will be filled with black color.
", None))
-#endif // QT_CONFIG(tooltip)
- self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
-#if QT_CONFIG(tooltip)
- self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"Set a custom gamma correction.
1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.
1.8 was the default in KCC 9.1.0 and earlier.
Use if you want to make midtones darker.
", None))
-#endif // QT_CONFIG(tooltip)
- self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
-#if QT_CONFIG(tooltip)
- self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Nothing
Images smaller than device resolution will not be resized.
Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.
Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.
", None))
-#endif // QT_CONFIG(tooltip)
- self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
-#if QT_CONFIG(tooltip)
- self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.
Checked
Output file size specified in "Chunk size MB" before split occurs.
", None))
-#endif // QT_CONFIG(tooltip)
- self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
-#if QT_CONFIG(tooltip)
- self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"Disable conversion to grayscale.
", None))
-#endif // QT_CONFIG(tooltip)
- self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
-#if QT_CONFIG(tooltip)
- self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Split
Double page spreads will be cut into two separate pages.
Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.
Checked - Rotate
Double page spreads will be rotated.
", None))
-#endif // QT_CONFIG(tooltip)
- self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
-#if QT_CONFIG(tooltip)
- self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
-#endif // QT_CONFIG(tooltip)
- self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
-#if QT_CONFIG(tooltip)
- self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not process any image, ignore profile and processing options.
", None))
-#endif // QT_CONFIG(tooltip)
- self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
-#if QT_CONFIG(tooltip)
- self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None))
-#endif // QT_CONFIG(tooltip)
- self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None))
+ 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))
+ self.youtubeButton.setText(QCoreApplication.translate("mainWindow", u"YouTube", None))
+ self.humbleButton.setText(QCoreApplication.translate("mainWindow", u"Humble Bundle Referral", None))
+ self.discordButton.setText(QCoreApplication.translate("mainWindow", u"Discord", None))
#if QT_CONFIG(tooltip)
self.noRotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not rotate double page spreads in spread splitter option.", None))
#endif // QT_CONFIG(tooltip)
self.noRotateBox.setText(QCoreApplication.translate("mainWindow", u"No rotate", None))
#if QT_CONFIG(tooltip)
- self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"Combines all selected files into a single file. (Helpful for combining chapters into volumes.)
", None))
+ self.maximizeStrips.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - 1x4
Keep format 1x4 panels strips.
Checked - 2x2
Turn 1x4 strips to 2x2 to maximize screen usage.
", None))
#endif // QT_CONFIG(tooltip)
- self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None))
+ self.maximizeStrips.setText(QCoreApplication.translate("mainWindow", u"1x4 to 2x2 strips", None))
#if QT_CONFIG(tooltip)
- self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
+ self.rotateBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Split
Double page spreads will be cut into two separate pages.
Indeterminate - Split and rotate
Double page spreads will be displayed twice. First split and then rotated.
Checked - Rotate
Double page spreads will be rotated.
", None))
#endif // QT_CONFIG(tooltip)
- self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
+ self.rotateBox.setText(QCoreApplication.translate("mainWindow", u"Spread splitter", None))
#if QT_CONFIG(tooltip)
- self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - 4 panels
Zoom each corner separately.
Indeterminate - 2 panels
Zoom only the top and bottom of the page.
Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.
", None))
+ self.pngLegacyBox.setToolTip(QCoreApplication.translate("mainWindow", u"Use a more compatible 8 bit PNG instead of 4 bit.", None))
#endif // QT_CONFIG(tooltip)
- self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
+ self.pngLegacyBox.setText(QCoreApplication.translate("mainWindow", u"PNG Legacy Mode", None))
#if QT_CONFIG(tooltip)
self.interPanelCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Disabled
Disabled
Indeterminate - Horizontal
Crop empty horizontal lines.
Checked - Both
Crop empty horizontal and vertical lines.
", None))
#endif // QT_CONFIG(tooltip)
self.interPanelCropBox.setText(QCoreApplication.translate("mainWindow", u"Inter-panel crop", None))
+#if QT_CONFIG(tooltip)
+ self.titleEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Title
", None))
+#endif // QT_CONFIG(tooltip)
+ self.titleEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Title", None))
+#if QT_CONFIG(tooltip)
+ self.authorEdit.setToolTip(QCoreApplication.translate("mainWindow", u"Default Author is KCC", None))
+#endif // QT_CONFIG(tooltip)
+ self.authorEdit.setPlaceholderText(QCoreApplication.translate("mainWindow", u"Default Author", None))
+#if QT_CONFIG(tooltip)
+ self.webtoonBox.setToolTip(QCoreApplication.translate("mainWindow", u"Enable special parsing mode for Korean Webtoons.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.webtoonBox.setText(QCoreApplication.translate("mainWindow", u"Webtoon mode", None))
+#if QT_CONFIG(tooltip)
+ self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"Combines all selected files into a single file. (Helpful for combining chapters into volumes.)
", None))
+#endif // QT_CONFIG(tooltip)
+ self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None))
+#if QT_CONFIG(tooltip)
+ self.deleteBox.setToolTip(QCoreApplication.translate("mainWindow", u"Delete input file(s) or directory. It's not recoverable!", None))
+#endif // QT_CONFIG(tooltip)
+ self.deleteBox.setText(QCoreApplication.translate("mainWindow", u"Delete input", None))
+#if QT_CONFIG(tooltip)
+ self.gammaBox.setToolTip(QCoreApplication.translate("mainWindow", u"Set a custom gamma correction.
1.0 is default (disabled).
< 1.0 makes the image brighter.
> 1.0 makes the image darker.
1.8 was the default in KCC 9.1.0 and earlier.
Use if you want to make midtones darker.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.gammaBox.setText(QCoreApplication.translate("mainWindow", u"Custom gamma", None))
+#if QT_CONFIG(tooltip)
+ self.noQuantizeBox.setToolTip(QCoreApplication.translate("mainWindow", u"Don't quantize PNG images to 16 colors (4 bit)\n"
+"\n"
+"This will double file size but preserve all 256 colors (8 bit).\n"
+"\n"
+"Eink only has 16 shades of gray so you probably don't want this.", None))
+#endif // QT_CONFIG(tooltip)
+ self.noQuantizeBox.setText(QCoreApplication.translate("mainWindow", u"No Quantize", None))
+#if QT_CONFIG(tooltip)
+ self.eraseRainbowBox.setToolTip(QCoreApplication.translate("mainWindow", u"Erase rainbow effect on color eink screen by attenuating interfering frequencies", None))
+#endif // QT_CONFIG(tooltip)
+ self.eraseRainbowBox.setText(QCoreApplication.translate("mainWindow", u"Rainbow eraser", None))
+#if QT_CONFIG(tooltip)
+ 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))
+#if QT_CONFIG(tooltip)
+ self.rotateRightBox.setToolTip(QCoreApplication.translate("mainWindow", u"Rotate 2 page spreads in opposite direction than normal.", None))
+#endif // QT_CONFIG(tooltip)
+ self.rotateRightBox.setText(QCoreApplication.translate("mainWindow", u"Rotate Right", None))
+#if QT_CONFIG(tooltip)
+ self.mangaBox.setToolTip(QCoreApplication.translate("mainWindow", u"Enable right-to-left reading.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.mangaBox.setText(QCoreApplication.translate("mainWindow", u"Right-to-left (manga)", None))
+#if QT_CONFIG(tooltip)
+ self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None))
+#endif // QT_CONFIG(tooltip)
+ self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None))
+#if QT_CONFIG(tooltip)
+ self.croppingBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Disabled
Disabled
Indeterminate - Margins
Margins
Checked - Margins + page numbers
Margins +page numbers
", None))
+#endif // QT_CONFIG(tooltip)
+ self.croppingBox.setText(QCoreApplication.translate("mainWindow", u"Cropping mode", None))
+#if QT_CONFIG(tooltip)
+ 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.outputSplit.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Automatic mode
The output will be split automatically.
Checked - Volume mode
Every subdirectory will be considered as a separate volume.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.outputSplit.setText(QCoreApplication.translate("mainWindow", u"Output split", None))
#if QT_CONFIG(tooltip)
self.metadataTitleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Don't use metadata Title
Write default title.
Indeterminate - Add metadata Title to the default schema
Write default title with Title from ComicInfo.xml or other embedded metadata.
Checked - Use metadata Title only
Write Title from ComicInfo.xml or other embedded metadata.
", None))
#endif // QT_CONFIG(tooltip)
self.metadataTitleBox.setText(QCoreApplication.translate("mainWindow", u"Metadata Title", None))
#if QT_CONFIG(tooltip)
- self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - JPEG
Use JPEG files
Indeterminate - force PNG
Create PNG files instead JPEG
Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2
", None))
+ self.smartCoverCropBox.setToolTip(QCoreApplication.translate("mainWindow", u"Attempt to crop main cover from wide image.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.smartCoverCropBox.setText(QCoreApplication.translate("mainWindow", u"Smart Cover Crop", None))
+#if QT_CONFIG(tooltip)
+ self.rotateFirstBox.setToolTip(QCoreApplication.translate("mainWindow", u"When the spread splitter option is partially checked,
Unchecked - Rotate Last
Put the rotated 2 page spread after the split spreads.
Checked - Rotate First
Put the rotated 2 page spread before the split spreads.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.rotateFirstBox.setText(QCoreApplication.translate("mainWindow", u"Rotate First", None))
+#if QT_CONFIG(tooltip)
+ self.mozJpegBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - JPEG
Use JPEG files
Indeterminate - force PNG
Create PNG files instead JPEG for black and white images
Checked - mozJpeg
10-20% smaller JPEG file, with the same image quality, but processing time multiplied by 2
", None))
#endif // QT_CONFIG(tooltip)
self.mozJpegBox.setText(QCoreApplication.translate("mainWindow", u"JPEG/PNG/mozJpeg", None))
#if QT_CONFIG(tooltip)
@@ -627,9 +769,41 @@ class Ui_mainWindow(object):
#endif // QT_CONFIG(tooltip)
self.autoLevelBox.setText(QCoreApplication.translate("mainWindow", u"Extreme Black Point", None))
#if QT_CONFIG(tooltip)
- self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.
Indeterminate - Disabled
Disable autocontrast
Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.
", None))
+ self.forcePngRgbBox.setToolTip(QCoreApplication.translate("mainWindow", u"Force full color images to be saved in lossless PNG format, dramatically increases the filesize.", None))
#endif // QT_CONFIG(tooltip)
- self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
+ self.forcePngRgbBox.setText(QCoreApplication.translate("mainWindow", u"Force PNG RGB", None))
+#if QT_CONFIG(tooltip)
+ self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Nothing
Images smaller than device resolution will not be resized.
Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.
Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.upscaleBox.setText(QCoreApplication.translate("mainWindow", u"Stretch/Upscale", None))
+#if QT_CONFIG(tooltip)
+ self.borderBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Autodetection
The color of margins fill will be detected automatically.
Indeterminate - White
Margins will be untouched.
Checked - Black
Margins will be filled with black color.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.borderBox.setText(QCoreApplication.translate("mainWindow", u"W/B margins", None))
+#if QT_CONFIG(tooltip)
+ self.qualityBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - 4 panels
Zoom each corner separately.
Indeterminate - 2 panels
Zoom only the top and bottom of the page.
Checked - 4 high-quality panels
Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.qualityBox.setText(QCoreApplication.translate("mainWindow", u"Panel View 4/2/HQ", None))
+#if QT_CONFIG(tooltip)
+ 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.pdfExtractBox.setText(QCoreApplication.translate("mainWindow", u"PDF Legacy Extract", None))
+#if QT_CONFIG(tooltip)
+ self.colorBox.setToolTip(QCoreApplication.translate("mainWindow", u"Disable conversion to grayscale.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.colorBox.setText(QCoreApplication.translate("mainWindow", u"Color mode", None))
+#if QT_CONFIG(tooltip)
+ self.pdfWidthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Render vector PDFs to device width instead of height.\n"
+"\n"
+"Useful if you plan to crop a little off the top and bottom to fill screen.", None))
+#endif // QT_CONFIG(tooltip)
+ self.pdfWidthBox.setText(QCoreApplication.translate("mainWindow", u"PDF Width Render", None))
+#if QT_CONFIG(tooltip)
+ self.disableProcessingBox.setToolTip(QCoreApplication.translate("mainWindow", u"Do not process any image, ignore profile and processing options.
", None))
+#endif // QT_CONFIG(tooltip)
+ self.disableProcessingBox.setText(QCoreApplication.translate("mainWindow", u"Disable processing", None))
#if QT_CONFIG(tooltip)
self.defaultOutputFolderBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - next to source
Place output files next to source files
Indeterminate - folder next to source
Place output files in a folder next to source files
Checked - Custom
Place output files in custom directory specified by right button
", None))
#endif // QT_CONFIG(tooltip)
@@ -639,34 +813,23 @@ class Ui_mainWindow(object):
#endif // QT_CONFIG(tooltip)
self.defaultOutputFolderButton.setText("")
#if QT_CONFIG(tooltip)
- self.jobList.setToolTip(QCoreApplication.translate("mainWindow", u"Double click on source to open it in metadata editor.
", None))
+ self.chunkSizeCheckBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked
Maximal output file size is 100 MB for Webtoon, 400 MB for others before split occurs.
Checked
Output file size specified in "Chunk size MB" before split occurs.
", None))
#endif // QT_CONFIG(tooltip)
+ self.chunkSizeCheckBox.setText(QCoreApplication.translate("mainWindow", u"Chunk size", None))
#if QT_CONFIG(tooltip)
- self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"Resolution of the target device.
", None))
+ self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - BW only
Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.
Indeterminate - Disabled
Disable autocontrast
Checked - BW and Color
BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.
", None))
#endif // QT_CONFIG(tooltip)
- self.hLabel.setText(QCoreApplication.translate("mainWindow", u"Custom height:", None))
+ self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Custom Autocontrast", None))
#if QT_CONFIG(tooltip)
- self.widthBox.setToolTip(QCoreApplication.translate("mainWindow", u"Resolution of the target device.
", None))
+ self.webpBox.setToolTip(QCoreApplication.translate("mainWindow", u"Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.\n"
+"\n"
+"Ignored for Kindle EPUB/MOBI and all PDF.", None))
#endif // QT_CONFIG(tooltip)
+ self.webpBox.setText(QCoreApplication.translate("mainWindow", u"WebP (experimental)", None))
#if QT_CONFIG(tooltip)
- self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"Resolution of the target device.
", None))
+ self.tempDirBox.setToolTip(QCoreApplication.translate("mainWindow", u"Unchecked - Main Drive
Use dedicated temporary directory on main OS drive.
Checked - Source File Drive
Create temporary file directory on source file drive.
", 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"Resolution of the target device.
", None))
-#endif // QT_CONFIG(tooltip)
- self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
-#if QT_CONFIG(tooltip)
- self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"Shift+Click to edit directory.
", 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"After calculating the cropping boundaries, "back up" a specified percentage amount.
", 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))
+ self.tempDirBox.setText(QCoreApplication.translate("mainWindow", u"Temp Directory", None))
#if QT_CONFIG(tooltip)
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"Shift+Click to select the output directory for this list.
", None))
#endif // QT_CONFIG(tooltip)
@@ -686,10 +849,20 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"Output format.
", None))
#endif // QT_CONFIG(tooltip)
+ self.gammaLabel.setText(QCoreApplication.translate("mainWindow", u"Gamma: Auto", None))
#if QT_CONFIG(tooltip)
- self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"Warning: chunk size greater than default may cause
performance/battery issues, especially on older devices.
", None))
+ self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"Resolution of the target device.
", 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"Resolution of the target device.
", None))
+#endif // QT_CONFIG(tooltip)
+#if QT_CONFIG(tooltip)
+ self.wLabel.setToolTip(QCoreApplication.translate("mainWindow", u"Resolution of the target device.
", 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"Resolution of the target device.
", 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))
# retranslateUi
diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py
index 749d3e7..c8f615d 100644
--- a/kindlecomicconverter/__init__.py
+++ b/kindlecomicconverter/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '9.3.7'
+__version__ = '10.1.3'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano , Pawel Jastrzebski , darodi'
__docformat__ = 'restructuredtext en'
diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py
index 8cb784f..8cd316c 100755
--- a/kindlecomicconverter/comic2ebook.py
+++ b/kindlecomicconverter/comic2ebook.py
@@ -18,6 +18,7 @@
# PERFORMANCE OF THIS SOFTWARE.
#
+from collections import Counter
import os
import pathlib
import re
@@ -43,11 +44,12 @@ from psutil import virtual_memory, disk_usage
from html import escape as hescape
import pymupdf
-from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
+from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean, get_contain_resolution
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 +67,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)
@@ -135,14 +137,14 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None):
"content=\"width=" + str(imgsizeframe[0]) + ", height=" + str(imgsizeframe[1]) + "\"/>\n"
"\n",
"\n",
- "\n",
+ "
\n",
])
if options.iskindle:
# this display none div fixes formatting issues with virtual panel mode, for some reason
f.write('
.
\n')
f.write(f'

\n')
if imgfile2:
- f.write(f'

\n')
+ f.write(f'

\n')
f.write("
\n")
if options.iskindle and options.panelview:
if options.autoscale:
@@ -309,13 +311,33 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
f.writelines(["
", hescape(options.summary), "\n"])
for author in options.authors:
f.writelines(["
", hescape(author), "\n"])
+ if not options.iskindle and options.series:
+ f.writelines(['
', hescape(options.series), "\n"])
+ f.writelines(['
', "series", "\n"])
+ if options.volume and options.number:
+ f.writelines(['
', hescape(f"{options.volume}.{options.number}"), "\n"])
+ elif options.volume:
+ f.writelines(['
', hescape(options.volume), "\n"])
+ elif options.number:
+ f.writelines(['
', hescape(options.number), "\n"])
f.write("
" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "\n")
if cover:
f.write("
\n")
if options.iskindle and options.profile != 'Custom':
f.writelines(["
\n",
- "
\n",
+ ])
+ if not options.kfx_resolution:
+ f.writelines([
+ "
\n",
+ ])
+ else:
+ x, y = options.kfx_resolution
+ f.writelines([
+ "
\n",
+ ])
+ f.writelines([
"
\n",
"
\n",
"
\n",
@@ -352,6 +374,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("
- 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.")
@@ -688,7 +712,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:
@@ -698,18 +721,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:
@@ -739,7 +768,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
@@ -750,7 +781,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
@@ -800,9 +834,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)
@@ -817,7 +849,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:
@@ -837,7 +869,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))
@@ -851,14 +883,22 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
-def getWorkFolder(afile):
+def getWorkFolder(afile, workdir=None):
+ if not workdir:
+ if options.tempdir:
+ workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
+ else:
+ workdir = mkdtemp('', 'KCC-')
+ fullPath = os.path.join(workdir, 'OEBPS', 'Images')
+ else:
+ fullPath = workdir
+ check_path = gettempdir()
+ if options.tempdir:
+ check_path = os.path.dirname(afile)
if os.path.isdir(afile):
- if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
+ if disk_usage(check_path)[2] < getDirectorySize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
- workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
try:
- os.rmdir(workdir)
- fullPath = os.path.join(workdir, 'OEBPS', 'Images')
copytree(afile, fullPath)
sanitizePermissions(fullPath)
return workdir
@@ -866,29 +906,33 @@ def getWorkFolder(afile):
rmtree(workdir, True)
raise UserWarning("Failed to prepare a workspace.")
elif os.path.isfile(afile):
- if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
+ if disk_usage(check_path)[2]< os.path.getsize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
if afile.lower().endswith('.pdf'):
- workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
- fullPath = os.path.join(workdir, 'OEBPS', 'Images')
if not os.path.exists(fullPath):
os.makedirs(fullPath)
path = workdir
sanitizePermissions(path)
- target_height = options.profileData[1][1]
+ 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_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}")
return workdir
else:
- workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
- fullPath = os.path.join(workdir, 'OEBPS', 'Images')
if not os.path.exists(fullPath):
os.makedirs(fullPath)
try:
@@ -959,6 +1003,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
@@ -967,6 +1018,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):
@@ -995,8 +1049,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
@@ -1014,6 +1070,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'):
@@ -1033,11 +1091,6 @@ def getDirectorySize(start_path='.'):
return total_size
-def getTopMargin(deviceres, size):
- y = int((deviceres[1] - size[1]) / 2) / deviceres[1] * 100
- return str(round(y, 1))
-
-
def getPanelViewResolution(imagesize, deviceres):
scale = float(deviceres[0]) / float(imagesize[0])
return int(deviceres[0]), int(scale * imagesize[1])
@@ -1068,7 +1121,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
@@ -1078,7 +1131,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)
@@ -1127,7 +1180,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
@@ -1239,7 +1292,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:
@@ -1271,12 +1324,15 @@ 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)
+ 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):
@@ -1339,11 +1395,21 @@ 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.")
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("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
+ help="Render vector PDFs to device width instead of height.")
+ processing_options.add_argument("--smartcovercrop", action="store_true", dest="smartcovercrop", default=False,
+ help="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,
help="Resize images smaller than device's resolution")
processing_options.add_argument("-s", "--stretch", action="store_true", dest="stretch", default=False,
@@ -1379,13 +1445,25 @@ 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",
+ 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,
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")
@@ -1404,6 +1482,20 @@ def checkOptions(options):
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
+
+ if options.profile in image.ProfileData.ProfilesKindle.keys():
+ options.iskindle = True
+ else:
+ options.isKobo = True
+
+ if not options.iskindle and ('MOBI' in options.format or 'EPUB-200MB' in options.format or 'KFX' in options.format):
+ raise UserWarning('MOBI/Send to Kindle not supported for non-Kindle profiles')
+
+ 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'
@@ -1415,6 +1507,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'
@@ -1428,10 +1522,7 @@ def checkOptions(options):
options.format = 'PDF'
else:
options.format = 'EPUB'
- if options.profile in image.ProfileData.ProfilesKindle.keys():
- options.iskindle = True
- else:
- options.isKobo = True
+
if options.white_borders:
options.bordersColor = 'white'
if options.black_borders:
@@ -1460,11 +1551,9 @@ 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.targetsize = 195
options.format = 'EPUB'
options.kfx = True
options.panelview = False
@@ -1481,6 +1570,27 @@ 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
+
+ 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])
+ options.profileData[1][0] = min(1920, options.profileData[1][0])
return options
@@ -1528,21 +1638,28 @@ 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...")
- path = getWorkFolder(source)
- pathfinder = os.path.join(path, "OEBPS", "Images")
- sanitizeTree(pathfinder)
- # TODO: remove flattenTree when subchapters are supported
- flattenTree(pathfinder)
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():
- os.renames(pathfinder, fusion_path.joinpath(source_path.stem))
+ targetpath = fusion_path.joinpath(f'{prefix}{source_path.stem}')
else:
- os.renames(pathfinder, fusion_path.joinpath(source_path.name))
-
+ targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}')
+
+ getWorkFolder(source, str(targetpath))
+ sanitizeTree(targetpath, prefix='fusion')
+ # TODO: remove flattenTree when subchapters are supported
+ flattenTree(targetpath)
end = perf_counter()
print(f"makefusion: {end - start} seconds")
@@ -1559,8 +1676,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)
@@ -1569,13 +1684,50 @@ 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)
+ x, y = image.ProfileData.Profiles[options.profile][1]
if options.webtoon:
- x, y = image.ProfileData.Profiles[options.profile][1]
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui)
+
+ options.kfx_resolution = None
+ if options.kfx:
+ original_resolutions = []
+ normalized_resolutions = []
+ for root, _, files in os.walk(os.path.join(path, "OEBPS", "Images")):
+ for file in files:
+ with Image.open(os.path.join(root, file)) as imagef:
+ original_resolutions.append(imagef.size)
+ size = get_contain_resolution(imagef, (x, y))
+ normalized_resolutions.append(size)
+
+ counter = Counter(normalized_resolutions)
+
+ aspect_ratios = []
+ filtered_resolutions = []
+ for w, h in normalized_resolutions:
+ aspect_ratio = h / w
+ # page-like aspect ratios, could be improved
+ if aspect_ratio > 1.3 and aspect_ratio < 1.7:
+ aspect_ratios.append(aspect_ratio)
+ filtered_resolutions.append((w, h))
+
+ most_common_res, most_common_count = counter.most_common(1)[0]
+ options.kfx_resolution = most_common_res
+ if most_common_count / sum(counter.values()) > .6:
+ pass
+ #elif max(aspect_ratios) - min(aspect_ratios) < .2:
+ else:
+ # get the widest resolution
+ options.kfx_resolution = max(filtered_resolutions)
+ # else:
+ # raise UserWarning('Aspect ratio of pages too different for KFX conversion')
+
if options.noprocessing:
print(f"{job_progress}Do not process image, ignore any profile or processing option")
else:
@@ -1616,12 +1768,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 and 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 and cover.smartcover:
+ cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
# use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
filepath.append(output_pdf)
@@ -1760,4 +1916,3 @@ def makeMOBI(work, qtgui=None):
makeMOBIWorkerPool.close()
makeMOBIWorkerPool.join()
return makeMOBIWorkerOutput
-
diff --git a/kindlecomicconverter/comic2panel.py b/kindlecomicconverter/comic2panel.py
index ccdbb06..3cb2e9f 100644
--- a/kindlecomicconverter/comic2panel.py
+++ b/kindlecomicconverter/comic2panel.py
@@ -62,18 +62,19 @@ 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
for i in imagesValid:
- img = Image.open(i).convert('RGB')
- if img.size[0] < targetWidth or img.size[0] > targetWidth:
- widthPercent = (targetWidth / float(img.size[0]))
- heightSize = int((float(img.size[1]) * float(widthPercent)))
- img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5))
- result.paste(img, (0, y))
- y += img.size[1]
+ with Image.open(i) as img:
+ img = img.convert('RGB')
+ if img.size[0] < targetWidth or img.size[0] > targetWidth:
+ widthPercent = (targetWidth / float(img.size[0]))
+ heightSize = int((float(img.size[1]) * float(widthPercent)))
+ img = ImageOps.fit(img, (targetWidth, heightSize), method=Image.BICUBIC, centering=(0.5, 0.5))
+ result.paste(img, (0, y))
+ y += img.size[1]
os.remove(i)
savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
@@ -253,10 +254,8 @@ def main(argv=None, job_progress='', qtgui=None):
return 1
if args.height > 0:
for sourceDir in args.input:
- targetDir = sourceDir + "-Splitted"
+ targetDir = sourceDir
if os.path.isdir(sourceDir):
- rmtree(targetDir, True)
- os.renames(sourceDir, targetDir)
work = []
pagenumber = 1
splitWorkerOutput = []
@@ -313,8 +312,6 @@ def main(argv=None, job_progress='', qtgui=None):
rmtree(targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0],
splitWorkerOutput[0][1])
- if args.inPlace:
- os.renames(targetDir, sourceDir)
else:
rmtree(targetDir, True)
raise UserWarning("C2P: Source directory is empty.")
diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py
index f3b6caa..0acfc76 100644
--- a/kindlecomicconverter/comicarchive.py
+++ b/kindlecomicconverter/comicarchive.py
@@ -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)
diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py
index fc7e07b..8b5486e 100755
--- a/kindlecomicconverter/image.py
+++ b/kindlecomicconverter/image.py
@@ -29,6 +29,7 @@ from PIL import Image, ImageOps, ImageFile, ImageChops, ImageDraw
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
from .inter_panel_crop_alg import crop_empty_inter_panel
+from .shared import get_contain_resolution
AUTO_CROP_THRESHOLD = 0.015
ImageFile.LOAD_TRUNCATED_IMAGES = True
@@ -98,11 +99,18 @@ 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),
- 'KS': ("Kindle Scribe", (1860, 2480), Palette16, 1.0),
- 'KCS': ("Kindle Colorsoft", (1264, 1680), 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),
+ 'KS1324': ("Kindle 1324", (1324, 1986), Palette16, 1.0),
+ 'KS': ("Kindle Scribe 1/2", (1860, 2480), 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),
}
ProfilesKindle = {
@@ -153,7 +161,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()
@@ -188,14 +196,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)
@@ -210,10 +225,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])
@@ -266,6 +286,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])
@@ -284,8 +306,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:
@@ -394,25 +415,32 @@ 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=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)
+ targetPath += '.jpg'
+ image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
return targetPath
def gammaCorrectImage(self):
@@ -485,26 +513,38 @@ class ComicPage:
if self.opt.kindle_azw3 and any(dim > 1920 for dim in self.image.size):
self.image = ImageOps.contain(self.image, (1920, 1920), Image.Resampling.LANCZOS)
elif self.image.size[0] > self.size[0] * 2 or self.image.size[1] > self.size[1]:
- self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1], Image.Resampling.LANCZOS))
+ self.image = ImageOps.contain(self.image, (self.size[0] * 2, self.size[1]), Image.Resampling.LANCZOS)
return
ratio_device = float(self.size[1]) / float(self.size[0])
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
method = self.resize_method()
- if self.opt.stretch:
+ if self.opt.kfx:
+ ratio_kfx = self.opt.kfx_resolution[1] / self.opt.kfx_resolution[0]
+ contain_size = get_contain_resolution(self.image, self.size)
+ if abs(ratio_image - ratio_kfx) < AUTO_CROP_THRESHOLD:
+ if contain_size[0] > self.opt.kfx_resolution[0] or contain_size[1] > self.opt.kfx_resolution[1]:
+ self.image = ImageOps.fit(self.image, self.opt.kfx_resolution, method=method)
+ else:
+ self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
+ else:
+ self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
+ elif self.opt.stretch:
self.image = self.image.resize(self.size, method)
elif 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 (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
+ 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')) and not self.opt.white_borders:
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
else:
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
@@ -548,6 +588,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
@@ -558,30 +599,56 @@ 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 self.options.smartcovercrop:
+ self.crop_main_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
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=85)
+ self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)
else:
copy = self.image.copy()
draw = ImageDraw.Draw(copy)
@@ -595,7 +662,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 +670,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.')
diff --git a/kindlecomicconverter/page_number_crop_alg.py b/kindlecomicconverter/page_number_crop_alg.py
index f92bcb9..78f182c 100644
--- a/kindlecomicconverter/page_number_crop_alg.py
+++ b/kindlecomicconverter/page_number_crop_alg.py
@@ -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)
diff --git a/kindlecomicconverter/pdfjpgextract.py b/kindlecomicconverter/pdfjpgextract.py
new file mode 100644
index 0000000..751a68e
--- /dev/null
+++ b/kindlecomicconverter/pdfjpgextract.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2012-2014 Ciro Mattia Gonano
+# Copyright (c) 2013-2019 Pawel Jastrzebski
+#
+# 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
diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py
index 4a82f23..857b6cc 100644
--- a/kindlecomicconverter/shared.py
+++ b/kindlecomicconverter/shared.py
@@ -61,6 +61,23 @@ def getImageFileName(imgfile):
ext = ext.lower()
return [name, ext]
+def get_contain_resolution(image, size):
+ '''same code as Pillow ImageOps.contain()'''
+ im_ratio = image.width / image.height
+ dest_ratio = size[0] / size[1]
+
+ if im_ratio != dest_ratio:
+ if im_ratio > dest_ratio:
+ new_height = round(image.height / image.width * size[0])
+ if new_height != size[1]:
+ size = (size[0], new_height)
+ else:
+ new_width = round(image.width / image.height * size[1])
+ if new_width != size[0]:
+ size = (new_width, size[1])
+
+ return size
+
def walkSort(dirnames, filenames):
convert = lambda text: int(text) if text.isdigit() else text
diff --git a/setup.py b/setup.py
index bd17012..f791414 100644
--- a/setup.py
+++ b/setup.py
@@ -40,7 +40,7 @@ class BuildBinaryCommand(setuptools.Command):
if sys.platform == 'darwin':
os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
- min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET')
+ min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '')
if min_os.startswith('10.1'):
os.system(f'appdmg kcc.json dist/kcc_osx_{min_os.replace(".", "_")}_legacy_{VERSION}.dmg')
else: