1
0
mirror of https://github.com/ciromattia/kcc synced 2025-12-22 22:22:21 +00:00

Moved MOBI creation to main code (close #111)

This commit is contained in:
Paweł Jastrzębski
2014-09-28 13:01:42 +02:00
parent 108e351126
commit 1a9cd0beb5
3 changed files with 129 additions and 115 deletions

View File

@@ -67,6 +67,8 @@ After completed conversion you should find ready file alongside the original inp
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details. Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
CLI version of KCC is intended for power users. It is not idiot-proof like GUI :-)
### Standalone `kcc-c2e.py` usage: ### Standalone `kcc-c2e.py` usage:
``` ```
@@ -75,7 +77,9 @@ Usage: kcc-c2e [options] comic_file|comic_folder
Options: Options:
MAIN: MAIN:
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (Choose one among K1, K2, K345, KDX, KPW, KV, KFHD, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA, KoAHD, KoAH2O) [Default=KV] Device profile (Available options: K1, K2, K345, KDX,
KPW, KV, KFHD, KFHDX, KFHDX8, KFA, KoMT, KoG, KoA,
KoAHD, KoAH2O) [Default=KV]
-q QUALITY, --quality=QUALITY -q QUALITY, --quality=QUALITY
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0] Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
-m, --manga-style Manga style (Right-to-left reading and splitting) -m, --manga-style Manga style (Right-to-left reading and splitting)
@@ -86,7 +90,8 @@ Options:
Output generated file to specified directory or file Output generated file to specified directory or file
-t TITLE, --title=TITLE -t TITLE, --title=TITLE
Comic title [Default=filename or directory name] Comic title [Default=filename or directory name]
--cbz-output Outputs a CBZ archive and does not generate EPUB -f FORMAT, --format=FORMAT
Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]
--batchsplit Split output into multiple files --batchsplit Split output into multiple files
PROCESSING: PROCESSING:

View File

