mirror of
https://github.com/ciromattia/kcc
synced 2026-04-18 06:58:58 +00:00
Compare commits
1 Commits
v9.7.1
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87acd1d7f7 |
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository_owner }}/kcc
|
images: ghcr.io/${{ github.repository_owner }}/kcc
|
||||||
# Always creates the "latest" tag
|
# Always creates the "latest" tag
|
||||||
|
|||||||
2
.github/workflows/package-linux.yml
vendored
2
.github/workflows/package-linux.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libpng-dev libjpeg-dev p7zip-full p7zip-rar python3-pip squashfs-tools libfuse2 libxcb-cursor0
|
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 certifi pyinstaller --no-binary pyinstaller
|
python -m pip install --upgrade pip setuptools wheel certifi pyinstaller --no-binary pyinstaller
|
||||||
python -m pip install -r requirements.txt
|
python -m pip install -r requirements.txt
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/package-macos.yml
vendored
2
.github/workflows/package-macos.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
- name: Install python dependencies
|
- name: Install python dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip pyinstaller certifi
|
python -m pip install --upgrade pip setuptools wheel pyinstaller certifi
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
- name: Install the Apple certificate and provisioning profile
|
- name: Install the Apple certificate and provisioning profile
|
||||||
# TODO signing
|
# TODO signing
|
||||||
|
|||||||
2
.github/workflows/package-osx-legacy.yml
vendored
2
.github/workflows/package-osx-legacy.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Install Python dependencies
|
- name: Install Python dependencies
|
||||||
run: |
|
run: |
|
||||||
python3 --version
|
python3 --version
|
||||||
pip3 install --upgrade pip pyinstaller certifi
|
pip3 install --upgrade pip setuptools wheel pyinstaller certifi
|
||||||
pip3 install --upgrade -r requirements-osx-legacy.txt
|
pip3 install --upgrade -r requirements-osx-legacy.txt
|
||||||
./gen_ui_files.sh
|
./gen_ui_files.sh
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
|
|||||||
2
.github/workflows/package-windows.yml
vendored
2
.github/workflows/package-windows.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
pip install certifi pyinstaller --no-binary pyinstaller
|
pip install certifi pyinstaller --no-binary pyinstaller
|
||||||
- name: build binary
|
- name: build binary
|
||||||
|
|||||||
13
.github/workflows/package-windows7.yml
vendored
13
.github/workflows/package-windows7.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
pip install -r requirements-win7.txt
|
pip install -r requirements-win7.txt
|
||||||
pip install certifi pyinstaller --no-binary pyinstaller
|
pip install certifi pyinstaller --no-binary pyinstaller
|
||||||
.\gen_ui_files.bat
|
.\gen_ui_files.bat
|
||||||
@@ -50,17 +50,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: windows7-build
|
name: windows7-build
|
||||||
path: dist/*.exe
|
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
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ FROM python:3.13-slim-bullseye AS builder
|
|||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN set -x && \
|
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" && \
|
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" && \
|
||||||
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \
|
RUNTIME_DEPS="bash ca-certificates chrpath locales locales-all libfreetype6 libfontconfig1 p7zip-full python3 python3-pip libgl1" && \
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
|
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
|
||||||
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
|
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -249,7 +249,6 @@ MAIN:
|
|||||||
PROCESSING:
|
PROCESSING:
|
||||||
-n, --noprocessing Do not modify image and ignore any profile or processing option
|
-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.
|
--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
|
-u, --upscale Resize images smaller than device's resolution
|
||||||
-s, --stretch Stretch images to device's resolution
|
-s, --stretch Stretch images to device's resolution
|
||||||
-r SPLITTER, --splitter SPLITTER
|
-r SPLITTER, --splitter SPLITTER
|
||||||
@@ -270,19 +269,13 @@ PROCESSING:
|
|||||||
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
|
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
|
||||||
--blackborders Disable autodetection and force black borders
|
--blackborders Disable autodetection and force black borders
|
||||||
--whiteborders Disable autodetection and force white borders
|
--whiteborders Disable autodetection and force white borders
|
||||||
--nosmartcovercrop Disable attempt to crop main cover from wide image
|
|
||||||
--coverfill Center-crop only the cover to fill target device screen
|
--coverfill Center-crop only the cover to fill target device screen
|
||||||
--forcecolor Don't convert images to grayscale
|
--forcecolor Don't convert images to grayscale
|
||||||
--forcepng Create PNG files instead JPEG for black and white images
|
--forcepng Create PNG files instead JPEG
|
||||||
--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
|
--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.
|
--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
|
--maximizestrips Turn 1x4 strips to 2x2 strips
|
||||||
-d, --delete Delete source file(s) or a directory. It's not recoverable.
|
-d, --delete Delete source file(s) or a directory. It's not recoverable.
|
||||||
--tempdir Create temporary files directory on source file drive.
|
|
||||||
|
|
||||||
OUTPUT SETTINGS:
|
OUTPUT SETTINGS:
|
||||||
-o OUTPUT, --output OUTPUT
|
-o OUTPUT, --output OUTPUT
|
||||||
@@ -299,7 +292,6 @@ OUTPUT SETTINGS:
|
|||||||
Split output into multiple files. 0: Don't split 1: Automatic mode 2: Consider every subdirectory as separate volume [Default=0]
|
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
|
--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.
|
--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.
|
--rotatefirst Put rotated spread first in spread splitter option.
|
||||||
--filefusion Combines all input files into a single file.
|
--filefusion Combines all input files into a single file.
|
||||||
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
--eraserainbow Erase rainbow effect on color eink screen by attenuating interfering frequencies
|
||||||
|
|||||||
1460
gui/KCC.ui
1460
gui/KCC.ui
File diff suppressed because it is too large
Load Diff
@@ -195,7 +195,7 @@ class VersionThread(QThread):
|
|||||||
icon = 'bindle'
|
icon = 'bindle'
|
||||||
if category == 'kofi':
|
if category == 'kofi':
|
||||||
icon = 'kofi'
|
icon = 'kofi'
|
||||||
message = f"{payload.get('name')}"
|
message = f"<b>{payload.get('name')}</b>"
|
||||||
if payload.get('link'):
|
if payload.get('link'):
|
||||||
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
|
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
|
||||||
if payload.get('showDeadline'):
|
if payload.get('showDeadline'):
|
||||||
@@ -329,10 +329,6 @@ class WorkerThread(QThread):
|
|||||||
options.noprocessing = True
|
options.noprocessing = True
|
||||||
if GUI.pdfExtractBox.isChecked():
|
if GUI.pdfExtractBox.isChecked():
|
||||||
options.pdfextract = True
|
options.pdfextract = True
|
||||||
if GUI.pdfWidthBox.isChecked():
|
|
||||||
options.pdfwidth = True
|
|
||||||
if GUI.noSmartCoverCropBox.isChecked():
|
|
||||||
options.nosmartcovercrop = True
|
|
||||||
if GUI.coverFillBox.isChecked():
|
if GUI.coverFillBox.isChecked():
|
||||||
options.coverfill = True
|
options.coverfill = True
|
||||||
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
|
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||||
@@ -341,8 +337,6 @@ class WorkerThread(QThread):
|
|||||||
options.metadatatitle = 2
|
options.metadatatitle = 2
|
||||||
if GUI.deleteBox.isChecked():
|
if GUI.deleteBox.isChecked():
|
||||||
options.delete = True
|
options.delete = True
|
||||||
if GUI.tempDirBox.isChecked():
|
|
||||||
options.tempdir = True
|
|
||||||
if GUI.spreadShiftBox.isChecked():
|
if GUI.spreadShiftBox.isChecked():
|
||||||
options.spreadshift = True
|
options.spreadshift = True
|
||||||
if GUI.fileFusionBox.isChecked():
|
if GUI.fileFusionBox.isChecked():
|
||||||
@@ -351,22 +345,12 @@ class WorkerThread(QThread):
|
|||||||
options.filefusion = False
|
options.filefusion = False
|
||||||
if GUI.noRotateBox.isChecked():
|
if GUI.noRotateBox.isChecked():
|
||||||
options.norotate = True
|
options.norotate = True
|
||||||
if GUI.rotateRightBox.isChecked():
|
|
||||||
options.rotateright = True
|
|
||||||
if GUI.rotateFirstBox.isChecked():
|
if GUI.rotateFirstBox.isChecked():
|
||||||
options.rotatefirst = True
|
options.rotatefirst = True
|
||||||
if GUI.forcePngRgbBox.isChecked():
|
|
||||||
options.force_png_rgb = True
|
|
||||||
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||||
options.forcepng = True
|
options.forcepng = True
|
||||||
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
||||||
options.mozjpeg = True
|
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():
|
if GUI.jpegQualityBox.isChecked():
|
||||||
options.jpegquality = GUI.jpegQualitySpinBox.value()
|
options.jpegquality = GUI.jpegQualitySpinBox.value()
|
||||||
if GUI.currentMode > 2:
|
if GUI.currentMode > 2:
|
||||||
@@ -698,18 +682,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
# noinspection PyCallByClass
|
# noinspection PyCallByClass
|
||||||
QDesktopServices.openUrl(QUrl('https://ko-fi.com/eink_dude'))
|
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):
|
def modeChange(self, mode):
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
self.currentMode = 1
|
self.currentMode = 1
|
||||||
@@ -844,14 +816,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
if bad_format in current_format:
|
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')
|
self.addMessage('Scribe PNG MOBI/EPUB has a lot of problems like blank pages/sections. Use JPG instead.', 'warning')
|
||||||
break
|
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):
|
def togglechunkSizeCheckBox(self, value):
|
||||||
GUI.chunkSizeWidget.setVisible(value)
|
GUI.chunkSizeWidget.setVisible(value)
|
||||||
@@ -919,10 +883,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
if bad_format in current_format:
|
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')
|
self.addMessage('Colorsoft MOBI/EPUB can have blank pages. Just go back a few pages, exit, and reenter book.', 'info')
|
||||||
break
|
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']:
|
if not profile['PVOptions']:
|
||||||
GUI.qualityBox.setChecked(False)
|
GUI.qualityBox.setChecked(False)
|
||||||
if str(GUI.deviceBox.currentText()) == 'Other':
|
if str(GUI.deviceBox.currentText()) == 'Other':
|
||||||
@@ -949,7 +909,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
elif not GUI.webtoonBox.isChecked():
|
elif not GUI.webtoonBox.isChecked():
|
||||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||||
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
|
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
|
||||||
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||||
|
|
||||||
def stripTags(self, html):
|
def stripTags(self, html):
|
||||||
s = HTMLStripper()
|
s = HTMLStripper()
|
||||||
@@ -1078,26 +1038,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
|
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
|
||||||
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
|
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
|
||||||
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
|
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
|
||||||
'pdfWidthBox': GUI.pdfWidthBox.checkState(),
|
|
||||||
'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(),
|
|
||||||
'coverFillBox': GUI.coverFillBox.checkState(),
|
'coverFillBox': GUI.coverFillBox.checkState(),
|
||||||
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
|
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
|
||||||
'mozJpegBox': GUI.mozJpegBox.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(),
|
'jpegQualityBox': GUI.jpegQualityBox.checkState(),
|
||||||
'jpegQuality': GUI.jpegQualitySpinBox.value(),
|
'jpegQuality': GUI.jpegQualitySpinBox.value(),
|
||||||
'widthBox': GUI.widthBox.value(),
|
'widthBox': GUI.widthBox.value(),
|
||||||
'heightBox': GUI.heightBox.value(),
|
'heightBox': GUI.heightBox.value(),
|
||||||
'deleteBox': GUI.deleteBox.checkState(),
|
'deleteBox': GUI.deleteBox.checkState(),
|
||||||
'tempDirBox': GUI.tempDirBox.checkState(),
|
|
||||||
'spreadShiftBox': GUI.spreadShiftBox.checkState(),
|
'spreadShiftBox': GUI.spreadShiftBox.checkState(),
|
||||||
'fileFusionBox': GUI.fileFusionBox.checkState(),
|
'fileFusionBox': GUI.fileFusionBox.checkState(),
|
||||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
|
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
|
||||||
'noRotateBox': GUI.noRotateBox.checkState(),
|
'noRotateBox': GUI.noRotateBox.checkState(),
|
||||||
'rotateRightBox': GUI.rotateRightBox.checkState(),
|
|
||||||
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
|
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
|
||||||
'maximizeStrips': GUI.maximizeStrips.checkState(),
|
'maximizeStrips': GUI.maximizeStrips.checkState(),
|
||||||
'gammaSlider': float(self.gammaValue) * 100,
|
'gammaSlider': float(self.gammaValue) * 100,
|
||||||
@@ -1220,7 +1172,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
'convertButton', 'formatBox']:
|
'convertButton', 'formatBox']:
|
||||||
getattr(GUI, element).setMinimumSize(QSize(0, 0))
|
getattr(GUI, element).setMinimumSize(QSize(0, 0))
|
||||||
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
|
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
|
||||||
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'gridLayout_6', 'horizontalLayout_2']:
|
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
|
||||||
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
|
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
|
||||||
if self.windowSize == '0x0':
|
if self.windowSize == '0x0':
|
||||||
MW.resize(500, 500)
|
MW.resize(500, 500)
|
||||||
@@ -1230,7 +1182,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
||||||
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
||||||
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
||||||
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
|
|
||||||
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
|
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
|
||||||
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
|
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
|
||||||
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
|
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
|
||||||
@@ -1428,9 +1379,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||||
GUI.wikiButton.clicked.connect(self.openWiki)
|
GUI.wikiButton.clicked.connect(self.openWiki)
|
||||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
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.convertButton.clicked.connect(self.convertStart)
|
||||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||||
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
|
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '9.7.1'
|
__version__ = '9.5.0'
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|||||||
@@ -310,15 +310,6 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
|||||||
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
|
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
|
||||||
for author in options.authors:
|
for author in options.authors:
|
||||||
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
|
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
|
||||||
if not options.iskindle and options.series:
|
|
||||||
f.writelines(['<meta property="belongs-to-collection" id="c02">', hescape(options.series), "</meta>\n"])
|
|
||||||
f.writelines(['<meta refines="#c02" property="collection-type">', "series", "</meta>\n"])
|
|
||||||
if options.volume and options.number:
|
|
||||||
f.writelines(['<meta refines="#c02" property="group-position">', hescape(f"{options.volume}.{options.number}"), "</meta>\n"])
|
|
||||||
elif options.volume:
|
|
||||||
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.volume), "</meta>\n"])
|
|
||||||
elif options.number:
|
|
||||||
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.number), "</meta>\n"])
|
|
||||||
f.write("<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n")
|
f.write("<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n")
|
||||||
if cover:
|
if cover:
|
||||||
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
|
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
|
||||||
@@ -362,8 +353,6 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
|||||||
mt = 'image/png'
|
mt = 'image/png'
|
||||||
elif '.gif' == filename[1]:
|
elif '.gif' == filename[1]:
|
||||||
mt = 'image/gif'
|
mt = 'image/gif'
|
||||||
elif '.webp' == filename[1]:
|
|
||||||
mt = 'image/webp'
|
|
||||||
else:
|
else:
|
||||||
mt = 'image/jpeg'
|
mt = 'image/jpeg'
|
||||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
|
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
|
||||||
@@ -556,7 +545,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
|
|||||||
f.close()
|
f.close()
|
||||||
build_html_start = perf_counter()
|
build_html_start = perf_counter()
|
||||||
if cover:
|
if cover:
|
||||||
cover.save_to_folder(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
|
cover.save_to_epub(os.path.join(path, 'OEBPS', 'Images', 'cover.jpg'), tomenumber, len_tomes)
|
||||||
dot_clean(path)
|
dot_clean(path)
|
||||||
options.covers.append((cover, options.uuid))
|
options.covers.append((cover, options.uuid))
|
||||||
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
for dirpath, dirnames, filenames in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||||
@@ -700,6 +689,7 @@ def imgFileProcessing(work):
|
|||||||
workImg = image.ComicPageParser((dirpath, afile), opt)
|
workImg = image.ComicPageParser((dirpath, afile), opt)
|
||||||
for i in workImg.payload:
|
for i in workImg.payload:
|
||||||
img = image.ComicPage(opt, *i)
|
img = image.ComicPage(opt, *i)
|
||||||
|
is_color = (opt.forcecolor and img.color)
|
||||||
if opt.cropping == 2 and not opt.webtoon:
|
if opt.cropping == 2 and not opt.webtoon:
|
||||||
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
||||||
if opt.cropping == 1 and not opt.webtoon:
|
if opt.cropping == 1 and not opt.webtoon:
|
||||||
@@ -709,24 +699,18 @@ def imgFileProcessing(work):
|
|||||||
|
|
||||||
img.gammaCorrectImage()
|
img.gammaCorrectImage()
|
||||||
|
|
||||||
if not img.colorOutput:
|
|
||||||
img.convertToGrayscale()
|
|
||||||
|
|
||||||
img.autocontrastImage()
|
img.autocontrastImage()
|
||||||
img.resizeImage()
|
img.resizeImage()
|
||||||
img.optimizeForDisplay(opt.eraserainbow, img.colorOutput)
|
img.optimizeForDisplay(opt.eraserainbow, is_color)
|
||||||
|
|
||||||
if img.colorOutput:
|
if is_color:
|
||||||
pass
|
pass
|
||||||
elif opt.forcepng:
|
elif opt.forcepng:
|
||||||
if not opt.noquantize:
|
img.convertToGrayscale()
|
||||||
|
if opt.format != 'PDF':
|
||||||
img.quantizeImage()
|
img.quantizeImage()
|
||||||
if opt.format == 'PDF':
|
else:
|
||||||
img.convertToGrayscale()
|
img.convertToGrayscale()
|
||||||
elif opt.profile == 'KDX' and opt.format == 'CBZ':
|
|
||||||
img.convertToGrayscale()
|
|
||||||
elif opt.pnglegacy:
|
|
||||||
img.convertToGrayscale()
|
|
||||||
output.append(img.saveToDir())
|
output.append(img.saveToDir())
|
||||||
return output
|
return output
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -756,9 +740,7 @@ def render_page(vector):
|
|||||||
cpu = vector[1] # number of CPUs
|
cpu = vector[1] # number of CPUs
|
||||||
filename = vector[2] # document filename
|
filename = vector[2] # document filename
|
||||||
output_dir = vector[3]
|
output_dir = vector[3]
|
||||||
target_width = vector[4]
|
target_height = vector[4]
|
||||||
target_height = vector[5]
|
|
||||||
pdf_width = vector[6]
|
|
||||||
with pymupdf.open(filename) as doc: # open the document
|
with pymupdf.open(filename) as doc: # open the document
|
||||||
num_pages = doc.page_count # get number of pages
|
num_pages = doc.page_count # get number of pages
|
||||||
|
|
||||||
@@ -769,10 +751,7 @@ def render_page(vector):
|
|||||||
|
|
||||||
for i in range(seg_from, seg_to): # work through our page segment
|
for i in range(seg_from, seg_to): # work through our page segment
|
||||||
page = doc[i]
|
page = doc[i]
|
||||||
if not pdf_width or page.rect.width > page.rect.height:
|
zoom = target_height / page.rect.height
|
||||||
zoom = target_height / page.rect.height
|
|
||||||
else:
|
|
||||||
zoom = target_width / page.rect.width
|
|
||||||
mat = pymupdf.Matrix(zoom, zoom)
|
mat = pymupdf.Matrix(zoom, zoom)
|
||||||
# TODO: decide colorspace earlier so later color check is cheaper.
|
# TODO: decide colorspace earlier so later color check is cheaper.
|
||||||
# This is actually pretty hard when you have to deal with color vector text
|
# This is actually pretty hard when you have to deal with color vector text
|
||||||
@@ -837,7 +816,7 @@ def extract_page(vector):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_height):
|
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
|
||||||
render = False
|
render = False
|
||||||
with pymupdf.open(filename) as doc:
|
with pymupdf.open(filename) as doc:
|
||||||
for page in doc:
|
for page in doc:
|
||||||
@@ -857,7 +836,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
|
|||||||
cpu = cpu_count()
|
cpu = cpu_count()
|
||||||
|
|
||||||
# make vectors of arguments for the processes
|
# make vectors of arguments for the processes
|
||||||
vectors = [(i, cpu, filename, output_dir, target_width, target_height, options.pdfwidth) for i in range(cpu)]
|
vectors = [(i, cpu, filename, output_dir, target_height) for i in range(cpu)]
|
||||||
print("Starting %i processes for '%s'." % (cpu, filename))
|
print("Starting %i processes for '%s'." % (cpu, filename))
|
||||||
|
|
||||||
|
|
||||||
@@ -874,8 +853,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
|
|||||||
def getWorkFolder(afile, workdir=None):
|
def getWorkFolder(afile, workdir=None):
|
||||||
if not workdir:
|
if not workdir:
|
||||||
workdir = mkdtemp('', 'KCC-')
|
workdir = mkdtemp('', 'KCC-')
|
||||||
if options.tempdir:
|
# workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
||||||
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
|
|
||||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||||
else:
|
else:
|
||||||
fullPath = workdir
|
fullPath = workdir
|
||||||
@@ -903,15 +881,13 @@ def getWorkFolder(afile, workdir=None):
|
|||||||
if njpg == 0:
|
if njpg == 0:
|
||||||
raise UserWarning("Failed to extract images from PDF file.")
|
raise UserWarning("Failed to extract images from PDF file.")
|
||||||
return workdir
|
return workdir
|
||||||
target_width, target_height = options.profileData[1]
|
target_height = options.profileData[1][1]
|
||||||
if options.cropping == 1:
|
if options.cropping == 1:
|
||||||
target_height *= 1.2 #Account for possible margin at the top and bottom
|
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
|
||||||
target_width *= 1.2
|
|
||||||
elif options.cropping == 2:
|
elif options.cropping == 2:
|
||||||
target_height *= 1.25 #Account for possible margin at the top and bottom with page number
|
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
|
||||||
target_width *= 1.25
|
|
||||||
try:
|
try:
|
||||||
mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height)
|
mupdf_pdf_process_pages_parallel(afile, fullPath, target_height)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rmtree(path, True)
|
rmtree(path, True)
|
||||||
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
||||||
@@ -1002,9 +978,6 @@ def getMetadata(path, originalpath):
|
|||||||
options.comicinfo_chapters = []
|
options.comicinfo_chapters = []
|
||||||
options.summary = ''
|
options.summary = ''
|
||||||
titleSuffix = ''
|
titleSuffix = ''
|
||||||
options.volume = ''
|
|
||||||
options.number = ''
|
|
||||||
options.series = ''
|
|
||||||
if options.title == 'defaulttitle':
|
if options.title == 'defaulttitle':
|
||||||
defaultTitle = True
|
defaultTitle = True
|
||||||
if os.path.isdir(originalpath):
|
if os.path.isdir(originalpath):
|
||||||
@@ -1033,10 +1006,8 @@ def getMetadata(path, originalpath):
|
|||||||
options.title = xml.data['Series']
|
options.title = xml.data['Series']
|
||||||
if xml.data['Volume']:
|
if xml.data['Volume']:
|
||||||
titleSuffix += ' Vol. ' + xml.data['Volume'].zfill(2)
|
titleSuffix += ' Vol. ' + xml.data['Volume'].zfill(2)
|
||||||
options.volume = xml.data['Volume']
|
|
||||||
if xml.data['Number']:
|
if xml.data['Number']:
|
||||||
titleSuffix += ' #' + xml.data['Number'].zfill(3)
|
titleSuffix += ' #' + xml.data['Number'].zfill(3)
|
||||||
options.number = xml.data['Number']
|
|
||||||
if options.metadatatitle == 1 and xml.data['Title']:
|
if options.metadatatitle == 1 and xml.data['Title']:
|
||||||
titleSuffix += ': ' + xml.data['Title']
|
titleSuffix += ': ' + xml.data['Title']
|
||||||
options.title += titleSuffix
|
options.title += titleSuffix
|
||||||
@@ -1054,8 +1025,6 @@ def getMetadata(path, originalpath):
|
|||||||
options.comicinfo_chapters = xml.data['Bookmarks']
|
options.comicinfo_chapters = xml.data['Bookmarks']
|
||||||
if xml.data['Summary']:
|
if xml.data['Summary']:
|
||||||
options.summary = xml.data['Summary']
|
options.summary = xml.data['Summary']
|
||||||
if xml.data['Series']:
|
|
||||||
options.series = xml.data['Series']
|
|
||||||
os.remove(xmlPath)
|
os.remove(xmlPath)
|
||||||
|
|
||||||
if originalpath.lower().endswith('.pdf'):
|
if originalpath.lower().endswith('.pdf'):
|
||||||
@@ -1316,12 +1285,12 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
|
|||||||
mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w')
|
mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w')
|
||||||
mimetypeFile.write('application/epub+zip')
|
mimetypeFile.write('application/epub+zip')
|
||||||
mimetypeFile.close()
|
mimetypeFile.close()
|
||||||
subprocess_run([SEVENZIP, 'a', '-mx0', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
|
subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
|
||||||
# crazy hack to ensure mimetype is first when using 7zip
|
# crazy hack to ensure mimetype is first when using 7zip
|
||||||
if isepub:
|
if isepub:
|
||||||
subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir)
|
subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir)
|
||||||
else:
|
else:
|
||||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_STORED)
|
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||||
if isepub:
|
if isepub:
|
||||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||||
for dirpath, _, filenames in os.walk(basedir):
|
for dirpath, _, filenames in os.walk(basedir):
|
||||||
@@ -1384,8 +1353,6 @@ def makeParser():
|
|||||||
help="Shift first page to opposite side in landscape for spread alignment")
|
help="Shift first page to opposite side in landscape for spread alignment")
|
||||||
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
||||||
help="Do not rotate double page spreads in spread splitter option.")
|
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,
|
output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False,
|
||||||
help="Put rotated 2 page spread first in spread splitter option.")
|
help="Put rotated 2 page spread first in spread splitter option.")
|
||||||
|
|
||||||
@@ -1393,10 +1360,6 @@ def makeParser():
|
|||||||
help="Do not modify image and ignore any profile or processing option")
|
help="Do not modify image and ignore any profile or processing option")
|
||||||
processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False,
|
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")
|
help="Use the legacy PDF image extraction method from KCC 8 and earlier")
|
||||||
processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
|
|
||||||
help="Render vector PDFs to device width instead of height.")
|
|
||||||
processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False,
|
|
||||||
help="Disable attempt to crop main cover from wide image")
|
|
||||||
processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False,
|
processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False,
|
||||||
help="Crop cover to fill screen")
|
help="Crop cover to fill screen")
|
||||||
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
|
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
|
||||||
@@ -1434,15 +1397,7 @@ def makeParser():
|
|||||||
output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False,
|
output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False,
|
||||||
help="Erase rainbow effect on color eink screen by attenuating interfering frequencies")
|
help="Erase rainbow effect on color eink screen by attenuating interfering frequencies")
|
||||||
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||||
help="Create PNG files instead JPEG for black and white images")
|
help="Create PNG files instead JPEG")
|
||||||
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,
|
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
||||||
help="Create JPEG files using mozJpeg")
|
help="Create JPEG files using mozJpeg")
|
||||||
processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality",
|
processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality",
|
||||||
@@ -1451,8 +1406,6 @@ def makeParser():
|
|||||||
help="Turn 1x4 strips to 2x2 strips")
|
help="Turn 1x4 strips to 2x2 strips")
|
||||||
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
|
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.")
|
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,
|
custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0,
|
||||||
help="Replace screen width provided by device profile")
|
help="Replace screen width provided by device profile")
|
||||||
@@ -1471,11 +1424,6 @@ def checkOptions(options):
|
|||||||
options.isKobo = False
|
options.isKobo = False
|
||||||
options.bordersColor = None
|
options.bordersColor = None
|
||||||
options.keep_epub = False
|
options.keep_epub = False
|
||||||
if options.format == 'PDF-200MB':
|
|
||||||
options.targetsize = 195
|
|
||||||
options.format = 'PDF'
|
|
||||||
if options.batchsplit != 2:
|
|
||||||
options.batchsplit = 1
|
|
||||||
if options.format == 'EPUB-200MB':
|
if options.format == 'EPUB-200MB':
|
||||||
options.targetsize = 195
|
options.targetsize = 195
|
||||||
options.format = 'EPUB'
|
options.format = 'EPUB'
|
||||||
@@ -1534,6 +1482,9 @@ def checkOptions(options):
|
|||||||
if 'Ko' in options.profile:
|
if 'Ko' in options.profile:
|
||||||
options.panelview = False
|
options.panelview = False
|
||||||
options.hq = 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
|
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
|
||||||
if options.format == 'KFX':
|
if options.format == 'KFX':
|
||||||
options.format = 'EPUB'
|
options.format = 'EPUB'
|
||||||
@@ -1559,15 +1510,6 @@ def checkOptions(options):
|
|||||||
options.jpegquality = 85
|
options.jpegquality = 85
|
||||||
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
|
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.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:
|
if options.kindle_scribe_azw3:
|
||||||
options.profileData = list(image.ProfileData.Profiles[options.profile])
|
options.profileData = list(image.ProfileData.Profiles[options.profile])
|
||||||
options.profileData[1] = list(options.profileData[1])
|
options.profileData[1] = list(options.profileData[1])
|
||||||
@@ -1715,16 +1657,12 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
|
||||||
else:
|
else:
|
||||||
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
|
||||||
if cover.smartcover:
|
|
||||||
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
|
|
||||||
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
|
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
|
||||||
elif options.format == 'PDF':
|
elif options.format == 'PDF':
|
||||||
print(f"{job_progress}Creating PDF file with PyMuPDF...")
|
print(f"{job_progress}Creating PDF file with PyMuPDF...")
|
||||||
# determine output filename based on source and tome count
|
# determine output filename based on source and tome count
|
||||||
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
|
||||||
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
|
||||||
if cover.smartcover:
|
|
||||||
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
|
|
||||||
# use optimized buildPDF logic with streaming and compression
|
# use optimized buildPDF logic with streaming and compression
|
||||||
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
|
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
|
||||||
filepath.append(output_pdf)
|
filepath.append(output_pdf)
|
||||||
|
|||||||
@@ -197,13 +197,10 @@ class ComicPageParser:
|
|||||||
and not self.opt.webtoon and self.opt.splitter == 1:
|
and not self.opt.webtoon and self.opt.splitter == 1:
|
||||||
spread = self.image
|
spread = self.image
|
||||||
if not self.opt.norotate:
|
if not self.opt.norotate:
|
||||||
if not self.opt.rotateright:
|
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||||
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])
|
self.payload.append(['R', self.source, spread, self.fill])
|
||||||
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
|
||||||
if self.opt.splitter != 1 and width / height < 2:
|
if self.opt.splitter != 1:
|
||||||
if width > height:
|
if width > height:
|
||||||
leftbox = (0, 0, int(width / 2), height)
|
leftbox = (0, 0, int(width / 2), height)
|
||||||
rightbox = (int(width / 2), 0, width, height)
|
rightbox = (int(width / 2), 0, width, height)
|
||||||
@@ -218,13 +215,10 @@ class ComicPageParser:
|
|||||||
pagetwo = self.image.crop(rightbox)
|
pagetwo = self.image.crop(rightbox)
|
||||||
self.payload.append(['S1', self.source, pageone, self.fill])
|
self.payload.append(['S1', self.source, pageone, self.fill])
|
||||||
self.payload.append(['S2', self.source, pagetwo, self.fill])
|
self.payload.append(['S2', self.source, pagetwo, self.fill])
|
||||||
if self.opt.splitter > 0 or (self.opt.splitter == 0 and width / height >= 2):
|
if self.opt.splitter > 0:
|
||||||
spread = self.image
|
spread = self.image
|
||||||
if not self.opt.norotate:
|
if not self.opt.norotate:
|
||||||
if not self.opt.rotateright:
|
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||||
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])
|
self.payload.append(['R', self.source, spread, self.fill])
|
||||||
else:
|
else:
|
||||||
self.payload.append(['N', self.source, self.image, self.fill])
|
self.payload.append(['N', self.source, self.image, self.fill])
|
||||||
@@ -277,8 +271,6 @@ class ComicPage:
|
|||||||
self.original_color_mode = image.mode
|
self.original_color_mode = image.mode
|
||||||
# TODO: color check earlier
|
# TODO: color check earlier
|
||||||
self.image = image.convert("RGB")
|
self.image = image.convert("RGB")
|
||||||
self.color = self.colorCheck()
|
|
||||||
self.colorOutput = self.color and self.opt.forcecolor
|
|
||||||
self.fill = fill
|
self.fill = fill
|
||||||
self.rotated = False
|
self.rotated = False
|
||||||
self.orgPath = os.path.join(path[0], path[1])
|
self.orgPath = os.path.join(path[0], path[1])
|
||||||
@@ -297,7 +289,8 @@ class ComicPage:
|
|||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
|
|
||||||
def colorCheck(self):
|
@cached_property
|
||||||
|
def color(self):
|
||||||
if self.original_color_mode in ("L", "1"):
|
if self.original_color_mode in ("L", "1"):
|
||||||
return False
|
return False
|
||||||
if self.opt.webtoon:
|
if self.opt.webtoon:
|
||||||
@@ -406,23 +399,17 @@ class ComicPage:
|
|||||||
raise RuntimeError('Cannot save image. ' + str(err))
|
raise RuntimeError('Cannot save image. ' + str(err))
|
||||||
|
|
||||||
def save_with_codec(self, image, targetPath):
|
def save_with_codec(self, image, targetPath):
|
||||||
if self.opt.forcepng and (not self.colorOutput or self.opt.force_png_rgb):
|
if self.opt.forcepng:
|
||||||
image.info.pop('transparency', None)
|
image.info.pop('transparency', None)
|
||||||
if self.opt.webp_output:
|
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
||||||
targetPath += '.webp'
|
|
||||||
image.save(targetPath, 'WEBP', lossless=True, quality=self.opt.jpegquality)
|
|
||||||
elif self.opt.kindle_azw3:
|
|
||||||
targetPath += '.gif'
|
targetPath += '.gif'
|
||||||
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
||||||
else:
|
else:
|
||||||
targetPath += '.png'
|
targetPath += '.png'
|
||||||
image.save(targetPath, 'PNG', optimize=1)
|
image.save(targetPath, 'PNG', optimize=1)
|
||||||
else:
|
else:
|
||||||
if self.opt.webp_output:
|
targetPath += '.jpg'
|
||||||
targetPath += '.webp'
|
if self.opt.mozjpeg:
|
||||||
image.save(targetPath, 'WEBP', quality=self.opt.jpegquality)
|
|
||||||
elif self.opt.mozjpeg:
|
|
||||||
targetPath += '.jpg'
|
|
||||||
with io.BytesIO() as output:
|
with io.BytesIO() as output:
|
||||||
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
|
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
|
||||||
input_jpeg_bytes = output.getvalue()
|
input_jpeg_bytes = output.getvalue()
|
||||||
@@ -430,7 +417,6 @@ class ComicPage:
|
|||||||
with open(targetPath, "wb") as output_jpeg_file:
|
with open(targetPath, "wb") as output_jpeg_file:
|
||||||
output_jpeg_file.write(output_jpeg_bytes)
|
output_jpeg_file.write(output_jpeg_bytes)
|
||||||
else:
|
else:
|
||||||
targetPath += '.jpg'
|
|
||||||
image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
|
image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
|
||||||
return targetPath
|
return targetPath
|
||||||
|
|
||||||
@@ -515,9 +501,7 @@ class ComicPage:
|
|||||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||||
pass
|
pass
|
||||||
else: # if image bigger than device resolution or smaller with upscaling
|
else: # if image bigger than device resolution or smaller with upscaling
|
||||||
if self.opt.profile == 'KDX' and abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD * 3:
|
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
|
||||||
elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
|
||||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
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 (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
|
||||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||||
@@ -525,7 +509,7 @@ class ComicPage:
|
|||||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||||
|
|
||||||
def resize_method(self):
|
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
|
return Image.Resampling.BICUBIC
|
||||||
else:
|
else:
|
||||||
return Image.Resampling.LANCZOS
|
return Image.Resampling.LANCZOS
|
||||||
@@ -569,7 +553,6 @@ class Cover:
|
|||||||
self.options = opt
|
self.options = opt
|
||||||
self.source = source
|
self.source = source
|
||||||
self.image = Image.open(source)
|
self.image = Image.open(source)
|
||||||
self.smartcover = False
|
|
||||||
# backwards compatibility for Pillow >9.1.0
|
# backwards compatibility for Pillow >9.1.0
|
||||||
if not hasattr(Image, 'Resampling'):
|
if not hasattr(Image, 'Resampling'):
|
||||||
Image.Resampling = Image
|
Image.Resampling = Image
|
||||||
@@ -580,8 +563,7 @@ class Cover:
|
|||||||
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||||
if not self.options.forcecolor:
|
if not self.options.forcecolor:
|
||||||
self.image = self.image.convert('L')
|
self.image = self.image.convert('L')
|
||||||
if not self.options.nosmartcovercrop:
|
self.crop_main_cover()
|
||||||
self.crop_main_cover()
|
|
||||||
|
|
||||||
size = list(self.options.profileData[1])
|
size = list(self.options.profileData[1])
|
||||||
if self.options.kindle_scribe_azw3:
|
if self.options.kindle_scribe_azw3:
|
||||||
@@ -596,19 +578,17 @@ class Cover:
|
|||||||
def crop_main_cover(self):
|
def crop_main_cover(self):
|
||||||
w, h = self.image.size
|
w, h = self.image.size
|
||||||
if w / h > 2:
|
if w / h > 2:
|
||||||
self.smartcover = True
|
|
||||||
if self.options.righttoleft:
|
if self.options.righttoleft:
|
||||||
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
|
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
|
||||||
else:
|
else:
|
||||||
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
|
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
|
||||||
elif w / h > 1.34:
|
elif w / h > 1.34:
|
||||||
self.smartcover = True
|
|
||||||
if self.options.righttoleft:
|
if self.options.righttoleft:
|
||||||
self.image = self.image.crop((0, 0, w/2 - w * 0.03, h))
|
self.image = self.image.crop((0, 0, w/2 - w * 0.03, h))
|
||||||
else:
|
else:
|
||||||
self.image = self.image.crop((w/2 + w * 0.03, 0, w, h))
|
self.image = self.image.crop((w/2 + w * 0.03, 0, w, h))
|
||||||
|
|
||||||
def save_to_folder(self, target, tomeid, len_tomes=0):
|
def save_to_epub(self, target, tomeid, len_tomes=0):
|
||||||
try:
|
try:
|
||||||
if tomeid == 0:
|
if tomeid == 0:
|
||||||
self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)
|
self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
PySide6<6.10
|
PySide6>6
|
||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=5.9.5
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
|
|||||||
Reference in New Issue
Block a user