1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-16 14:08:45 +00:00

Compare commits

..

1 Commits

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 01:42:51 +00:00
15 changed files with 1271 additions and 1671 deletions

View File

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

View File

@@ -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 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
- name: build binary
run: |

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ FROM python:3.13-slim-bullseye AS builder
# Install system dependencies
RUN set -x && \
BUILD_DEPS="build-essential cmake libffi-dev libfreetype6-dev libfontconfig1-dev libpng-dev libjpeg-dev libssl-dev libxft-dev make python3-dev" && \
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" && \
DEBIAN_FRONTEND=noninteractive apt-get update -y && \
apt-get install -y --no-install-recommends ${BUILD_DEPS} ${RUNTIME_DEPS}

View File

@@ -249,7 +249,6 @@ 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
@@ -270,19 +269,13 @@ PROCESSING:
Crop empty sections. 0: Disabled 1: Horizontally 2: Both [Default=0]
--blackborders Disable autodetection and force black borders
--whiteborders Disable autodetection and force white borders
--nosmartcovercrop Disable attempt to crop main cover from wide image
--coverfill Center-crop only the cover to fill target device screen
--forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG 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
--forcepng Create PNG files instead JPEG
--mozjpeg Create JPEG files using mozJpeg
--jpeg-quality The JPEG quality, on a scale from 0 (worst) to 95 (best). Default 85 for most devices.
--maximizestrips Turn 1x4 strips to 2x2 strips
-d, --delete Delete source file(s) or a directory. It's not recoverable.
--tempdir Create temporary files directory on source file drive.
OUTPUT SETTINGS:
-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]
--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

File diff suppressed because it is too large Load Diff

View File