@@ -36,12 +36,10 @@ from subprocess import STDOUT, PIPE
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt5 import QtGui, QtCore, QtWidgets
from xml.dom.minidom import parse from xml.dom.minidom import parse
from html.parser import HTMLParser from html.parser import HTMLParser
from psutil import virtual_memory, Popen, Process from psutil import Popen, Process
from uuid import uuid4
from copy import copy from copy import copy
from .shared import md5Checksum from .shared import md5Checksum
from . import comic2ebook from . import comic2ebook
from . import dualmetafix
from . import KCC_rc_web from . import KCC_rc_web
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
from . import KCC_ui_osx as KCC_ui from . import KCC_ui_osx as KCC_ui
@@ -265,84 +263,14 @@ class ProgressThread(QtCore.QThread):
self.running = False self.running = False
class WorkerSignals(QtCore.QObject):
result = QtCore.pyqtSignal(list)
class KindleGenThread(QtCore.QRunnable):
def __init__(self, batch):
super(KindleGenThread, self).__init__()
self.signals = WorkerSignals()
self.work = batch
def run(self):
kindlegenErrorCode = 0
kindlegenError = ''
try:
if os.path.getsize(self.work) < 629145600:
output = Popen('kindlegen -dont_append_source -locale en "' + self.work + '"', stdout=PIPE,
stderr=STDOUT, shell=True)
for line in output.stdout:
line = line.decode('utf-8')
# ERROR: Generic error
if "Error(" in line:
kindlegenErrorCode = 1
kindlegenError = line
# ERROR: EPUB too big
if ":E23026:" in line:
kindlegenErrorCode = 23026
if kindlegenErrorCode > 0:
break
else:
# ERROR: EPUB too big
kindlegenErrorCode = 23026
self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
except Exception as err:
# ERROR: KCC unknown generic error
kindlegenErrorCode = 1
kindlegenError = format(err)
self.signals.result.emit([kindlegenErrorCode, kindlegenError, self.work])
class DualMetaFixThread(QtCore.QRunnable):
def __init__(self, batch):
super(DualMetaFixThread, self).__init__()
self.signals = WorkerSignals()
self.work = batch
def run(self):
item = self.work
os.remove(item)
mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean')
try:
# noinspection PyArgumentList
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(str(uuid4()), 'UTF-8'))
self.signals.result.emit([True])
except Exception as err:
self.signals.result.emit([False, format(err)])
class WorkerThread(QtCore.QThread): class WorkerThread(QtCore.QThread):
#noinspection PyArgumentList #noinspection PyArgumentList
def __init__(self): def __init__(self):
QtCore.QThread.__init__(self) QtCore.QThread.__init__(self)
self.pool = QtCore.QThreadPool()
self.conversionAlive = False self.conversionAlive = False
self.errors = False self.errors = False
self.kindlegenErrorCode = [0] self.kindlegenErrorCode = [0]
self.workerOutput = [] self.workerOutput = []
# Let's make sure that we don't fill the memory
availableMemory = virtual_memory().total/1000000000
if availableMemory <= 2:
self.threadNumber = 1
elif 2 < availableMemory <= 4:
self.threadNumber = 2
else:
self.threadNumber = 4
# Let's make sure that we don't use too many threads
if self.threadNumber > QtCore.QThread.idealThreadCount():
self.threadNumber = QtCore.QThread.idealThreadCount()
self.progressBarTick = MW.progressBarTick self.progressBarTick = MW.progressBarTick
self.addMessage = MW.addMessage self.addMessage = MW.addMessage
@@ -361,10 +289,6 @@ class WorkerThread(QtCore.QThread):
MW.addTrayMessage.emit('Conversion interrupted.', 'Critical') MW.addTrayMessage.emit('Conversion interrupted.', 'Critical')
MW.modeConvert.emit(1) MW.modeConvert.emit(1)
def addResult(self, output):
MW.progressBarTick.emit('tick')
self.workerOutput.append(output)
def sanitizeTrace(self, traceback): def sanitizeTrace(self, traceback):
return ''.join(format_tb(traceback))\ return ''.join(format_tb(traceback))\
.replace('C:\\Users\\AcidWeb\\Documents\\Projekty\\KCC\\', '')\ .replace('C:\\Users\\AcidWeb\\Documents\\Projekty\\KCC\\', '')\
@@ -391,8 +315,7 @@ class WorkerThread(QtCore.QThread):
options.quality = 1 options.quality = 1
elif GUI.QualityBox.checkState() == 2: elif GUI.QualityBox.checkState() == 2:
options.quality = 2 options.quality = 2
if str(GUI.FormatBox.currentText()) == 'CBZ': options.format = str(GUI.FormatBox.currentText())
options.cbzoutput = True
if GUI.currentMode == 1: if GUI.currentMode == 1:
if 'KFH' in profile: if 'KFH' in profile:
options.upscale = True options.upscale = True
@@ -416,10 +339,7 @@ class WorkerThread(QtCore.QThread):
if GUI.WebtoonBox.isChecked(): if GUI.WebtoonBox.isChecked():
options.webtoon = True options.webtoon = True
if float(GUI.GammaValue) > 0.09: if float(GUI.GammaValue) > 0.09:
# noinspection PyTypeChecker
options.gamma = float(GUI.GammaValue) options.gamma = float(GUI.GammaValue)
if str(GUI.FormatBox.currentText()) == 'MOBI':
options.batchsplit = True
# Other/custom settings. # Other/custom settings.
if GUI.currentMode > 2: if GUI.currentMode > 2:
@@ -489,16 +409,10 @@ class WorkerThread(QtCore.QThread):
MW.progressBarTick.emit('tick') MW.progressBarTick.emit('tick')
MW.addMessage.emit('Creating MOBI files', 'info', False) MW.addMessage.emit('Creating MOBI files', 'info', False)
GUI.progress.content = 'Creating MOBI files' GUI.progress.content = 'Creating MOBI files'
self.workerOutput = [] work = []
# Number of KindleGen threads depends on the size of RAM
self.pool.setMaxThreadCount(self.threadNumber)
for item in outputPath: for item in outputPath:
worker = KindleGenThread(item) work.append([item])
worker.signals.result.connect(self.addResult) self.workerOutput = comic2ebook.makeMOBI(work, self)
self.pool.start(worker)
self.pool.waitForDone()
while len(self.workerOutput) != len(outputPath):
sleep(0.1)
self.kindlegenErrorCode = [0] self.kindlegenErrorCode = [0]
for errors in self.workerOutput: for errors in self.workerOutput:
if errors[0] != 0: if errors[0] != 0:
@@ -518,15 +432,9 @@ class WorkerThread(QtCore.QThread):
MW.addMessage.emit('Processing MOBI files', 'info', False) MW.addMessage.emit('Processing MOBI files', 'info', False)
GUI.progress.content = 'Processing MOBI files' GUI.progress.content = 'Processing MOBI files'
self.workerOutput = [] self.workerOutput = []
# DualMetaFix is very fast and there is not reason to use multithreading.
self.pool.setMaxThreadCount(1)
for item in outputPath: for item in outputPath:
worker = DualMetaFixThread(item) self.workerOutput.append(comic2ebook.makeMOBIFix(item))
worker.signals.result.connect(self.addResult) MW.progressBarTick.emit('tick')
self.pool.start(worker)
self.pool.waitForDone()
while len(self.workerOutput) != len(outputPath):
sleep(0.1)
for success in self.workerOutput: for success in self.workerOutput:
if not success[0]: if not success[0]:
self.errors = True self.errors = True

