1
0
mirror of https://github.com/ciromattia/kcc synced 2026-08-02 10:01:29 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 7206c0cd48 Bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 03:17:04 +00:00
9 changed files with 262 additions and 485 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ jobs:
# apply provisioning profile # apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- uses: actions/setup-node@v6 - uses: actions/setup-node@v7
with: with:
node-version: 16 node-version: 16
- run: npm install -g appdmg - run: npm install -g appdmg
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
pip3 install --upgrade pip pyinstaller certifi pip3 install --upgrade pip 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@v7
with: with:
node-version: 16 node-version: 16
- run: npm install -g appdmg - run: npm install -g appdmg
-1
View File
@@ -42,7 +42,6 @@ KCC avoids many common formatting issues (some of which occur [even on the Kindl
4) incorrect page turn direction for manga that's read right to left 4) incorrect page turn direction for manga that's read right to left
5) unaligned two page spreads in landscape, where pages are shifted over by 1 5) unaligned two page spreads in landscape, where pages are shifted over by 1
6) Removing without blur the rainbow effect on color eink Kaleido 3 due to manga screentones 6) Removing without blur the rainbow effect on color eink Kaleido 3 due to manga screentones
7) Fixing page order problems due to Windows sort being different than Kindle/Kobo sort
The GUI looks like this, built in Qt6, with my most commonly used settings: The GUI looks like this, built in Qt6, with my most commonly used settings:
+63 -76
View File
@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>649</width> <width>644</width>
<height>706</height> <height>706</height>
</rect> </rect>
</property> </property>
@@ -711,27 +711,7 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="0" column="2">
<widget class="QPushButton" name="editorButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to edit directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Metadata Editor</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="humbleButton"> <widget class="QPushButton" name="humbleButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
@@ -748,7 +728,7 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="1">
<widget class="QPushButton" name="kofiButton"> <widget class="QPushButton" name="kofiButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
@@ -771,16 +751,23 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="0">
<widget class="QPushButton" name="labelSpreadsButton"> <widget class="QPushButton" name="editorButton">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>30</height> <height>30</height>
</size> </size>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Shift+Click to edit directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Label Spreads</string> <string>Metadata Editor</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/editor.png</normaloff>:/Other/icons/editor.png</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@@ -1087,56 +1074,6 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="1" column="1">
<widget class="QComboBox" name="deviceBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="clearButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Clear list</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="directoryButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Add input folder(s)</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
</property>
</widget>
</item>
<item row="1" column="3"> <item row="1" column="3">
<widget class="QPushButton" name="convertButton"> <widget class="QPushButton" name="convertButton">
<property name="minimumSize"> <property name="minimumSize">
@@ -1162,6 +1099,36 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="3">
<widget class="QPushButton" name="clearButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>Clear list</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/clear.png</normaloff>:/Other/icons/clear.png</iconset>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="deviceBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>28</height>
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Target device.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QPushButton" name="fileButton"> <widget class="QPushButton" name="fileButton">
<property name="minimumSize"> <property name="minimumSize">
@@ -1182,6 +1149,26 @@ Keeping this file may crash some readers like the Kobo native CBZ reader.</strin
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="4">
<widget class="QPushButton" name="directoryButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Add directory containing JPG, PNG or GIF files to queue.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;CBR, CBZ and CB7 files inside will not be processed!&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Add input folder(s)</string>
</property>
<property name="icon">
<iconset resource="KCC.qrc">
<normaloff>:/Other/icons/folder_new.png</normaloff>:/Other/icons/folder_new.png</iconset>
</property>
</widget>
</item>
<item row="1" column="4"> <item row="1" column="4">
<widget class="QComboBox" name="formatBox"> <widget class="QComboBox" name="formatBox">
<property name="minimumSize"> <property name="minimumSize">
+131 -214
View File
@@ -19,11 +19,10 @@
from datetime import datetime, timezone from datetime import datetime, timezone
import itertools import itertools
import json
from pathlib import Path from pathlib import Path
from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings) from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings)
from PySide6.QtGui import (QColor, QIcon, QImage, QKeyEvent, QPixmap, QDesktopServices) from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices)
from PySide6.QtWidgets import (QApplication, QDialogButtonBox, QHBoxLayout, QLabel, QListWidgetItem, QMainWindow, QSizePolicy, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog, QAbstractItemView, QListView, QTreeView, QWidget) from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog, QAbstractItemView, QListView, QTreeView)
from PySide6.QtNetwork import (QLocalSocket, QLocalServer) from PySide6.QtNetwork import (QLocalSocket, QLocalServer)
import os import os
@@ -39,15 +38,10 @@ from xml.sax.saxutils import escape
from psutil import Process from psutil import Process
from copy import copy from copy import copy
from packaging.version import Version from packaging.version import Version
from tempfile import gettempdir, mkdtemp from tempfile import gettempdir
from PIL import Image
from PIL.Image import Dither
from .KCC_spread_label import LabelSpreadsDialog
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
from .comicarchive import SEVENZIP, TAR, available_archive_tools from .comicarchive import SEVENZIP, TAR, available_archive_tools
from .comic2ebook import OS_SORT_KEY, flattenTree, getWorkFolder, removeNonImages, sanitizeTree
from . import __version__ from . import __version__
from . import comic2ebook from . import comic2ebook
from . import metadata from . import metadata
@@ -240,138 +234,6 @@ class ProgressThread(QThread):
self.running = False self.running = False
def get_options():
parser = comic2ebook.makeParser()
options = parser.parse_args()
options.profile = GUI.profiles[str(GUI.deviceBox.currentText())]['Label']
gui_current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
options.format = gui_current_format
if GUI.mangaBox.isChecked():
options.righttoleft = True
if GUI.lightnovelBox.isChecked():
options.lightnovel = True
if GUI.ebokBox.isChecked():
options.ebok = True
if GUI.invertDirectionBox.isChecked():
options.invertdirection = True
if GUI.rotateBox.checkState() == Qt.CheckState.PartiallyChecked:
options.splitter = 2
elif GUI.rotateBox.checkState() == Qt.CheckState.Checked:
options.splitter = 1
if GUI.qualityBox.checkState() == Qt.CheckState.PartiallyChecked:
options.autoscale = True
elif GUI.qualityBox.checkState() == Qt.CheckState.Checked:
options.hq = True
if GUI.vertical4PanelBox.isChecked():
options.vertical4panel = True
if GUI.webtoonBox.isChecked():
options.webtoon = True
if GUI.upscaleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.stretch = True
elif GUI.upscaleBox.checkState() == Qt.CheckState.Checked:
options.upscale = True
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
options.gamma = float(GUI.gammaValue)
if GUI.autoLevelBox.isChecked():
options.autolevel = True
if GUI.autocontrastBox.checkState() == Qt.CheckState.PartiallyChecked:
options.noautocontrast = True
elif GUI.autocontrastBox.checkState() == Qt.CheckState.Checked:
options.colorautocontrast = True
if GUI.croppingBox.isChecked():
if GUI.croppingBox.checkState() == Qt.CheckState.PartiallyChecked:
options.cropping = 1
else:
options.cropping = 2
else:
options.cropping = 0
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
options.croppingp = float(GUI.croppingPowerValue)
options.preservemargin = GUI.preserveMarginBox.value()
if GUI.interPanelCropBox.isChecked():
if GUI.interPanelCropBox.checkState() == Qt.CheckState.PartiallyChecked:
options.interpanelcrop = 1
else:
options.interpanelcrop = 2
else:
options.interpanelcrop = 0
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
options.white_borders = True
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
options.black_borders = True
if GUI.outputSplit.isChecked():
options.batchsplit = 2
if GUI.colorBox.isChecked():
options.forcecolor = True
if GUI.eraseRainbowBox.isChecked():
options.eraserainbow = True
if GUI.maximizeStrips.isChecked():
options.maximizestrips = True
if GUI.disableProcessingBox.isChecked():
options.noprocessing = True
if GUI.legacyExtractBox.isChecked():
options.legacyextract = True
if GUI.pdfWidthBox.isChecked():
options.pdfwidth = True
if GUI.smartCoverCropBox.isChecked():
options.smartcovercrop = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.metadatatitle = 1
elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked:
options.metadatatitle = 2
if GUI.keepComicInfoBox.isChecked():
options.keepcomicinfo = True
if GUI.deleteBox.isChecked():
options.delete = True
if GUI.tempDirBox.isChecked():
options.tempdir = True
if GUI.spreadShiftBox.isChecked():
options.spreadshift = True
if GUI.onePageLandscapeBox.isChecked():
options.onepagelandscape = True
if GUI.fileFusionBox.isChecked():
options.filefusion = True
else:
options.filefusion = False
if GUI.noRotateBox.isChecked():
options.norotate = True
if GUI.rotateRightBox.isChecked():
options.rotateright = True
if GUI.rotateFirstBox.isChecked():
options.rotatefirst = True
if GUI.forcePngRgbBox.isChecked():
options.force_png_rgb = True
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
options.forcepng = True
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
options.mozjpeg = True
if GUI.webpBox.isChecked():
options.webp = True
if GUI.pngLegacyBox.isChecked():
options.pnglegacy = True
if GUI.noQuantizeBox.isChecked():
options.noquantize = True
if GUI.jpegQualityBox.isChecked():
options.jpegquality = GUI.jpegQualitySpinBox.value()
if GUI.currentMode > 2:
options.customwidth = str(GUI.widthBox.value())
options.customheight = str(GUI.heightBox.value())
if GUI.targetDirectory != '':
options.output = GUI.targetDirectory
if GUI.titleEdit.text():
options.title = str(GUI.titleEdit.text())
if GUI.authorEdit.text():
options.author = str(GUI.authorEdit.text())
if GUI.languageEdit.text():
options.language = str(GUI.languageEdit.text())
if GUI.chunkSizeCheckBox.isChecked():
options.targetsize = int(GUI.chunkSizeBox.value())
return options, gui_current_format
class WorkerThread(QThread): class WorkerThread(QThread):
def __init__(self): def __init__(self):
QThread.__init__(self) QThread.__init__(self)
@@ -401,9 +263,136 @@ class WorkerThread(QThread):
def run(self): def run(self):
MW.modeConvert.emit(0) MW.modeConvert.emit(0)
parser = comic2ebook.makeParser()
options = parser.parse_args()
argv = '' argv = ''
currentJobs = [] currentJobs = []
options, gui_current_format = get_options()
options.profile = GUI.profiles[str(GUI.deviceBox.currentText())]['Label']
gui_current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
options.format = gui_current_format
if GUI.mangaBox.isChecked():
options.righttoleft = True
if GUI.lightnovelBox.isChecked():
options.lightnovel = True
if GUI.ebokBox.isChecked():
options.ebok = True
if GUI.invertDirectionBox.isChecked():
options.invertdirection = True
if GUI.rotateBox.checkState() == Qt.CheckState.PartiallyChecked:
options.splitter = 2
elif GUI.rotateBox.checkState() == Qt.CheckState.Checked:
options.splitter = 1
if GUI.qualityBox.checkState() == Qt.CheckState.PartiallyChecked:
options.autoscale = True
elif GUI.qualityBox.checkState() == Qt.CheckState.Checked:
options.hq = True
if GUI.vertical4PanelBox.isChecked():
options.vertical4panel = True
if GUI.webtoonBox.isChecked():
options.webtoon = True
if GUI.upscaleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.stretch = True
elif GUI.upscaleBox.checkState() == Qt.CheckState.Checked:
options.upscale = True
if GUI.gammaBox.isChecked() and float(GUI.gammaValue) > 0.09:
options.gamma = float(GUI.gammaValue)
if GUI.autoLevelBox.isChecked():
options.autolevel = True
if GUI.autocontrastBox.checkState() == Qt.CheckState.PartiallyChecked:
options.noautocontrast = True
elif GUI.autocontrastBox.checkState() == Qt.CheckState.Checked:
options.colorautocontrast = True
if GUI.croppingBox.isChecked():
if GUI.croppingBox.checkState() == Qt.CheckState.PartiallyChecked:
options.cropping = 1
else:
options.cropping = 2
else:
options.cropping = 0
if GUI.croppingBox.checkState() != Qt.CheckState.Unchecked:
options.croppingp = float(GUI.croppingPowerValue)
options.preservemargin = GUI.preserveMarginBox.value()
if GUI.interPanelCropBox.isChecked():
if GUI.interPanelCropBox.checkState() == Qt.CheckState.PartiallyChecked:
options.interpanelcrop = 1
else:
options.interpanelcrop = 2
else:
options.interpanelcrop = 0
if GUI.borderBox.checkState() == Qt.CheckState.PartiallyChecked:
options.white_borders = True
elif GUI.borderBox.checkState() == Qt.CheckState.Checked:
options.black_borders = True
if GUI.outputSplit.isChecked():
options.batchsplit = 2
if GUI.colorBox.isChecked():
options.forcecolor = True
if GUI.eraseRainbowBox.isChecked():
options.eraserainbow = True
if GUI.maximizeStrips.isChecked():
options.maximizestrips = True
if GUI.disableProcessingBox.isChecked():
options.noprocessing = True
if GUI.legacyExtractBox.isChecked():
options.legacyextract = True
if GUI.pdfWidthBox.isChecked():
options.pdfwidth = True
if GUI.smartCoverCropBox.isChecked():
options.smartcovercrop = True
if GUI.coverFillBox.isChecked():
options.coverfill = True
if GUI.metadataTitleBox.checkState() == Qt.CheckState.PartiallyChecked:
options.metadatatitle = 1
elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked:
options.metadatatitle = 2
if GUI.keepComicInfoBox.isChecked():
options.keepcomicinfo = True
if GUI.deleteBox.isChecked():
options.delete = True
if GUI.tempDirBox.isChecked():
options.tempdir = True
if GUI.spreadShiftBox.isChecked():
options.spreadshift = True
if GUI.onePageLandscapeBox.isChecked():
options.onepagelandscape = True
if GUI.fileFusionBox.isChecked():
options.filefusion = True
else:
options.filefusion = False
if GUI.noRotateBox.isChecked():
options.norotate = True
if GUI.rotateRightBox.isChecked():
options.rotateright = True
if GUI.rotateFirstBox.isChecked():
options.rotatefirst = True
if GUI.forcePngRgbBox.isChecked():
options.force_png_rgb = True
if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked:
options.forcepng = True
elif GUI.mozJpegBox.checkState() == Qt.CheckState.Checked:
options.mozjpeg = True
if GUI.webpBox.isChecked():
options.webp = True
if GUI.pngLegacyBox.isChecked():
options.pnglegacy = True
if GUI.noQuantizeBox.isChecked():
options.noquantize = True
if GUI.jpegQualityBox.isChecked():
options.jpegquality = GUI.jpegQualitySpinBox.value()
if GUI.currentMode > 2:
options.customwidth = str(GUI.widthBox.value())
options.customheight = str(GUI.heightBox.value())
if GUI.targetDirectory != '':
options.output = GUI.targetDirectory
if GUI.titleEdit.text():
options.title = str(GUI.titleEdit.text())
if GUI.authorEdit.text():
options.author = str(GUI.authorEdit.text())
if GUI.languageEdit.text():
options.language = str(GUI.languageEdit.text())
if GUI.chunkSizeCheckBox.isChecked():
options.targetsize = int(GUI.chunkSizeBox.value())
for i in range(GUI.jobList.count()): for i in range(GUI.jobList.count()):
# Make sure that we don't consider any system message as job to do # Make sure that we don't consider any system message as job to do
@@ -670,76 +659,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.jobList.addItem(dname) GUI.jobList.addItem(dname)
GUI.jobList.scrollToBottom() GUI.jobList.scrollToBottom()
def labelSpreadsStart(self):
currentJobs = []
# TODO: make this a function since it's copy pasted
for i in range(GUI.jobList.count()):
# Make sure that we don't consider any system message as job to do
if GUI.jobList.item(i).icon().isNull():
currentJobs.append(str(GUI.jobList.item(i).text()))
for job in currentJobs:
images = []
spreads = []
options, _ = get_options()
options.profileData = [(600, 800), (600,800)]
path = getWorkFolder(job, options)
removeNonImages(path)
sanitizeTree(path, options)
flattenTree(path)
if options.tempdir:
workdir = mkdtemp('', 'KCC-', os.path.dirname(job))
else:
workdir = mkdtemp('', 'KCC-')
for root, _, files in os.walk(path):
files.sort(key=OS_SORT_KEY)
start_index = 0
if job.endswith('.pdf') or job.endswith('.epub'):
start_index = 1
if options.spreadshift:
start_index = 0 if start_index == 1 else 1
for i in range(start_index, len(files), 2):
if i == len(files) - 1:
continue
# TODO: with statements
# TODO: ignore 1% of top and bottom too?
im1 = Image.open(os.path.join(root, files[i])).convert('1', dither=Dither.NONE)
size1 = im1.size
crop1 = im1.crop((0, 0, 0.2*size1[0], size1[1]))
crop11 = im1.crop((0.01*size1[0], 0, 0.04*size1[0], size1[1]))
#crop1 = crop11
im2 = Image.open(os.path.join(root, files[i+1])).convert('1', dither=Dither.NONE)
size2 = im2.size
crop2 = im2.crop((0.8*size2[0], 0, size2[0], size2[1]))
crop22 = im2.crop((0.96*size2[0], 0, .99* size2[0], size2[1]))
#crop2 = crop22
# dst = Image.new('1', (im1.width + im2.width, im1.height))
# dst.paste(im2, (0, 0))
# dst.paste(im1, (im1.width, 0))
hist1 = crop11.histogram()
hist2 = crop22.histogram()
# TODO: small percentage instead of zero
if hist1[0] == 0 or hist1[-1] == 0 or hist2[0] == 0 or hist2[-1] == 0:
continue
dst = Image.new('1', (crop1.width + crop2.width, crop1.height))
dst.paste(crop2, (0, 0))
dst.paste(crop1, (crop1.width, 0))
dst.save(os.path.join(workdir, f'label-{i:04}.png'))
images.append(os.path.join(workdir, f'label-{i:04}.png'))
if images:
dlg = LabelSpreadsDialog(APP.primaryScreen().availableGeometry().height(), images, spreads)
dlg.setWindowTitle(job)
if dlg.exec() == 1:
with open(job+'.json', "w") as fp:
# TODO: not very clean to grab index from filename
spreads = [int(filename[6:10]) for filename in spreads]
json.dump({'spreads': sorted(set(spreads))} , fp)
rmtree(path, True)
rmtree(workdir, True)
def selectFileMetaEditor(self, sname): def selectFileMetaEditor(self, sname):
files = [] files = []
@@ -1517,7 +1436,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'README': "https://github.com/ciromattia/kcc?tab=readme-ov-file#kcc", 'README': "https://github.com/ciromattia/kcc?tab=readme-ov-file#kcc",
'FAQ': "https://github.com/ciromattia/kcc/blob/master/README.md#faq", 'FAQ': "https://github.com/ciromattia/kcc/blob/master/README.md#faq",
'WIKI': "https://github.com/ciromattia/kcc/wiki", 'WIKI': "https://github.com/ciromattia/kcc/wiki",
'YOUTUBE': "https://www.youtube.com/@eink-dude",
'TUTORIAL': "https://youtu.be/QQ6zJcMF2Iw?si=80rfm6DU6OUJdFqa", 'TUTORIAL': "https://youtu.be/QQ6zJcMF2Iw?si=80rfm6DU6OUJdFqa",
'EMAIL': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions", 'EMAIL': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions",
'DONATE': "https://github.com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations", 'DONATE': "https://github.com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations",
@@ -1554,7 +1472,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.kofiButton.clicked.connect(self.openKofi) GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble) GUI.humbleButton.clicked.connect(self.openHumble)
GUI.convertButton.clicked.connect(self.convertStart) GUI.convertButton.clicked.connect(self.convertStart)
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma) GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox) GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
GUI.croppingBox.stateChanged.connect(self.togglecroppingBox) GUI.croppingBox.stateChanged.connect(self.togglecroppingBox)
-92
View File
@@ -1,92 +0,0 @@
import os
from PySide6.QtCore import (Qt)
from PySide6.QtGui import (QKeyEvent, QPixmap)
from PySide6.QtWidgets import (QHBoxLayout, QLabel, QDialog)
class LabelSpreadsDialog(QDialog):
def __init__(self, available_height, images, spreads):
super().__init__()
self.index = 0
self.images = images
self.spreads = spreads
self.index2page = {i: os.path.basename(image) for i, image in enumerate(images)}
self.setWindowTitle("TODO: Filename goes here")
# self.setGeometry(APP.primaryScreen().availableGeometry())
# self.setMaximumSize(APP.primaryScreen().availableSize())
self.available_height = available_height
layout = QHBoxLayout()
self.setLayout(layout)
label = QLabel()
label2 = QLabel()
self.label = label
self.label2 = label2
layout.addWidget(label)
layout.addWidget(label2)
label2.setText("not a spread")
help_text = [
"Use arrows to change index.",
"Use space bar to confirm spreads.",
"Use spread shift option to offset by 1.",
"Use enter key to confirm all spreads."
"Close window to cancel."
]
buttonLabel = QLabel('\n'.join(help_text))
layout.addWidget(buttonLabel)
# print(label.size())
# print(label.maximumSize())
# l, t, r, b = layout.getContentsMargins()
#label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
pixmap = QPixmap(images[0]).scaledToHeight(self.available_height * 0.9)
label.setPixmap(pixmap)
#label.setScaledContents(True)
#label2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# pixmap2 = QPixmap(images[0]).scaledToHeight(self.frameGeometry().height() - t - b - t - b)
# label2.setPixmap(pixmap2)
#label2.setScaledContents(True)
#self.resize(pixmap2.width(), pixmap2.height())
def keyReleaseEvent(self, event):
# t = 20
# b = 20
if isinstance(event, QKeyEvent):
if event.key() == Qt.Key.Key_Left:
self.index = max(0, self.index - 1)
if self.index2page[self.index] in self.spreads:
self.label2.setText('spread')
else:
self.label2.setText('not a spread')
pixmap = QPixmap(self.images[self.index]).scaledToHeight(self.available_height * 0.9)
self.label.setPixmap(pixmap)
# pixmap2 = QPixmap(images[self.index]).scaledToHeight(self.frameGeometry().height() - t - b - t - b)
# self.label2.setPixmap(pixmap2)
elif event.key() == Qt.Key.Key_Right:
self.index = min(self.index + 1, len(self.images) - 1)
if self.index2page[self.index] in self.spreads:
self.label2.setText('spread')
else:
self.label2.setText('not a spread')
pixmap = QPixmap(self.images[self.index]).scaledToHeight(self.available_height * 0.9)
self.label.setPixmap(pixmap)
# pixmap2 = QPixmap(images[self.index]).scaledToHeight(self.frameGeometry().height() - t - b - t - b)
# self.label2.setPixmap(pixmap2)
elif event.key() == Qt.Key.Key_Space:
if self.label2.text() == "not a spread":
self.spreads.append(os.path.basename(self.images[self.index]))
self.label2.setText('spread')
else:
self.spreads.remove(os.path.basename(self.images[self.index]))
self.label2.setText('not a spread')
elif event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter:
self.accept()
else:
super().keyReleaseEvent(event)
else:
super().keyReleaseEvent(event)
+52 -59
View File
@@ -26,7 +26,7 @@ class Ui_mainWindow(object):
def setupUi(self, mainWindow): def setupUi(self, mainWindow):
if not mainWindow.objectName(): if not mainWindow.objectName():
mainWindow.setObjectName(u"mainWindow") mainWindow.setObjectName(u"mainWindow")
mainWindow.resize(649, 706) mainWindow.resize(644, 706)
icon = QIcon() icon = QIcon()
icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon.addFile(u":/Icon/icons/comic2ebook.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
mainWindow.setWindowIcon(icon) mainWindow.setWindowIcon(icon)
@@ -360,39 +360,33 @@ class Ui_mainWindow(object):
self.gridLayout_6 = QGridLayout(self.toolWidget) self.gridLayout_6 = QGridLayout(self.toolWidget)
self.gridLayout_6.setObjectName(u"gridLayout_6") self.gridLayout_6.setObjectName(u"gridLayout_6")
self.gridLayout_6.setContentsMargins(0, 0, 0, 0) self.gridLayout_6.setContentsMargins(0, 0, 0, 0)
self.editorButton = QPushButton(self.toolWidget)
self.editorButton.setObjectName(u"editorButton")
self.editorButton.setMinimumSize(QSize(0, 30))
icon2 = QIcon()
icon2.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.editorButton.setIcon(icon2)
self.gridLayout_6.addWidget(self.editorButton, 0, 0, 1, 1)
self.humbleButton = QPushButton(self.toolWidget) self.humbleButton = QPushButton(self.toolWidget)
self.humbleButton.setObjectName(u"humbleButton") self.humbleButton.setObjectName(u"humbleButton")
self.humbleButton.setMinimumSize(QSize(0, 30)) self.humbleButton.setMinimumSize(QSize(0, 30))
icon3 = QIcon() icon2 = QIcon()
icon3.addFile(u":/Brand/icons/Humble_H-Red.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon2.addFile(u":/Brand/icons/Humble_H-Red.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.humbleButton.setIcon(icon3) self.humbleButton.setIcon(icon2)
self.gridLayout_6.addWidget(self.humbleButton, 0, 3, 1, 1) self.gridLayout_6.addWidget(self.humbleButton, 0, 2, 1, 1)
self.kofiButton = QPushButton(self.toolWidget) self.kofiButton = QPushButton(self.toolWidget)
self.kofiButton.setObjectName(u"kofiButton") self.kofiButton.setObjectName(u"kofiButton")
self.kofiButton.setMinimumSize(QSize(0, 30)) self.kofiButton.setMinimumSize(QSize(0, 30))
icon4 = QIcon() icon3 = QIcon()
icon4.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon3.addFile(u":/Brand/icons/kofi_symbol.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.kofiButton.setIcon(icon4) self.kofiButton.setIcon(icon3)
self.kofiButton.setIconSize(QSize(19, 16)) self.kofiButton.setIconSize(QSize(19, 16))
self.gridLayout_6.addWidget(self.kofiButton, 0, 2, 1, 1) self.gridLayout_6.addWidget(self.kofiButton, 0, 1, 1, 1)
self.labelSpreadsButton = QPushButton(self.toolWidget) self.editorButton = QPushButton(self.toolWidget)
self.labelSpreadsButton.setObjectName(u"labelSpreadsButton") self.editorButton.setObjectName(u"editorButton")
self.labelSpreadsButton.setMinimumSize(QSize(0, 30)) self.editorButton.setMinimumSize(QSize(0, 30))
icon4 = QIcon()
icon4.addFile(u":/Other/icons/editor.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.editorButton.setIcon(icon4)
self.gridLayout_6.addWidget(self.labelSpreadsButton, 0, 1, 1, 1) self.gridLayout_6.addWidget(self.editorButton, 0, 0, 1, 1)
self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2) self.gridLayout.addWidget(self.toolWidget, 0, 0, 1, 2)
@@ -549,20 +543,39 @@ class Ui_mainWindow(object):
self.gridLayout_4 = QGridLayout(self.buttonWidget) self.gridLayout_4 = QGridLayout(self.buttonWidget)
self.gridLayout_4.setObjectName(u"gridLayout_4") self.gridLayout_4.setObjectName(u"gridLayout_4")
self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.convertButton = QPushButton(self.buttonWidget)
self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30))
self.convertButton.setFont(font)
icon5 = QIcon()
icon5.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.convertButton.setIcon(icon5)
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
self.clearButton = QPushButton(self.buttonWidget)
self.clearButton.setObjectName(u"clearButton")
self.clearButton.setMinimumSize(QSize(0, 30))
icon6 = QIcon()
icon6.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.clearButton.setIcon(icon6)
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1)
self.deviceBox = QComboBox(self.buttonWidget) self.deviceBox = QComboBox(self.buttonWidget)
self.deviceBox.setObjectName(u"deviceBox") self.deviceBox.setObjectName(u"deviceBox")
self.deviceBox.setMinimumSize(QSize(0, 28)) self.deviceBox.setMinimumSize(QSize(0, 28))
self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1) self.gridLayout_4.addWidget(self.deviceBox, 1, 1, 1, 1)
self.clearButton = QPushButton(self.buttonWidget) self.fileButton = QPushButton(self.buttonWidget)
self.clearButton.setObjectName(u"clearButton") self.fileButton.setObjectName(u"fileButton")
self.clearButton.setMinimumSize(QSize(0, 30)) self.fileButton.setMinimumSize(QSize(0, 30))
icon5 = QIcon() icon7 = QIcon()
icon5.addFile(u":/Other/icons/clear.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.clearButton.setIcon(icon5) self.fileButton.setIcon(icon7)
self.gridLayout_4.addWidget(self.clearButton, 0, 3, 1, 1) self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
self.directoryButton = QPushButton(self.buttonWidget) self.directoryButton = QPushButton(self.buttonWidget)
self.directoryButton.setObjectName(u"directoryButton") self.directoryButton.setObjectName(u"directoryButton")
@@ -575,25 +588,6 @@ class Ui_mainWindow(object):
self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1) self.gridLayout_4.addWidget(self.directoryButton, 0, 4, 1, 1)
self.convertButton = QPushButton(self.buttonWidget)
self.convertButton.setObjectName(u"convertButton")
self.convertButton.setMinimumSize(QSize(0, 30))
self.convertButton.setFont(font)
icon6 = QIcon()
icon6.addFile(u":/Other/icons/convert.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.convertButton.setIcon(icon6)
self.gridLayout_4.addWidget(self.convertButton, 1, 3, 1, 1)
self.fileButton = QPushButton(self.buttonWidget)
self.fileButton.setObjectName(u"fileButton")
self.fileButton.setMinimumSize(QSize(0, 30))
icon7 = QIcon()
icon7.addFile(u":/Other/icons/document_new.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.fileButton.setIcon(icon7)
self.gridLayout_4.addWidget(self.fileButton, 0, 1, 1, 1)
self.formatBox = QComboBox(self.buttonWidget) self.formatBox = QComboBox(self.buttonWidget)
self.formatBox.setObjectName(u"formatBox") self.formatBox.setObjectName(u"formatBox")
self.formatBox.setMinimumSize(QSize(0, 28)) self.formatBox.setMinimumSize(QSize(0, 28))
@@ -854,13 +848,12 @@ class Ui_mainWindow(object):
"Keeping this file may crash some readers like the Kobo native CBZ reader.", None)) "Keeping this file may crash some readers like the Kobo native CBZ reader.", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.keepComicInfoBox.setText(QCoreApplication.translate("mainWindow", u"Keep ComicInfo.xml", None)) self.keepComicInfoBox.setText(QCoreApplication.translate("mainWindow", u"Keep ComicInfo.xml", None))
self.humbleButton.setText(QCoreApplication.translate("mainWindow", u"Humble Bundle Referral", None))
self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None)) self.editorButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to edit directory.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None)) self.editorButton.setText(QCoreApplication.translate("mainWindow", u"Metadata Editor", None))
self.humbleButton.setText(QCoreApplication.translate("mainWindow", u"Humble Bundle Referral", None))
self.kofiButton.setText(QCoreApplication.translate("mainWindow", u"Support me on Ko-fi", None))
self.labelSpreadsButton.setText(QCoreApplication.translate("mainWindow", u"Label Spreads", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None)) self.chunkSizeWidget.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Warning: chunk size greater than default may cause<br/>performance/battery issues, especially on older devices.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
@@ -885,22 +878,22 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None)) self.heightBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
#if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html>", None)) self.convertButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Shift+Click to select the output directory for this list.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None)) self.convertButton.setText(QCoreApplication.translate("mainWindow", u"Convert", None))
self.clearButton.setText(QCoreApplication.translate("mainWindow", u"Clear list", None))
#if QT_CONFIG(tooltip)
self.deviceBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Target device.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None)) self.fileButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add CBR, CBZ, CB7 or PDF file to queue.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None)) self.fileButton.setText(QCoreApplication.translate("mainWindow", u"Add input file(s)", None))
#if QT_CONFIG(tooltip)
self.directoryButton.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Add directory containing JPG, PNG or GIF files to queue.<br/><span style=\" font-weight:600;\">CBR, CBZ and CB7 files inside will not be processed!</span></p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.directoryButton.setText(QCoreApplication.translate("mainWindow", u"Add input folder(s)", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None)) self.formatBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Output format.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '11.0.0' __version__ = '10.4.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'
+13 -40
View File
@@ -20,7 +20,6 @@
from collections import Counter from collections import Counter
from datetime import datetime from datetime import datetime
import json
import os import os
import pathlib import pathlib
import re import re
@@ -873,7 +872,7 @@ def extract_page(vector):
def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_height, pdfwidth): def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, 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:
@@ -893,7 +892,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, pdfwidth) 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)) print("Starting %i processes for '%s'." % (cpu, filename))
@@ -907,7 +906,7 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
def getWorkFolder(afile, options, workdir=None): def getWorkFolder(afile, workdir=None):
if not workdir: if not workdir:
if options.tempdir: if options.tempdir:
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile)) workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
@@ -956,7 +955,7 @@ def getWorkFolder(afile, options, workdir=None):
target_height *= 1.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 target_width *= 1.25
try: try:
mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, target_height, options.pdfwidth) mupdf_pdf_process_pages_parallel(afile, fullPath, target_width, 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}")
@@ -980,16 +979,9 @@ def getWorkFolder(afile, options, workdir=None):
os.path.join(fullPath, tdir[0], 'ComicInfo.xml') os.path.join(fullPath, tdir[0], 'ComicInfo.xml')
) )
if len(tdir) == 1 and os.path.isdir(os.path.join(fullPath, tdir[0])): if len(tdir) == 1 and os.path.isdir(os.path.join(fullPath, tdir[0])):
if options.tempdir:
workdir2 = mkdtemp('', 'KCC-', os.path.dirname(afile))
else:
workdir2 = mkdtemp('', 'KCC-')
fullPath2 = os.path.join(workdir2, 'OEBPS', 'Images')
os.makedirs(fullPath2, exist_ok=True)
for file in os.listdir(os.path.join(fullPath, tdir[0])): for file in os.listdir(os.path.join(fullPath, tdir[0])):
move(os.path.join(fullPath, tdir[0], file), fullPath2) move(os.path.join(fullPath, tdir[0], file), fullPath)
rmtree(workdir, True) os.rmdir(os.path.join(fullPath, tdir[0]))
return workdir2
if options.legacyextract: if options.legacyextract:
return workdir return workdir
@@ -1230,7 +1222,7 @@ def removeNonImages(filetree):
raise RuntimeError('\n'.join(warning)) raise RuntimeError('\n'.join(warning))
def sanitizeTree(filetree, options, prefix='kcc'): def sanitizeTree(filetree, prefix='kcc'):
chapterNames = {} chapterNames = {}
page = 1 page = 1
cover_path = None cover_path = None
@@ -1255,7 +1247,7 @@ def sanitizeTree(filetree, options, prefix='kcc'):
dirs.sort(key=OS_SORT_KEY) dirs.sort(key=OS_SORT_KEY)
for i, name in enumerate(dirs): for i, name in enumerate(dirs):
tmpName = name tmpName = name
slugified = slugify(name, options, is_natural_sorted) slugified = slugify(name, is_natural_sorted)
while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper(): while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper():
slugified += "A" slugified += "A"
chapterNames[slugified] = tmpName chapterNames[slugified] = tmpName
@@ -1418,7 +1410,7 @@ def createNewTome(parent):
return tomePath, tomePathRoot return tomePath, tomePathRoot
def slugify(value, options, is_natural_sorted): def slugify(value, is_natural_sorted):
if options.format == 'CBZ' and is_natural_sorted: if options.format == 'CBZ' and is_natural_sorted:
return value return value
if options.format != 'CBZ': if options.format != 'CBZ':
@@ -1783,10 +1775,10 @@ def makeFusion(sources: List[str]):
else: else:
targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}') targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}')
path = getWorkFolder(source, options, str(targetpath)) path = getWorkFolder(source, str(targetpath))
if path != str(targetpath): if path != str(targetpath):
move(os.path.join(path, 'OEBPS', 'Images'), targetpath) move(os.path.join(path, 'OEBPS', 'Images'), targetpath)
sanitizeTree(targetpath, options, prefix='fusion') sanitizeTree(targetpath, prefix='fusion')
# TODO: remove flattenTree when subchapters are supported # TODO: remove flattenTree when subchapters are supported
flattenTree(targetpath) flattenTree(targetpath)
@@ -1809,7 +1801,7 @@ def makeBook(source, qtgui=None, job_progress=''):
if not options.filefusion: if not options.filefusion:
checkPre('LLL-') checkPre('LLL-')
print(f"{job_progress}Preparing source images...") print(f"{job_progress}Preparing source images...")
path = getWorkFolder(source, options) path = getWorkFolder(source)
print(f"{job_progress}Checking images...") print(f"{job_progress}Checking images...")
if options.lightnovel: if options.lightnovel:
@@ -1843,26 +1835,7 @@ def makeBook(source, qtgui=None, job_progress=''):
getMetadata(os.path.join(path, "OEBPS", "Images"), source) getMetadata(os.path.join(path, "OEBPS", "Images"), source)
removeNonImages(os.path.join(path, "OEBPS", "Images")) removeNonImages(os.path.join(path, "OEBPS", "Images"))
detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source) detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source)
chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'), options) chapterNames, cover_path = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if os.path.exists(source+'.json'):
flattenTree(os.path.join(path, 'OEBPS', 'Images'))
with open(source+'.json') as f:
data = json.load(f)
for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
sorted_files = os_sorted(files)
for i in range(len(files)):
if i in data['spreads']:
im1 = Image.open(os.path.join(root, sorted_files[i]))
im2 = Image.open(os.path.join(root, sorted_files[i+1]))
dst = Image.new('RGB', (im1.width + im2.width, im1.height))
dst.paste(im2, (0, 0))
dst.paste(im1, (im1.width, 0))
base, _ = os.path.splitext(os.path.basename(sorted_files[i]))
dst.save(os.path.join(path, 'OEBPS', 'Images', f'{base}-merged.png'))
os.remove(os.path.join(root, sorted_files[i]))
os.remove(os.path.join(root, sorted_files[i+1]))
if options.filefusion: if options.filefusion:
# Strip the fusion_0001_ sort prefix from makeFusion if present # Strip the fusion_0001_ sort prefix from makeFusion if present
chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()} chapterNames = {k: sub(r'^fusion_\d{4}_', '', v) for k, v in chapterNames.items()}