From ee375abfc51da95139bbc3e97a535a104a7154c0 Mon Sep 17 00:00:00 2001 From: Silver0006 <52266188+Silver0006@users.noreply.github.com> Date: Sun, 15 Jun 2025 17:58:03 -0400 Subject: [PATCH] Added ability to combine multiple CBZ into one files (#960) * Added basic CBZ combine func Need to add support for epub and maybe mobi. * Removed irrelevant code for CBZ file fusion * Fixed false description * Removed irrelevant code * Removed redundant code Replaced page tracker and os.rename with os.renames. Removed unneeded reference to gui. Changed mkdir to mkdtemp. * Made folder and cbz work together You can select multiple folders of images, multiple cbz files, and folders with subfolders. Fusion will combine them all together at the same time. Mainly added this to idiot proof it. * Updated gui Removed redundant tooltip * simplify code * fix merging chapter folders with . * uncheck fusion message --------- Co-authored-by: Alex Xu --- gui/KCC.ui | 10 +++++++ kindlecomicconverter/KCC_gui.py | 24 ++++++++++++++++ kindlecomicconverter/KCC_rc.py | 40 +++++++++++++-------------- kindlecomicconverter/KCC_ui.py | 11 +++++++- kindlecomicconverter/KCC_ui_editor.py | 2 +- kindlecomicconverter/comic2ebook.py | 36 ++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 22 deletions(-) diff --git a/gui/KCC.ui b/gui/KCC.ui index ed691d7..4991751 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -590,6 +590,16 @@ + + + + <html><head/><body><p>Combines all selected files into a single file. (Helpful for combining chapters into volumes.)</p></body></html> + + + File Fusion + + + diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index af2c41c..87c83d9 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -267,6 +267,10 @@ class WorkerThread(QThread): options.delete = True if GUI.spreadShiftBox.isChecked(): options.spreadshift = True + if GUI.fileFusionBox.isChecked(): + options.filefusion = True + else: + options.filefusion = False if GUI.noRotateBox.isChecked(): options.norotate = True if GUI.mozJpegBox.checkState() == Qt.CheckState.PartiallyChecked: @@ -288,6 +292,19 @@ class WorkerThread(QThread): if GUI.jobList.item(i).icon().isNull(): currentJobs.append(str(GUI.jobList.item(i).text())) GUI.jobList.clear() + if options.filefusion: + bookDir = [] + MW.addMessage.emit('Attempting file fusion', 'info', False) + for job in currentJobs: + bookDir.append(job) + try: + comic2ebook.options = comic2ebook.checkOptions(copy(options)) + currentJobs.clear() + currentJobs.append(comic2ebook.makeFusion(bookDir)) + MW.addMessage.emit('Created fusion at ' + currentJobs[0], 'info', False) + except Exception as e: + print('Fusion Failed. ' + str(e)) + MW.addMessage.emit('Fusion Failed. ' + str(e), 'error', True) for job in currentJobs: sleep(0.5) if not self.conversionAlive: @@ -433,6 +450,12 @@ class WorkerThread(QThread): move(item, GUI.targetDirectory) except Exception: pass + if options.filefusion: + for path in currentJobs: + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + rmtree(path) GUI.progress.content = '' GUI.progress.stop() MW.hideProgressBar.emit() @@ -825,6 +848,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'heightBox': GUI.heightBox.value(), 'deleteBox': GUI.deleteBox.checkState().value, 'spreadShiftBox': GUI.spreadShiftBox.checkState().value, + 'fileFusionBox': GUI.fileFusionBox.checkState().value, 'noRotateBox': GUI.noRotateBox.checkState().value, 'maximizeStrips': GUI.maximizeStrips.checkState().value, 'gammaSlider': float(self.gammaValue) * 100, diff --git a/kindlecomicconverter/KCC_rc.py b/kindlecomicconverter/KCC_rc.py index e0eb11f..46ed290 100644 --- a/kindlecomicconverter/KCC_rc.py +++ b/kindlecomicconverter/KCC_rc.py @@ -1,6 +1,6 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.9.0 +# Created by: The Resource Compiler for Qt version 6.9.1 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore @@ -11612,51 +11612,51 @@ qt_resource_struct = b"\ \x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x07\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x02.\xed\ -\x00\x00\x01\x97 [\xc3\x1b\ +\x00\x00\x01\x97\x5c>\xea\xe6\ \x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x02\x83\x87\ -\x00\x00\x01\x97 [\xc3\x19\ +\x00\x00\x01\x97\x5c>\xea\xe5\ \x00\x00\x01\xea\x00\x00\x00\x00\x00\x01\x00\x02Y\x8c\ -\x00\x00\x01\x97 [\xc3\x19\ +\x00\x00\x01\x97\x5c>\xea\xe4\ \x00\x00\x01\xd6\x00\x00\x00\x00\x00\x01\x00\x02N)\ -\x00\x00\x01\x97 [\xc3\x1a\ +\x00\x00\x01\x97\x5c>\xea\xe5\ \x00\x00\x00X\x00\x02\x00\x00\x00\x04\x00\x00\x00\x0c\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x01(\x97\ -\x00\x00\x01\x97 [\xc3\x1b\ +\x00\x00\x01\x97\x5c>\xea\xe6\ \x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x011\xef\ -\x00\x00\x01\x97 [\xc3\x1c\ +\x00\x00\x01\x97\x5c>\xea\xe7\ \x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x01\x1d\x90\ -\x00\x00\x01\x97 [\xc3\x1a\ +\x00\x00\x01\x97\x5c>\xea\xe5\ \x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x01:\x05\ -\x00\x00\x01\x97 [\xc3\x1c\ +\x00\x00\x01\x97\x5c>\xea\xe6\ \x00\x00\x00X\x00\x02\x00\x00\x00\x03\x00\x00\x00\x11\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x02B\x00\x00\x00\x00\x00\x01\x00\x02\xb5\xd3\ -\x00\x00\x01\x97 [\xc3*\ +\x00\x00\x01\x97\x5c>\xea\xf1\ \x00\x00\x02\x14\x00\x00\x00\x00\x00\x01\x00\x02\x9f\xd6\ -\x00\x00\x01\x97 [\xc3(\ +\x00\x00\x01\x97\x5c>\xea\xf0\ \x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x02\xa93\ -\x00\x00\x01\x97 [\xc3(\ +\x00\x00\x01\x97\x5c>\xea\xef\ \x00\x00\x00X\x00\x02\x00\x00\x00\x07\x00\x00\x00\x15\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x01P\xb1\ -\x00\x00\x01\x97 [\xc3+\ +\x00\x00\x01\x97\x5c>\xea\xf1\ \x00\x00\x012\x00\x00\x00\x00\x00\x01\x00\x01yY\ -\x00\x00\x01\x97 [\xc3(\ +\x00\x00\x01\x97\x5c>\xea\xef\ \x00\x00\x01\x94\x00\x00\x00\x00\x00\x01\x00\x01\xd2-\ -\x00\x00\x01\x97 [\xc3)\ +\x00\x00\x01\x97\x5c>\xea\xf0\ \x00\x00\x01z\x00\x00\x00\x00\x00\x01\x00\x01\x8c\xe6\ -\x00\x00\x01\x97 [\xc3'\ +\x00\x00\x01\x97\x5c>\xea\xef\ \x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x01LR\ -\x00\x00\x01\x97 [\xc3!\ +\x00\x00\x01\x97\x5c>\xea\xea\ \x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x01?\xe9\ -\x00\x00\x01\x97 [\xc3&\ +\x00\x00\x01\x97\x5c>\xea\xee\ \x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x01\x82\xb0\ -\x00\x00\x01\x97 [\xc3'\ +\x00\x00\x01\x97\x5c>\xea\xef\ \x00\x00\x00X\x00\x02\x00\x00\x00\x01\x00\x00\x00\x1d\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00h\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x97 [\xc3&\ +\x00\x00\x01\x97\x5c>\xea\xee\ " def qInitResources(): diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 4a03a26..288b950 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'KCC.ui' ## -## Created by: Qt User Interface Compiler version 6.9.0 +## Created by: Qt User Interface Compiler version 6.9.1 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ @@ -320,6 +320,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.spreadShiftBox, 5, 0, 1, 1) + self.fileFusionBox = QCheckBox(self.optionWidget) + self.fileFusionBox.setObjectName(u"fileFusionBox") + + self.gridLayout_2.addWidget(self.fileFusionBox, 6, 0, 1, 1) + self.upscaleBox = QCheckBox(self.optionWidget) self.upscaleBox.setObjectName(u"upscaleBox") self.upscaleBox.setTristate(True) @@ -555,6 +560,10 @@ class Ui_mainWindow(object): self.spreadShiftBox.setToolTip(QCoreApplication.translate("mainWindow", u"Shift first page to opposite side in landscape for two page spread alignment", None)) #endif // QT_CONFIG(tooltip) self.spreadShiftBox.setText(QCoreApplication.translate("mainWindow", u"Spread shift", None)) +#if QT_CONFIG(tooltip) + self.fileFusionBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Combines all selected files into a single file. (Helpful for combining chapters into volumes.)

", None)) +#endif // QT_CONFIG(tooltip) + self.fileFusionBox.setText(QCoreApplication.translate("mainWindow", u"File Fusion", None)) #if QT_CONFIG(tooltip) self.upscaleBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Unchecked - Nothing
Images smaller than device resolution will not be resized.

Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.

Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.

", None)) #endif // QT_CONFIG(tooltip) diff --git a/kindlecomicconverter/KCC_ui_editor.py b/kindlecomicconverter/KCC_ui_editor.py index a9526c9..75fac37 100644 --- a/kindlecomicconverter/KCC_ui_editor.py +++ b/kindlecomicconverter/KCC_ui_editor.py @@ -3,7 +3,7 @@ ################################################################################ ## Form generated from reading UI file 'MetaEditor.ui' ## -## Created by: Qt User Interface Compiler version 6.9.0 +## Created by: Qt User Interface Compiler version 6.9.1 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 498651b..6d76b2c 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -28,6 +28,7 @@ from copy import copy from glob import glob, escape from re import sub from stat import S_IWRITE, S_IREAD, S_IEXEC +from typing import List from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED from tempfile import mkdtemp, gettempdir, TemporaryFile from shutil import move, copytree, rmtree, copyfile @@ -36,6 +37,7 @@ from uuid import uuid4 from natsort import os_sort_keygen from slugify import slugify as slugify_ext from PIL import Image, ImageFile +from pathlib import Path from subprocess import STDOUT, PIPE, CalledProcessError from psutil import virtual_memory, disk_usage from html import escape as hescape @@ -1242,6 +1244,40 @@ def checkPre(source): raise UserWarning("Target directory is not writable.") +def makeFusion(sources: List[str]): + if len(sources) < 2: + raise UserWarning('Fusion requires at least 2 sources. Did you forget to uncheck fusion?') + start = perf_counter() + first_path = Path(sources[0]) + if first_path.is_file(): + fusion_path = first_path.parent.joinpath(first_path.stem + ' [fused]') + else: + fusion_path = first_path.parent.joinpath(first_path.name + ' [fused]') + print("Running Fusion") + + for source in sources: + print(f"Processing {source}...") + checkPre(source) + print("Checking images...") + path = getWorkFolder(source) + pathfinder = os.path.join(path, "OEBPS", "Images") + sanitizeTree(pathfinder) + # TODO: remove flattenTree when subchapters are supported + flattenTree(pathfinder) + source_path = Path(source) + if source_path.is_file(): + os.renames(pathfinder, fusion_path.joinpath(source_path.stem)) + else: + os.renames(pathfinder, fusion_path.joinpath(source_path.name)) + + + end = perf_counter() + print(f"makefusion: {end - start} seconds") + print("Combined File: "+ str(fusion_path)) + + return str(fusion_path) + + def makeBook(source, qtgui=None): start = perf_counter() global GUI