@@ -195,7 +195,7 @@ class VersionThread(QThread):
icon = 'bindle'
if category == 'kofi':
icon = 'kofi'
message = f"{payload.get('name')}"
message = f"<b>{payload.get('name')}</b>"
if payload.get('link'):
message = '<a href="{}"><b>{}</b></a>'.format(payload.get('link'), payload.get('name'))
if payload.get('showDeadline'):
@@ -329,10 +329,6 @@ class WorkerThread(QThread):
options.noprocessing = True
if GUI.pdfExtractBox.isChecked():
options.pdfextract = True
if GUI.pdfWidthBox.isChecked():
options.pdfwidth = True
if GUI.noSmartCoverCropBox.isChecked():
options.nosmartcovercrop = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
@@ -341,8 +337,6 @@ class WorkerThread(QThread):
options.metadatatitle = 2
if GUI.deleteBox.isChecked():
options.delete = True
if GUI.tempDirBox.isChecked():
options.tempdir = True
if GUI.spreadShiftBox.isChecked():
options.spreadshift = True
if GUI.fileFusionBox.isChecked():
@@ -351,22 +345,12 @@ 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:
@@ -698,18 +682,6 @@ 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
@@ -844,14 +816,6 @@ 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)
@@ -919,10 +883,6 @@ 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':
@@ -949,7 +909,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()
@@ -1078,26 +1038,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'eraseRainbowBox': GUI.eraseRainbowBox.checkState(),
'disableProcessingBox': GUI.disableProcessingBox.checkState(),
'pdfExtractBox': GUI.pdfExtractBox.checkState(),
'pdfWidthBox': GUI.pdfWidthBox.checkState(),
'noSmartCoverCropBox': GUI.noSmartCoverCropBox.checkState(),
'coverFillBox': GUI.coverFillBox.checkState(),
'metadataTitleBox': GUI.metadataTitleBox.checkState(),
'mozJpegBox': GUI.mozJpegBox.checkState(),
'forcePngRgbBox': GUI.forcePngRgbBox.checkState(),
'webpBox': GUI.webpBox.checkState(),
'pngLegacyBox': GUI.pngLegacyBox.checkState(),
'noQuantizeBox': GUI.noQuantizeBox.checkState(),
'jpegQualityBox': GUI.jpegQualityBox.checkState(),
'jpegQuality': GUI.jpegQualitySpinBox.value(),
'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(),
'deleteBox': GUI.deleteBox.checkState(),
'tempDirBox': GUI.tempDirBox.checkState(),
'spreadShiftBox': GUI.spreadShiftBox.checkState(),
'fileFusionBox': GUI.fileFusionBox.checkState(),
'defaultOutputFolderBox': GUI.defaultOutputFolderBox.checkState(),
'noRotateBox': GUI.noRotateBox.checkState(),
'rotateRightBox': GUI.rotateRightBox.checkState(),
'rotateFirstBox': GUI.rotateFirstBox.checkState(),
'maximizeStrips': GUI.maximizeStrips.checkState(),
'gammaSlider': float(self.gammaValue) * 100,
@@ -1220,7 +1172,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', 'gridLayout_6', 'horizontalLayout_2']:
for element in ['gridLayout_2', 'gridLayout_3', 'gridLayout_4', 'horizontalLayout', 'horizontalLayout_2']:
getattr(GUI, element).setContentsMargins(-1, 0, -1, 0)
if self.windowSize == '0x0':
MW.resize(500, 500)
@@ -1230,7 +1182,6 @@ 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'},
@@ -1428,9 +1379,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.wikiButton.clicked.connect(self.openWiki)
GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble)
GUI.youtubeButton.clicked.connect(self.openYouTube)
GUI.discordButton.clicked.connect(self.openDiscord)
GUI.convertButton.clicked.connect(self.convertStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox)

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -310,15 +310,6 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
f.writelines(["<dc:description>", hescape(options.summary), "</dc:description>\n"])
for author in options.authors:
f.writelines(["<dc:creator>", hescape(author), "</dc:creator>\n"])
if not options.iskindle and options.series:
f.writelines(['<meta property="belongs-to-collection" id="c02">', hescape(options.series), "</meta>\n"])
f.writelines(['<meta refines="#c02" property="collection-type">', "series", "</meta>\n"])
if options.volume and options.number:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(f"{options.volume}.{options.number}"), "</meta>\n"])
elif options.volume:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.volume), "</meta>\n"])
elif options.number:
f.writelines(['<meta refines="#c02" property="group-position">', hescape(options.number), "</meta>\n"])
f.write("<meta property=\"dcterms:modified\">" + strftime("%Y-%m-%dT%H:%M:%SZ", gmtime()) + "</meta>\n")
if cover:
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
@@ -362,8 +353,6 @@ 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=\"" +
@@ -556,7 +545,7 @@ def buildEPUB(path, chapternames, tomenumber, ischunked, cover: image.Cover, ori
f.close()
build_html_start = perf_counter()
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)
options.covers.append((cover, options.uuid))
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)
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:
@@ -709,24 +699,18 @@ def imgFileProcessing(work):
img.gammaCorrectImage()
if not img.colorOutput:
img.convertToGrayscale()
img.autocontrastImage()
img.resizeImage()
img.optimizeForDisplay(opt.eraserainbow, img.colorOutput)
img.optimizeForDisplay(opt.eraserainbow, is_color)
if img.colorOutput:
if is_color:
pass
elif opt.forcepng:
if not opt.noquantize:
img.convertToGrayscale()
if opt.format != 'PDF':
img.quantizeImage()
if opt.format == 'PDF':
img.convertToGrayscale()
elif opt.profile == 'KDX' and opt.format == 'CBZ':
img.convertToGrayscale()
elif opt.pnglegacy:
img.convertToGrayscale()
else:
img.convertToGrayscale()
output.append(img.saveToDir())
return output
except Exception:
@@ -756,9 +740,7 @@ def render_page(vector):
cpu = vector[1] # number of CPUs
filename = vector[2] # document filename
output_dir = vector[3]
target_width = vector[4]
target_height = vector[5]
pdf_width = vector[6]
target_height = vector[4]
with pymupdf.open(filename) as doc: # open the document
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
page = doc[i]
if not pdf_width or page.rect.width > page.rect.height:
zoom = target_height / page.rect.height
else:
zoom = target_width / page.rect.width
zoom = target_height / page.rect.height
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
@@ -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
with pymupdf.open(filename) as doc:
for page in doc:
@@ -857,7 +836,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
cpu = cpu_count()
# 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))
@@ -874,8 +853,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
def getWorkFolder(afile, workdir=None):
if not workdir:
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')
else:
fullPath = workdir
@@ -903,15 +881,13 @@ def getWorkFolder(afile, workdir=None):
if njpg == 0:
raise UserWarning("Failed to extract images from PDF file.")
return workdir
target_width, target_height = options.profileData[1]
target_height = options.profileData[1][1]
if options.cropping == 1:
target_height *= 1.2 #Account for possible margin at the top and bottom
target_width *= 1.2
target_height = target_height + target_height*0.20 #Account for possible margin at the top and bottom
elif options.cropping == 2:
target_height *= 1.25 #Account for possible margin at the top and bottom with page number
target_width *= 1.25
target_height = target_height + target_height*0.25 #Account for possible margin at the top and bottom with page number
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:
rmtree(path, True)
raise UserWarning(f"Failed to extract images from PDF file. {e}")
@@ -1002,9 +978,6 @@ 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):
@@ -1033,10 +1006,8 @@ 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
@@ -1054,8 +1025,6 @@ 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'):
@@ -1316,12 +1285,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', '-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
if isepub:
subprocess_run([SEVENZIP, 'rn', zipfilename, '!mimetype', 'mimetype'], capture_output=True, check=True, cwd=basedir)
else:
zipOutput = ZipFile(zipfilename, 'w', ZIP_STORED)
zipOutput = ZipFile(zipfilename, 'w', ZIP_DEFLATED)
if isepub:
zipOutput.writestr('mimetype', 'application/epub+zip', ZIP_STORED)
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")
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.")
@@ -1393,10 +1360,6 @@ def makeParser():
help="Do not modify image and ignore any profile or processing option")
processing_options.add_argument("--pdfextract", action="store_true", dest="pdfextract", default=False,
help="Use the legacy PDF image extraction method from KCC 8 and earlier")
processing_options.add_argument("--pdfwidth", action="store_true", dest="pdfwidth", default=False,
help="Render vector PDFs to device width instead of height.")
processing_options.add_argument("--nosmartcovercrop", action="store_true", dest="nosmartcovercrop", default=False,
help="Disable attempt to crop main cover from wide image")
processing_options.add_argument("--coverfill", action="store_true", dest="coverfill", default=False,
help="Crop cover to fill screen")
processing_options.add_argument("-u", "--upscale", action="store_true", dest="upscale", default=False,
@@ -1434,15 +1397,7 @@ 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 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")
help="Create PNG files instead JPEG")
processing_options.add_argument("--mozjpeg", action="store_true", dest="mozjpeg", default=False,
help="Create JPEG files using mozJpeg")
processing_options.add_argument("--jpeg-quality", type=int, dest="jpegquality",
@@ -1451,8 +1406,6 @@ def makeParser():
help="Turn 1x4 strips to 2x2 strips")
processing_options.add_argument("-d", "--delete", action="store_true", dest="delete", default=False,
help="Delete source file(s) or a directory. It's not recoverable.")
processing_options.add_argument("--tempdir", action="store_true", dest="tempdir", default=False,
help="Create temporary files directory on source file drive.")
custom_profile_options.add_argument("--customwidth", type=int, dest="customwidth", default=0,
help="Replace screen width provided by device profile")
@@ -1471,11 +1424,6 @@ 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'
@@ -1534,6 +1482,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.format = 'EPUB'
@@ -1559,15 +1510,6 @@ 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])
@@ -1715,16 +1657,12 @@ def makeBook(source, qtgui=None, job_progress=''):
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
if cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
elif options.format == 'PDF':
print(f"{job_progress}Creating PDF file with PyMuPDF...")
# determine output filename based on source and tome count
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
if cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
# use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)
filepath.append(output_pdf)

View File

@@ -197,13 +197,10 @@ class ComicPageParser:
and not self.opt.webtoon and self.opt.splitter == 1:
spread = self.image
if not self.opt.norotate:
if not self.opt.rotateright:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
else:
spread = spread.rotate(-90, Image.Resampling.BICUBIC, True)
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 and width / height < 2:
if self.opt.splitter != 1:
if width > height:
leftbox = (0, 0, int(width / 2), height)
rightbox = (int(width / 2), 0, width, height)
@@ -218,13 +215,10 @@ 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 or (self.opt.splitter == 0 and width / height >= 2):
if self.opt.splitter > 0:
spread = self.image
if not self.opt.norotate:
if not self.opt.rotateright:
spread = spread.rotate(90, Image.Resampling.BICUBIC, True)
else:
spread = spread.rotate(-90, Image.Resampling.BICUBIC, True)
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])
@@ -277,8 +271,6 @@ 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])
@@ -297,7 +289,8 @@ class ComicPage:
if not hasattr(Image, 'Resampling'):
Image.Resampling = Image
def colorCheck(self):
@cached_property
def color(self):
if self.original_color_mode in ("L", "1"):
return False
if self.opt.webtoon:
@@ -406,23 +399,17 @@ class ComicPage:
raise RuntimeError('Cannot save image. ' + str(err))
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)
if self.opt.webp_output:
targetPath += '.webp'
image.save(targetPath, 'WEBP', lossless=True, quality=self.opt.jpegquality)
elif self.opt.kindle_azw3:
if self.opt.iskindle and ('MOBI' in self.opt.format or 'EPUB' in self.opt.format):
targetPath += '.gif'
image.save(targetPath, 'GIF', optimize=1, interlace=False)
else:
targetPath += '.png'
image.save(targetPath, 'PNG', optimize=1)
else:
if self.opt.webp_output:
targetPath += '.webp'
image.save(targetPath, 'WEBP', quality=self.opt.jpegquality)
elif self.opt.mozjpeg:
targetPath += '.jpg'
targetPath += '.jpg'
if self.opt.mozjpeg:
with io.BytesIO() as output:
image.save(output, format="JPEG", optimize=1, quality=self.opt.jpegquality)
input_jpeg_bytes = output.getvalue()
@@ -430,7 +417,6 @@ 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
@@ -515,9 +501,7 @@ 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 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:
if 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)
@@ -525,7 +509,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
@@ -569,7 +553,6 @@ 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
@@ -580,8 +563,7 @@ class Cover:
self.image = ImageOps.autocontrast(self.image, preserve_tone=True)
if not self.options.forcecolor:
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])
if self.options.kindle_scribe_azw3:
@@ -596,19 +578,17 @@ class Cover:
def crop_main_cover(self):
w, h = self.image.size
if w / h > 2:
self.smartcover = True
if self.options.righttoleft:
self.image = self.image.crop((w/6, 0, w/2 - w * 0.02, h))
else:
self.image = self.image.crop((w/2 + w * 0.02, 0, 5/6 * w, h))
elif w / h > 1.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))
def save_to_folder(self, target, tomeid, len_tomes=0):
def save_to_epub(self, target, tomeid, len_tomes=0):
try:
if tomeid == 0:
self.image.save(target, "JPEG", optimize=1, quality=self.options.jpegquality)

View File

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