View File

@@ -38,6 +38,8 @@ from xml.dom.minidom import parse
from uuid import uuid4 from uuid import uuid4
from slugify import slugify as slugifyExt from slugify import slugify as slugifyExt
from PIL import Image from PIL import Image
from subprocess import STDOUT, PIPE
from psutil import Popen, virtual_memory
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
@@ -47,6 +49,7 @@ from . import comic2panel
from . import image from . import image
from . import cbxarchive from . import cbxarchive
from . import pdfjpgextract from . import pdfjpgextract
from . import dualmetafix
def main(argv=None): def main(argv=None):
@@ -955,8 +958,8 @@ def makeParser():
otherOptions = OptionGroup(psr, "OTHER") otherOptions = OptionGroup(psr, "OTHER")
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV", mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV",
help="Device profile (Choose one among K1, K2, K345, KDX, KPW, KV, KFHD, KFHDX, KFHDX8, KFA," help="Device profile (Available options: K1, K2, K345, KDX, KPW, KV, KFHD, KFHDX, KFHDX8,"
" KoMT, KoG, KoA, KoAHD, KoAH2O) [Default=KV]") " KFA, KoMT, KoG, KoA, KoAHD, KoAH2O) [Default=KV]")
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0", mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]") help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
@@ -968,8 +971,8 @@ def makeParser():
help="Output generated file to specified directory or file") help="Output generated file to specified directory or file")
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [Default=filename or directory name]") help="Comic title [Default=filename or directory name]")
outputOptions.add_option("--cbz-output", action="store_true", dest="cbzoutput", default=False, outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
help="Outputs a CBZ archive and does not generate EPUB") help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]")
outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False, outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
help="Split output into multiple files"), help="Split output into multiple files"),
@@ -1016,10 +1019,20 @@ def checkOptions():
global options global options
options.panelview = True options.panelview = True
options.bordersColor = None options.bordersColor = None
if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K345', 'KPW', 'KV', 'KFHD', 'KFHDX', 'KFHDX8', 'KFA']:
options.format = 'MOBI'
elif options.profile in ['Other']:
options.format = 'EPUB'
elif options.profile in ['KDX', 'KoMT', 'KoG', 'KoA', 'KoAHD', 'KoAH2O']:
options.format = 'CBZ'
if options.white_borders: if options.white_borders:
options.bordersColor = "white" options.bordersColor = 'white'
if options.black_borders: if options.black_borders:
options.bordersColor = "black" options.bordersColor = 'black'
# Splitting MOBI is not optional
if options.format == 'MOBI':
options.batchsplit = True
# Disabling grayscale conversion for Kindle Fire family. # Disabling grayscale conversion for Kindle Fire family.
if 'KFH' in options.profile or options.forcecolor: if 'KFH' in options.profile or options.forcecolor:
options.forcecolor = True options.forcecolor = True
@@ -1048,10 +1061,10 @@ def checkOptions():
print("ERROR: Kindle for Android profile require --customwidth and --customheight options!") print("ERROR: Kindle for Android profile require --customwidth and --customheight options!")
sys.exit(1) sys.exit(1)
# CBZ files on Kindle DX/DXG support higher resolution # CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.cbzoutput: if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200 options.customheight = 1200
# Ultra mode don't work with CBZ format # Ultra mode don't work with CBZ format
if options.quality == 2 and options.cbzoutput: if options.quality == 2 and options.format == 'CBZ':
options.quality = 1 options.quality = 1
# Override profile data # Override profile data
if options.customwidth != 0 or options.customheight != 0: if options.customwidth != 0 or options.customheight != 0:
@@ -1069,7 +1082,7 @@ def checkOptions():
def makeBook(source, qtGUI=None): def makeBook(source, qtGUI=None):
"""Generates EPUB/CBZ comic ebook from a bunch of images.""" """Generates MOBI/EPUB/CBZ comic ebook from a bunch of images."""
global GUI global GUI
GUI = qtGUI GUI = qtGUI
if GUI: if GUI:
@@ -1091,7 +1104,7 @@ def makeBook(source, qtGUI=None):
if GUI: if GUI:
GUI.progressBarTick.emit('1') GUI.progressBarTick.emit('1')
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if 'Ko' in options.profile and options.cbzoutput: if 'Ko' in options.profile and options.format == 'CBZ':
sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images')) sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images'))
if options.batchsplit: if options.batchsplit:
tomes = splitDirectory(path) tomes = splitDirectory(path)
@@ -1100,7 +1113,7 @@ def makeBook(source, qtGUI=None):
filepath = [] filepath = []
tomeNumber = 0 tomeNumber = 0
if GUI: if GUI:
if options.cbzoutput: if options.format == 'CBZ':
GUI.progressBarTick.emit('Compressing CBZ files') GUI.progressBarTick.emit('Compressing CBZ files')
else: else:
GUI.progressBarTick.emit('Compressing EPUB files') GUI.progressBarTick.emit('Compressing EPUB files')
@@ -1111,7 +1124,7 @@ def makeBook(source, qtGUI=None):
if len(tomes) > 1: if len(tomes) > 1:
tomeNumber += 1 tomeNumber += 1
options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']' options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']'
if options.cbzoutput: if options.format == 'CBZ':
# if CBZ output wanted, compress all images and return filepath # if CBZ output wanted, compress all images and return filepath
print("\nCreating CBZ file...") print("\nCreating CBZ file...")
if len(tomes) > 1: if len(tomes) > 1:
@@ -1120,7 +1133,7 @@ def makeBook(source, qtGUI=None):
filepath.append(getOutputFilename(source, options.output, '.cbz', '')) filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images")) makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"))
else: else:
print("\nCreating EPUB structure...") print("\nCreating EPUB file...")
buildEPUB(tome, chapterNames, tomeNumber) buildEPUB(tome, chapterNames, tomeNumber)
# actually zip the ePub # actually zip the ePub
if len(tomes) > 1: if len(tomes) > 1:
@@ -1132,4 +1145,92 @@ def makeBook(source, qtGUI=None):
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':
print("\nCreating MOBI file...")
work = []
for i in filepath:
work.append([i])
output = makeMOBI(work, GUI)
for errors in output:
if errors[0] != 0:
print('KINDLEGEN ERROR!')
print(errors)
return filepath
for i in filepath:
output = makeMOBIFix(i)
if not output[0]:
print('DUALMETAFIX ERROR!')
return filepath
else:
os.remove(i.replace('.epub', '.mobi') + '_toclean')
return filepath return filepath
def makeMOBIFix(item):
os.remove(item)
mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean')
try:
dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(str(uuid4()), 'UTF-8'))
return [True]
except Exception as err:
return [False, format(err)]
def makeMOBIWorkerTick(output):
makeMOBIWorkerOutput.append(output)
if output[0] != 0:
makeMOBIWorkerPool.terminate()
if GUI:
GUI.progressBarTick.emit('tick')
if not GUI.conversionAlive:
makeMOBIWorkerPool.terminate()
def makeMOBIWorker(item):
item = item[0]
kindlegenErrorCode = 0
kindlegenError = ''
try:
if os.path.getsize(item) < 629145600:
output = Popen('kindlegen -dont_append_source -locale en "' + item + '"',
stdout=PIPE, stderr=STDOUT, shell=True)
for line in output.stdout:
line = line.decode('utf-8')
# ERROR: Generic error
if "Error(" in line:
kindlegenErrorCode = 1
kindlegenError = line
# ERROR: EPUB too big
if ":E23026:" in line:
kindlegenErrorCode = 23026
if kindlegenErrorCode > 0:
break
else:
# ERROR: EPUB too big
kindlegenErrorCode = 23026
return [kindlegenErrorCode, kindlegenError, item]
except Exception as err:
# ERROR: KCC unknown generic error
kindlegenErrorCode = 1
kindlegenError = format(err)
return [kindlegenErrorCode, kindlegenError, item]
def makeMOBI(work, qtGUI=None):
global GUI, makeMOBIWorkerPool, makeMOBIWorkerOutput
GUI = qtGUI
makeMOBIWorkerOutput = []
availableMemory = virtual_memory().total/1000000000
if availableMemory <= 2:
threadNumber = 1
elif 2 < availableMemory <= 4:
threadNumber = 2
else:
threadNumber = 4
makeMOBIWorkerPool = Pool(threadNumber)
for i in work:
makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick)
makeMOBIWorkerPool.close()
makeMOBIWorkerPool.join()
return makeMOBIWorkerOutput