From 3723bf9d520c7e9678973abcec4f21352c2204c8 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Fri, 26 Jun 2026 10:23:28 -0700 Subject: [PATCH] add light novel mode (#1361) * add a novel mode where it only resizes images * add GUI * cleanup * fix typo * fully cooked * remove print * clean up * cook * cook * handle LA case * cleanup --- README.md | 1 + gui/KCC.ui | 12 +++++++++ kindlecomicconverter/KCC_gui.py | 5 +++- kindlecomicconverter/KCC_ui.py | 11 ++++++++ kindlecomicconverter/comic2ebook.py | 40 +++++++++++++++++++++++++++-- 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8264376..afdde3b 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ MAIN: Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KPW5, KV, KO, K11, KS, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoN, KoC, KoCC, KoL, KoLC, KoF, KoS, KoE) [Default=KV] -m, --manga-style Manga style (right-to-left reading and splitting) + --lightnovel Only resize images and preserve original file structure. --ebok Force EBOK tag instead of PDOC for MOBI --invertdirection Invert page turn direction -q, --hq Try to increase the quality of magnification diff --git a/gui/KCC.ui b/gui/KCC.ui index 2b7e9a6..ea0fc7f 100644 --- a/gui/KCC.ui +++ b/gui/KCC.ui @@ -990,6 +990,18 @@ Ignored for Kindle EPUB/MOBI and all PDF. + + + + Only resize images and preserve original file structure. + +Ignores most options besides JPEG quality, color mode, output folder. + + + Light novel mode + + + diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 111df35..7c64fd1 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -273,6 +273,8 @@ class WorkerThread(QThread): 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(): @@ -476,7 +478,7 @@ class WorkerThread(QThread): MW.addMessage.emit('Creating PDF files... Done!', 'info', True) else: MW.addMessage.emit('Creating EPUB files... Done!', 'info', True) - if 'MOBI' in gui_current_format: + if 'MOBI' in gui_current_format and not options.lightnovel: MW.progressBarTick.emit(f'{job_progress_number}Creating MOBI files') MW.progressBarTick.emit(str(len(outputPath) * 2 + 1)) MW.progressBarTick.emit('tick') @@ -1085,6 +1087,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): self.settings.setValue('startNumber', self.startNumber + 1) self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height())) self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(), + 'lightnovelBox': GUI.lightnovelBox.checkState(), 'ebokBox': GUI.ebokBox.checkState(), 'invertDirectionBox': GUI.invertDirectionBox.checkState(), 'languageEdit': GUI.languageEdit.text(), diff --git a/kindlecomicconverter/KCC_ui.py b/kindlecomicconverter/KCC_ui.py index 26c17ba..b44704a 100644 --- a/kindlecomicconverter/KCC_ui.py +++ b/kindlecomicconverter/KCC_ui.py @@ -502,6 +502,11 @@ class Ui_mainWindow(object): self.gridLayout_2.addWidget(self.ebokBox, 10, 3, 1, 1) + self.lightnovelBox = QCheckBox(self.optionWidget) + self.lightnovelBox.setObjectName(u"lightnovelBox") + + self.gridLayout_2.addWidget(self.lightnovelBox, 10, 0, 1, 1) + self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2) @@ -877,6 +882,12 @@ class Ui_mainWindow(object): self.ebokBox.setToolTip(QCoreApplication.translate("mainWindow", u"

Force Kindle MOBI to be be tagged as EBOK instead of PDOC.

This may cause USB loaded books to be deleted if you go online after a month offline.

", None)) #endif // QT_CONFIG(tooltip) self.ebokBox.setText(QCoreApplication.translate("mainWindow", u"Force EBOK", None)) +#if QT_CONFIG(tooltip) + self.lightnovelBox.setToolTip(QCoreApplication.translate("mainWindow", u"Only resize images and preserve original file structure.\n" +"\n" +"Ignores most options besides JPEG quality, color mode, output folder.", None)) +#endif // QT_CONFIG(tooltip) + self.lightnovelBox.setText(QCoreApplication.translate("mainWindow", u"Light novel mode", None)) #if QT_CONFIG(tooltip) self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"

Resolution of the target device.

", None)) #endif // QT_CONFIG(tooltip) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 2ecc57a..a6a6ac9 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -39,7 +39,7 @@ from multiprocessing import Pool, cpu_count from uuid import uuid4 from natsort import os_sort_keygen, os_sorted from slugify import slugify as slugify_ext -from PIL import Image, ImageFile +from PIL import Image, ImageFile, ImageOps from pathlib import Path from subprocess import STDOUT, PIPE, CalledProcessError from psutil import virtual_memory, disk_usage @@ -965,6 +965,8 @@ def getWorkFolder(afile, workdir=None): try: cbx = comicarchive.ComicArchive(afile) path = cbx.extract(fullPath) + if options.lightnovel: + return workdir sanitizePermissions(path) tdir = os.listdir(fullPath) @@ -1448,6 +1450,8 @@ def makeParser(): " [Default=KV]") main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, help="Manga style (right-to-left reading and splitting)") + main_options.add_argument("--lightnovel", action="store_true", dest="lightnovel", default=False, + help="Only resize images and preserve original file structure.") main_options.add_argument("--ebok", action="store_true", dest="ebok", default=False, help="Force EBOK tag instead of PDOC for MOBI") main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False, @@ -1582,6 +1586,9 @@ def checkOptions(options): else: options.isKobo = True + if options.lightnovel: + options.noKepub = True + if not options.iskindle and ('MOBI' in options.format or 'EPUB-200MB' in options.format or 'KFX' in options.format): raise UserWarning('MOBI/Send to Kindle not supported for non-Kindle profiles') @@ -1779,6 +1786,35 @@ def makeBook(source, qtgui=None, job_progress=''): print(f"{job_progress}Preparing source images...") path = getWorkFolder(source) print(f"{job_progress}Checking images...") + + if options.lightnovel: + for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')): + for file in files: + _, ext = os.path.splitext(file) + if ext.lower() in ('.jpg', '.jpeg', '.png', '.webp', '.gif'): + with Image.open(os.path.join(root, file)) as img: + # TODO: detect BW images saved as RGB + if not options.forcecolor: + if img.mode == 'RGB': + img = img.convert('L') + elif img.mode == 'RGBA': + img = img.convert('LA') + x, y = image.ProfileData.Profiles[options.profile][1] + if options.iskindle: + x = min(x, 1920) + y = min(y, 1920) + if img.size[0] > x or img.size[1] > y: + img = ImageOps.contain(img, (x, y)) + img.save(os.path.join(root, file), quality=options.jpegquality) + _, ext = os.path.splitext(source) + if ext != '.epub': + ext = '.cbz' + output_file = getOutputFilename(source, options.output, ext, '') + makeZIP(output_file, os.path.join(path, 'OEBPS', 'Images'), job_progress) + rmtree(path, True) + + return [output_file] + getMetadata(os.path.join(path, "OEBPS", "Images"), source) removeNonImages(os.path.join(path, "OEBPS", "Images")) detectSuboptimalProcessing(os.path.join(path, "OEBPS", "Images"), source) @@ -1892,7 +1928,7 @@ def makeBook(source, qtgui=None, job_progress=''): rmtree(tome, True) if GUI: GUI.progressBarTick.emit('tick') - if not GUI and options.format == 'MOBI': + if not GUI and options.format == 'MOBI' and not options.lightnovel: print(f"{job_progress}Creating MOBI files...") work = [] for i in filepath: