mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 13:38:46 +00:00
Compare commits
24 Commits
dependabot
...
v9.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d61a9e558 | ||
|
|
8aaedf274d | ||
|
|
5782a44e7b | ||
|
|
e4c918f0f3 | ||
|
|
8f4072bfab | ||
|
|
61f3097be5 | ||
|
|
fa33ef8f89 | ||
|
|
232bac00a9 | ||
|
|
d19a4754fa | ||
|
|
9a93cc4b17 | ||
|
|
e0471b2dc9 | ||
|
|
a87eb318cf | ||
|
|
87987c9ebf | ||
|
|
f5fe8d93b0 | ||
|
|
249f823f01 | ||
|
|
3a9d4f274d | ||
|
|
b5de6fd39d | ||
|
|
b4b9e41a0c | ||
|
|
9b9181a715 | ||
|
|
472fdc97b5 | ||
|
|
6fdfddd7d9 | ||
|
|
34fb68ac65 | ||
|
|
b4d72cd581 | ||
|
|
1dead9af8f |
2
.github/workflows/package-linux.yml
vendored
2
.github/workflows/package-linux.yml
vendored
@@ -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: |
|
||||
|
||||
2
.github/workflows/package-macos.yml
vendored
2
.github/workflows/package-macos.yml
vendored
@@ -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
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
2
.github/workflows/package-windows.yml
vendored
2
.github/workflows/package-windows.yml
vendored
@@ -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
|
||||
|
||||
13
.github/workflows/package-windows7.yml
vendored
13
.github/workflows/package-windows7.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
env:
|
||||
PYINSTALLER_COMPILE_BOOTLOADER: 1
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements-win7.txt
|
||||
pip install certifi pyinstaller --no-binary pyinstaller
|
||||
.\gen_ui_files.bat
|
||||
@@ -50,6 +50,17 @@ jobs:
|
||||
with:
|
||||
name: windows7-build
|
||||
path: dist/*.exe
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v2.0
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '1dc1bad6-4a8c-4f85-af30-5c5d3d392ea6'
|
||||
project-slug: 'kcc'
|
||||
signing-policy-slug: 'release-signing'
|
||||
github-artifact-id: '${{ steps.upload-unsigned-artifact.outputs.artifact-id }}'
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: 'dist/'
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -249,6 +249,7 @@ MAIN:
|
||||
PROCESSING:
|
||||
-n, --noprocessing Do not modify image and ignore any profile or processing option
|
||||
--pdfextract Use legacy PDF image extraction method from KCC 8 and earlier.
|
||||
--pdfwidth Render vector PDFs based on device width instead of height.
|
||||
-u, --upscale Resize images smaller than device's resolution
|
||||
-s, --stretch Stretch images to device's resolution
|
||||
-r SPLITTER, --splitter SPLITTER
|
||||
@@ -269,9 +270,14 @@ 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
|
||||
@@ -292,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
|
||||
|
||||
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'
|
||||
if category == 'kofi':
|
||||
icon = 'kofi'
|
||||
message = f"<b>{payload.get('name')}</b>"
|
||||
message = f"{payload.get('name')}"
|
||||
if payload.get('link'):
|
||||
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
|
||||
if payload.get('showDeadline'):
|
||||
@@ -329,6 +329,10 @@ class WorkerThread(QThread):
|
||||
options.noprocessing = True
|
||||
if GUI.pdfExtractBox.isChecked():
|
||||
options.pdfextract = True
|
||||
if GUI.pdfWidthBox.isChecked():
|
||||
options.pdfwidth = True
|
||||
if GUI.smartCoverCropBox.isChecked():
|
||||
options.smartcovercrop = True
|
||||
if GUI.coverFillBox.isChecked():
|
||||
options.coverfill = True
|
||||
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
@@ -345,12 +349,22 @@ class WorkerThread(QThread):
|
||||
options.filefusion = False
|
||||
if GUI.noRotateBox.isChecked():
|
||||
options.norotate = True
|
||||
if GUI.rotateRightBox.isChecked():
|
||||
options.rotateright = True
|
||||
if GUI.rotateFirstBox.isChecked():
|
||||
options.rotatefirst = True
|
||||
if GUI.forcePngRgbBox.isChecked():
|
||||
options.force_png_rgb = True
|
||||
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
|
||||
options.forcepng = True
|
||||
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
|
||||
options.mozjpeg = True
|
||||
if GUI.webpBox.isChecked():
|
||||
options.webp = True
|
||||
if GUI.pngLegacyBox.isChecked():
|
||||
options.pnglegacy = True
|
||||
if GUI.noQuantizeBox.isChecked():
|
||||
options.noquantize = True
|
||||
if GUI.jpegQualityBox.isChecked():
|
||||
options.jpegquality = GUI.jpegQualitySpinBox.value()
|
||||
if GUI.currentMode > 2:
|
||||
@@ -682,6 +696,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://ko-fi.com/eink_dude'))
|
||||
|
||||
def openHumble(self):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
|
||||
|
||||
def openYouTube(self):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
|
||||
|
||||
def openDiscord(self):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://discord.gg/um5JRKwmGT'))
|
||||
|
||||
def modeChange(self, mode):
|
||||
if mode == 1:
|
||||
self.currentMode = 1
|
||||
@@ -816,6 +842,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if bad_format in current_format:
|
||||
self.addMessage('Scribe PNG MOBI/EPUB has a lot of problems like blank pages/sections. Use JPG instead.', 'warning')
|
||||
break
|
||||
GUI.pngLegacyBox.setEnabled(True)
|
||||
GUI.noQuantizeBox.setEnabled(True)
|
||||
GUI.forcePngRgbBox.setEnabled(True)
|
||||
else:
|
||||
GUI.pngLegacyBox.setEnabled(False)
|
||||
GUI.noQuantizeBox.setEnabled(False)
|
||||
GUI.forcePngRgbBox.setEnabled(False)
|
||||
|
||||
|
||||
def togglechunkSizeCheckBox(self, value):
|
||||
GUI.chunkSizeWidget.setVisible(value)
|
||||
@@ -883,6 +917,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if bad_format in current_format:
|
||||
self.addMessage('Colorsoft MOBI/EPUB can have blank pages. Just go back a few pages, exit, and reenter book.', 'info')
|
||||
break
|
||||
elif profile['Label'] == 'KDX':
|
||||
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
||||
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
|
||||
GUI.pngLegacyBox.setChecked(True)
|
||||
if not profile['PVOptions']:
|
||||
GUI.qualityBox.setChecked(False)
|
||||
if str(GUI.deviceBox.currentText()) == 'Other':
|
||||
@@ -909,7 +947,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
elif not GUI.webtoonBox.isChecked():
|
||||
GUI.chunkSizeCheckBox.setEnabled(True)
|
||||
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
|
||||
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
|
||||
|
||||
def stripTags(self, html):
|
||||
s = HTMLStripper()
|
||||
@@ -1038,9 +1076,15 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'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(),
|
||||
@@ -1050,6 +1094,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'fileFusionBox': GUI.fileFusionBox.checkState(),
|
||||
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
|
||||
'noRotateBox': GUI.noRotateBox.checkState(),
|
||||
'rotateRightBox': GUI.rotateRightBox.checkState(),
|
||||
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
|
||||
'maximizeStrips': GUI.maximizeStrips.checkState(),
|
||||
'gammaSlider': float(self.gammaValue) * 100,
|
||||
@@ -1172,7 +1217,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
'convertButton', 'formatBox']:
|
||||
getattr(GUI, element).setMinimumSize(QSize(0, 0))
|
||||
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
|
||||
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
|
||||
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'gridLayout_6', 'horizontalLayout_2']:
|
||||
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
|
||||
if self.windowSize == '0x0':
|
||||
MW.resize(500, 500)
|
||||
@@ -1182,6 +1227,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
"EPUB": {'icon': 'EPUB', 'format': 'EPUB'},
|
||||
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
|
||||
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
|
||||
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
|
||||
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
|
||||
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
|
||||
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
|
||||
@@ -1379,6 +1425,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||
GUI.wikiButton.clicked.connect(self.openWiki)
|
||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
||||
GUI.humbleButton.clicked.connect(self.openHumble)
|
||||
GUI.youtubeButton.clicked.connect(self.openYouTube)
|
||||
GUI.discordButton.clicked.connect(self.openDiscord)
|
||||
GUI.convertButton.clicked.connect(self.convertStart)
|
||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
__version__ = '9.5.0'
|
||||
__version__ = '9.7.0'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -353,6 +353,8 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
||||
mt = 'image/png'
|
||||
elif '.gif' == filename[1]:
|
||||
mt = 'image/gif'
|
||||
elif '.webp' == filename[1]:
|
||||
mt = 'image/webp'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
|
||||
@@ -689,7 +691,6 @@ def imgFileProcessing(work):
|
||||
workImg = image.ComicPageParser((dirpath, afile), opt)
|
||||
for i in workImg.payload:
|
||||
img = image.ComicPage(opt, *i)
|
||||
is_color = (opt.forcecolor and img.color)
|
||||
if opt.cropping == 2 and not opt.webtoon:
|
||||
img.cropPageNumber(opt.croppingp, opt.croppingm)
|
||||
if opt.cropping == 1 and not opt.webtoon:
|
||||
@@ -699,18 +700,24 @@ def imgFileProcessing(work):
|
||||
|
||||
img.gammaCorrectImage()
|
||||
|
||||
if not img.colorOutput:
|
||||
img.convertToGrayscale()
|
||||
|
||||
img.autocontrastImage()
|
||||
img.resizeImage()
|
||||
img.optimizeForDisplay(opt.eraserainbow, is_color)
|
||||
img.optimizeForDisplay(opt.eraserainbow, img.colorOutput)
|
||||
|
||||
if is_color:
|
||||
if img.colorOutput:
|
||||
pass
|
||||
elif opt.forcepng:
|
||||
img.convertToGrayscale()
|
||||
if opt.format != 'PDF':
|
||||
if not opt.noquantize:
|
||||
img.quantizeImage()
|
||||
else:
|
||||
img.convertToGrayscale()
|
||||
if opt.format == 'PDF':
|
||||
img.convertToGrayscale()
|
||||
elif opt.profile == 'KDX' and opt.format == 'CBZ':
|
||||
img.convertToGrayscale()
|
||||
elif opt.pnglegacy:
|
||||
img.convertToGrayscale()
|
||||
output.append(img.saveToDir())
|
||||
return output
|
||||
except Exception:
|
||||
@@ -740,7 +747,9 @@ def render_page(vector):
|
||||
cpu = vector[1] # number of CPUs
|
||||
filename = vector[2] # document filename
|
||||
output_dir = vector[3]
|
||||
target_height = vector[4]
|
||||
target_width = vector[4]
|
||||
target_height = vector[5]
|
||||
pdf_width = vector[6]
|
||||
with pymupdf.open(filename) as doc: # open the document
|
||||
num_pages = doc.page_count # get number of pages
|
||||
|
||||
@@ -751,7 +760,10 @@ def render_page(vector):
|
||||
|
||||
for i in range(seg_from, seg_to): # work through our page segment
|
||||
page = doc[i]
|
||||
zoom = target_height / page.rect.height
|
||||
if not pdf_width or page.rect.width > page.rect.height:
|
||||
zoom = target_height / page.rect.height
|
||||
else:
|
||||
zoom = target_width / page.rect.width
|
||||
mat = pymupdf.Matrix(zoom, zoom)
|
||||
# TODO: decide colorspace earlier so later color check is cheaper.
|
||||
# This is actually pretty hard when you have to deal with color vector text
|
||||
@@ -816,7 +828,7 @@ def extract_page(vector):
|
||||
|
||||
|
||||
|
||||
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_height):
|
||||
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_height):
|
||||
render = False
|
||||
with pymupdf.open(filename) as doc:
|
||||
for page in doc:
|
||||
@@ -836,7 +848,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))
|
||||
|
||||
|
||||
@@ -881,13 +893,15 @@ def getWorkFolder(afile, workdir=None):
|
||||
if njpg == 0:
|
||||
raise UserWarning("Failed to extract images from PDF file.")
|
||||
return workdir
|
||||
target_height = options.profileData[1][1]
|
||||
target_width, target_height = options.profileData[1]
|
||||
if options.cropping == 1:
|
||||
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
|
||||
target_height *= 1.2 #Account for possible margin at the top and bottom
|
||||
target_width *= 1.2
|
||||
elif options.cropping == 2:
|
||||
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
|
||||
target_height *= 1.25 #Account for possible margin at the top and bottom with page number
|
||||
target_width *= 1.25
|
||||
try:
|
||||
mupdf_pdf_process_pages_parallel(afile, fullPath, target_height)
|
||||
mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height)
|
||||
except Exception as e:
|
||||
rmtree(path, True)
|
||||
raise UserWarning(f"Failed to extract images from PDF file. {e}")
|
||||
@@ -1285,12 +1299,12 @@ def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
|
||||
mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w')
|
||||
mimetypeFile.write('application/epub+zip')
|
||||
mimetypeFile.close()
|
||||
subprocess_run([SEVENZIP, 'a', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
|
||||
subprocess_run([SEVENZIP, 'a', '-mx0', '-tzip', zipfilename, "*"], capture_output=True, check=True, cwd=basedir)
|
||||
# crazy hack to ensure mimetype is first when using 7zip
|
||||
if isepub:
|
||||
subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir)
|
||||
else:
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
|
||||
zipOutput = ZipFile(zipfilename, 'w', ZIP_STORED)
|
||||
if isepub:
|
||||
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
|
||||
for dirpath, _, filenames in os.walk(basedir):
|
||||
@@ -1353,6 +1367,8 @@ def makeParser():
|
||||
help="Shift first page to opposite side in landscape for spread alignment")
|
||||
output_options.add_argument("--norotate", action="store_true", dest="norotate", default=False,
|
||||
help="Do not rotate double page spreads in spread splitter option.")
|
||||
output_options.add_argument("--rotateright", action="store_true", dest="rotateright", default=False,
|
||||
help="Rotate double page spreads in opposite direction.")
|
||||
output_options.add_argument("--rotatefirst", action="store_true", dest="rotatefirst", default=False,
|
||||
help="Put rotated 2 page spread first in spread splitter option.")
|
||||
|
||||
@@ -1360,6 +1376,10 @@ def makeParser():
|
||||
help="Do not modify image and ignore any profile or processing option")
|
||||
processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False,
|
||||
help="Use the legacy PDF image extraction method from KCC 8 and earlier")
|
||||
processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
|
||||
help="Render vector PDFs to device width instead of height.")
|
||||
processing_options.add_argument("--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,
|
||||
@@ -1397,7 +1417,15 @@ def makeParser():
|
||||
output_options.add_argument("--eraserainbow", action="store_true", dest="eraserainbow", default=False,
|
||||
help="Erase rainbow effect on color eink screen by attenuating interfering frequencies")
|
||||
processing_options.add_argument("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG")
|
||||
help="Create PNG files instead JPEG for black and white images")
|
||||
processing_options.add_argument("--force-png-rgb", action="store_true", dest="force_png_rgb", default=False,
|
||||
help="Force color images to be saved as PNG")
|
||||
processing_options.add_argument("--webp", action="store_true", dest="webp", default=False,
|
||||
help="Replace JPG with lossy WEBP and PNG with lossless WEBP")
|
||||
processing_options.add_argument("--pnglegacy", action="store_true", dest="pnglegacy", default=False,
|
||||
help="Use a more compatible 8 bit png instead of 4 bit")
|
||||
processing_options.add_argument("--noquantize", action="store_true", dest="noquantize", default=False,
|
||||
help="Don't quantize to 16 color PNG")
|
||||
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
|
||||
help="Create JPEG files using mozJpeg")
|
||||
processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality",
|
||||
@@ -1424,6 +1452,11 @@ def checkOptions(options):
|
||||
options.isKobo = False
|
||||
options.bordersColor = None
|
||||
options.keep_epub = False
|
||||
if options.format == 'PDF-200MB':
|
||||
options.targetsize = 195
|
||||
options.format = 'PDF'
|
||||
if options.batchsplit != 2:
|
||||
options.batchsplit = 1
|
||||
if options.format == 'EPUB-200MB':
|
||||
options.targetsize = 195
|
||||
options.format = 'EPUB'
|
||||
@@ -1482,9 +1515,6 @@ def checkOptions(options):
|
||||
if 'Ko' in options.profile:
|
||||
options.panelview = False
|
||||
options.hq = False
|
||||
# CBZ files on Kindle DX/DXG support higher resolution
|
||||
if options.profile == 'KDX' and options.format == 'CBZ':
|
||||
options.customheight = 1200
|
||||
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
|
||||
if options.format == 'KFX':
|
||||
options.format = 'EPUB'
|
||||
@@ -1510,6 +1540,15 @@ def checkOptions(options):
|
||||
options.jpegquality = 85
|
||||
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
|
||||
options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3
|
||||
|
||||
options.webp_output = options.format != 'PDF' and not options.kindle_azw3 and options.webp
|
||||
|
||||
# CBZ files on Kindle DX/DXG support higher resolution
|
||||
if options.profile == 'KDX' and options.format == 'CBZ':
|
||||
options.profileData = list(image.ProfileData.Profiles[options.profile])
|
||||
options.profileData[1] = list(options.profileData[1])
|
||||
options.profileData[1][1] = 1200
|
||||
|
||||
if options.kindle_scribe_azw3:
|
||||
options.profileData = list(image.ProfileData.Profiles[options.profile])
|
||||
options.profileData[1] = list(options.profileData[1])
|
||||
|
||||
@@ -197,7 +197,10 @@ class ComicPageParser:
|
||||
and not self.opt.webtoon 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:
|
||||
@@ -218,7 +221,10 @@ class ComicPageParser:
|
||||
if self.opt.splitter > 0:
|
||||
spread = self.image
|
||||
if not self.opt.norotate:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
if not self.opt.rotateright:
|
||||
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
|
||||
else:
|
||||
spread = spread.rotate(-90, Image.Resampling.BICUBIC, True)
|
||||
self.payload.append(['R', self.source, spread, self.fill])
|
||||
else:
|
||||
self.payload.append(['N', self.source, self.image, self.fill])
|
||||
@@ -271,6 +277,8 @@ class ComicPage:
|
||||
self.original_color_mode = image.mode
|
||||
# TODO: color check earlier
|
||||
self.image = image.convert("RGB")
|
||||
self.color = self.colorCheck()
|
||||
self.colorOutput = self.color and self.opt.forcecolor
|
||||
self.fill = fill
|
||||
self.rotated = False
|
||||
self.orgPath = os.path.join(path[0], path[1])
|
||||
@@ -289,8 +297,7 @@ class ComicPage:
|
||||
if not hasattr(Image, 'Resampling'):
|
||||
Image.Resampling = Image
|
||||
|
||||
@cached_property
|
||||
def color(self):
|
||||
def colorCheck(self):
|
||||
if self.original_color_mode in ("L", "1"):
|
||||
return False
|
||||
if self.opt.webtoon:
|
||||
@@ -399,17 +406,23 @@ class ComicPage:
|
||||
raise RuntimeError('Cannot save image. ' + str(err))
|
||||
|
||||
def save_with_codec(self, image, targetPath):
|
||||
if self.opt.forcepng:
|
||||
if self.opt.forcepng and (not self.colorOutput or self.opt.force_png_rgb):
|
||||
image.info.pop('transparency', None)
|
||||
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
|
||||
if self.opt.webp_output:
|
||||
targetPath += '.webp'
|
||||
image.save(targetPath, 'WEBP', lossless=True, quality=self.opt.jpegquality)
|
||||
elif self.opt.kindle_azw3:
|
||||
targetPath += '.gif'
|
||||
image.save(targetPath, 'GIF', optimize=1, interlace=False)
|
||||
else:
|
||||
targetPath += '.png'
|
||||
image.save(targetPath, 'PNG', optimize=1)
|
||||
else:
|
||||
targetPath += '.jpg'
|
||||
if self.opt.mozjpeg:
|
||||
if self.opt.webp_output:
|
||||
targetPath += '.webp'
|
||||
image.save(targetPath, 'WEBP', quality=self.opt.jpegquality)
|
||||
elif self.opt.mozjpeg:
|
||||
targetPath += '.jpg'
|
||||
with io.BytesIO() as output:
|
||||
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
|
||||
input_jpeg_bytes = output.getvalue()
|
||||
@@ -417,6 +430,7 @@ class ComicPage:
|
||||
with open(targetPath, "wb") as output_jpeg_file:
|
||||
output_jpeg_file.write(output_jpeg_bytes)
|
||||
else:
|
||||
targetPath += '.jpg'
|
||||
image.save(targetPath, 'JPEG', optimize=1, quality=self.opt.jpegquality)
|
||||
return targetPath
|
||||
|
||||
@@ -501,7 +515,9 @@ class ComicPage:
|
||||
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
|
||||
pass
|
||||
else: # if image bigger than device resolution or smaller with upscaling
|
||||
if abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||
if self.opt.profile == 'KDX' and abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD * 3:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||
elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method)
|
||||
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
|
||||
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
|
||||
@@ -509,7 +525,7 @@ class ComicPage:
|
||||
self.image = ImageOps.contain(self.image, self.size, method=method)
|
||||
|
||||
def resize_method(self):
|
||||
if self.image.size[0] < self.size[0] and self.image.size[1] < self.size[1]:
|
||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
return Image.Resampling.BICUBIC
|
||||
else:
|
||||
return Image.Resampling.LANCZOS
|
||||
@@ -563,7 +579,8 @@ class Cover:
|
||||
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
|
||||
if not self.options.forcecolor:
|
||||
self.image = self.image.convert('L')
|
||||
self.crop_main_cover()
|
||||
if self.options.smartcovercrop:
|
||||
self.crop_main_cover()
|
||||
|
||||
size = list(self.options.profileData[1])
|
||||
if self.options.kindle_scribe_azw3:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PySide6>6
|
||||
PySide6<6.10
|
||||
Pillow>=11.3.0
|
||||
psutil>=5.9.5
|
||||
requests>=2.31.0
|
||||
|
||||
Reference in New Issue
Block a user