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
This commit is contained in:
Alex Xu
2026-06-26 10:23:28 -07:00
committed by GitHub
parent 2a6d61530f
commit 3723bf9d52
5 changed files with 66 additions and 3 deletions
+1
View File
@@ -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) 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] [Default=KV]
-m, --manga-style Manga style (right-to-left reading and splitting) -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 --ebok Force EBOK tag instead of PDOC for MOBI
--invertdirection Invert page turn direction --invertdirection Invert page turn direction
-q, --hq Try to increase the quality of magnification -q, --hq Try to increase the quality of magnification
+12
View File
@@ -990,6 +990,18 @@ Ignored for Kindle EPUB/MOBI and all PDF.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="0">
<widget class="QCheckBox" name="lightnovelBox">
<property name="toolTip">
<string>Only resize images and preserve original file structure.
Ignores most options besides JPEG quality, color mode, output folder.</string>
</property>
<property name="text">
<string>Light novel mode</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
+4 -1
View File
@@ -273,6 +273,8 @@ class WorkerThread(QThread):
options.format = gui_current_format options.format = gui_current_format
if GUI.mangaBox.isChecked(): if GUI.mangaBox.isChecked():
options.righttoleft = True options.righttoleft = True
if GUI.lightnovelBox.isChecked():
options.lightnovel = True
if GUI.ebokBox.isChecked(): if GUI.ebokBox.isChecked():
options.ebok = True options.ebok = True
if GUI.invertDirectionBox.isChecked(): if GUI.invertDirectionBox.isChecked():
@@ -476,7 +478,7 @@ class WorkerThread(QThread):
MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True) MW.addMessage.emit('Creating PDF files... <b>Done!</b>', 'info', True)
else: else:
MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', 'info', True) MW.addMessage.emit('Creating EPUB files... <b>Done!</b>', '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(f'{job_progress_number}Creating MOBI files')
MW.progressBarTick.emit(str(len(outputPath) * 2 + 1)) MW.progressBarTick.emit(str(len(outputPath) * 2 + 1))
MW.progressBarTick.emit('tick') MW.progressBarTick.emit('tick')
@@ -1085,6 +1087,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.settings.setValue('startNumber', self.startNumber + 1) self.settings.setValue('startNumber', self.startNumber + 1)
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height())) self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(), self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
'lightnovelBox': GUI.lightnovelBox.checkState(),
'ebokBox': GUI.ebokBox.checkState(), 'ebokBox': GUI.ebokBox.checkState(),
'invertDirectionBox': GUI.invertDirectionBox.checkState(), 'invertDirectionBox': GUI.invertDirectionBox.checkState(),
'languageEdit': GUI.languageEdit.text(), 'languageEdit': GUI.languageEdit.text(),
+11
View File
@@ -502,6 +502,11 @@ class Ui_mainWindow(object):
self.gridLayout_2.addWidget(self.ebokBox, 10, 3, 1, 1) 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) self.gridLayout.addWidget(self.optionWidget, 5, 0, 1, 2)
@@ -877,6 +882,12 @@ class Ui_mainWindow(object):
self.ebokBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Force Kindle MOBI to be be tagged as EBOK instead of PDOC.</p><p>This may cause USB loaded books to be deleted if you go online after a month offline.</p></body></html>", None)) self.ebokBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p>Force Kindle MOBI to be be tagged as EBOK instead of PDOC.</p><p>This may cause USB loaded books to be deleted if you go online after a month offline.</p></body></html>", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.ebokBox.setText(QCoreApplication.translate("mainWindow", u"Force EBOK", None)) 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) #if QT_CONFIG(tooltip)
self.hLabel.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p style='white-space:pre'>Resolution of the target device.</p></body></html>", None)) self.hLabel.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)
+38 -2
View File
@@ -39,7 +39,7 @@ from multiprocessing import Pool, cpu_count
from uuid import uuid4 from uuid import uuid4
from natsort import os_sort_keygen, os_sorted from natsort import os_sort_keygen, os_sorted
from slugify import slugify as slugify_ext from slugify import slugify as slugify_ext
from PIL import Image, ImageFile from PIL import Image, ImageFile, ImageOps
from pathlib import Path from pathlib import Path
from subprocess import STDOUT, PIPE, CalledProcessError from subprocess import STDOUT, PIPE, CalledProcessError
from psutil import virtual_memory, disk_usage from psutil import virtual_memory, disk_usage
@@ -965,6 +965,8 @@ def getWorkFolder(afile, workdir=None):
try: try:
cbx = comicarchive.ComicArchive(afile) cbx = comicarchive.ComicArchive(afile)
path = cbx.extract(fullPath) path = cbx.extract(fullPath)
if options.lightnovel:
return workdir
sanitizePermissions(path) sanitizePermissions(path)
tdir = os.listdir(fullPath) tdir = os.listdir(fullPath)
@@ -1448,6 +1450,8 @@ def makeParser():
" [Default=KV]") " [Default=KV]")
main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, main_options.add_argument("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (right-to-left reading and splitting)") 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, main_options.add_argument("--ebok", action="store_true", dest="ebok", default=False,
help="Force EBOK tag instead of PDOC for MOBI") help="Force EBOK tag instead of PDOC for MOBI")
main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False, main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False,
@@ -1582,6 +1586,9 @@ def checkOptions(options):
else: else:
options.isKobo = True 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): 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') 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...") print(f"{job_progress}Preparing source images...")
path = getWorkFolder(source) path = getWorkFolder(source)
print(f"{job_progress}Checking images...") 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) 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)
@@ -1892,7 +1928,7 @@ def makeBook(source, qtgui=None, job_progress=''):
rmtree(tome, True) rmtree(tome, True)
if GUI: if GUI:
GUI.progressBarTick.emit('tick') 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...") print(f"{job_progress}Creating MOBI files...")
work = [] work = []
for i in filepath: for i in filepath: