mirror of
https://github.com/ciromattia/kcc
synced 2025-12-28 00:52:27 +00:00
401 lines
17 KiB
Python
401 lines
17 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (c) 2013 Ciro Mattia Gonano <ciromattia@gmail.com>
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for
|
|
# any purpose with or without fee is hereby granted, provided that the
|
|
# above copyright notice and this permission notice appear in all
|
|
# copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
|
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
# PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
__version__ = '3.0'
|
|
__license__ = 'ISC'
|
|
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
|
__docformat__ = 'restructuredtext en'
|
|
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import traceback
|
|
import comic2ebook
|
|
import kindlestrip
|
|
from image import ProfileData
|
|
from subprocess import call, STDOUT, PIPE
|
|
from PyQt4 import QtGui, QtCore
|
|
|
|
|
|
class Icons:
|
|
def __init__(self):
|
|
self.deviceKindle = QtGui.QIcon()
|
|
self.deviceKindle.addPixmap(QtGui.QPixmap(":/Devices/icons/Kindle.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
|
|
self.MOBIFormat = QtGui.QIcon()
|
|
self.MOBIFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/MOBI.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
self.CBZFormat = QtGui.QIcon()
|
|
self.CBZFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/CBZ.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
self.EPUBFormat = QtGui.QIcon()
|
|
self.EPUBFormat.addPixmap(QtGui.QPixmap(":/Formats/icons/EPUB.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
|
|
self.info = QtGui.QIcon()
|
|
self.info.addPixmap(QtGui.QPixmap(":/Status/icons/info.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
self.warning = QtGui.QIcon()
|
|
self.warning.addPixmap(QtGui.QPixmap(":/Status/icons/warning.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
self.error = QtGui.QIcon()
|
|
self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
|
|
|
|
# noinspection PyBroadException
|
|
class WorkerThread(QtCore.QThread):
|
|
def __init__(self, parent):
|
|
QtCore.QThread.__init__(self)
|
|
self.parent = parent
|
|
|
|
def __del__(self):
|
|
self.wait()
|
|
|
|
def run(self):
|
|
self.emit(QtCore.SIGNAL("modeConvert"), False)
|
|
profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())]
|
|
argv = ["--profile=" + profile]
|
|
currentJobs = []
|
|
global errors
|
|
if GUI.MangaBox.isChecked():
|
|
argv.append("--manga-style")
|
|
if GUI.RotateBox.isChecked():
|
|
argv.append("--rotate")
|
|
if not GUI.HQPVBox.isChecked():
|
|
argv.append("--nopanelviewhq")
|
|
if GUI.ProcessingBox.isChecked():
|
|
argv.append("--noprocessing")
|
|
if GUI.UpscaleBox.isChecked() and not GUI.StretchBox.isChecked():
|
|
argv.append("--upscale")
|
|
if GUI.NoRotateBox.isChecked():
|
|
argv.append("--nosplitrotate")
|
|
if GUI.BorderBox.isChecked():
|
|
argv.append("--blackborders")
|
|
if GUI.StretchBox.isChecked():
|
|
argv.append("--stretch")
|
|
if GUI.NoDitheringBox.isChecked():
|
|
argv.append("--nodithering")
|
|
if self.parent.GammaValue > 0.09:
|
|
argv.append("--gamma=" + self.parent.GammaValue)
|
|
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
|
argv.append("--cbz-output")
|
|
if str(GUI.customWidth.text()) != '':
|
|
argv.append("--customwidth=" + str(GUI.customWidth.text()))
|
|
if str(GUI.customHeight.text()) != '':
|
|
argv.append("--customheight=" + str(GUI.customHeight.text()))
|
|
for i in range(GUI.JobList.count()):
|
|
currentJobs.append(str(GUI.JobList.item(i).text()))
|
|
GUI.JobList.clear()
|
|
for job in currentJobs:
|
|
errors = False
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Source: ' + job, 'info')
|
|
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file...', 'info')
|
|
else:
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file...', 'info')
|
|
jobargv = list(argv)
|
|
jobargv.append(job)
|
|
try:
|
|
outputPath = comic2ebook.main(jobargv, self)
|
|
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
|
except Exception as err:
|
|
errors = True
|
|
type_, value_, traceback_ = sys.exc_info()
|
|
QtGui.QMessageBox.critical(MainWindow, 'KCC Error',
|
|
"Error on file %s:\n%s\nTraceback:\n%s"
|
|
% (jobargv[-1], str(err), traceback.format_tb(traceback_)),
|
|
QtGui.QMessageBox.Ok)
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create EPUB!', 'error')
|
|
continue
|
|
if not errors:
|
|
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file... Done!', 'info', True)
|
|
else:
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True)
|
|
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
|
if not os.path.getsize(outputPath) > 314572800:
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
|
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
|
try:
|
|
retcode = call('kindlegen "' + outputPath + '"', shell=True)
|
|
except:
|
|
continue
|
|
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
|
if retcode == 0:
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True)
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header...', 'info')
|
|
os.remove(outputPath)
|
|
mobiPath = outputPath.replace('.epub', '.mobi')
|
|
shutil.move(mobiPath, mobiPath + '_tostrip')
|
|
try:
|
|
kindlestrip.main((mobiPath + '_tostrip', mobiPath))
|
|
except Exception:
|
|
errors = True
|
|
continue
|
|
if not errors:
|
|
os.remove(mobiPath + '_tostrip')
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header... Done!',
|
|
'info', True)
|
|
else:
|
|
shutil.move(mobiPath + '_tostrip', mobiPath)
|
|
self.emit(QtCore.SIGNAL("addMessage"),
|
|
'KindleStrip failed to remove SRCS header!', 'warning')
|
|
self.emit(QtCore.SIGNAL("addMessage"),
|
|
'MOBI file will work correctly but it will be highly oversized.',
|
|
'warning')
|
|
else:
|
|
os.remove(outputPath)
|
|
os.remove(outputPath.replace('.epub', '.mobi'))
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Try converting smaller batch.', 'error')
|
|
else:
|
|
os.remove(outputPath)
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file is too big for KindleGen!',
|
|
'error')
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'Try converting smaller batch.',
|
|
'error')
|
|
self.parent.needClean = True
|
|
self.emit(QtCore.SIGNAL("addMessage"), 'All jobs completed.', 'warning')
|
|
self.emit(QtCore.SIGNAL("modeConvert"), True)
|
|
|
|
|
|
# noinspection PyBroadException
|
|
class Ui_KCC(object):
|
|
def selectDir(self):
|
|
# Dialog allow to select multiple directories but we can't parse that. QT Bug.
|
|
if self.needClean:
|
|
self.needClean = False
|
|
GUI.JobList.clear()
|
|
dname = QtGui.QFileDialog.getExistingDirectory(MainWindow, 'Select directory', self.lastPath)
|
|
# Lame UTF-8 security measure
|
|
try:
|
|
str(dname)
|
|
except Exception:
|
|
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
|
QtGui.QMessageBox.Ok)
|
|
return
|
|
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
|
|
GUI.JobList.addItem(dname)
|
|
self.clearEmptyJobs()
|
|
|
|
def selectFile(self):
|
|
if self.needClean:
|
|
self.needClean = False
|
|
GUI.JobList.clear()
|
|
if self.UnRAR:
|
|
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
|
|
'*.cbz *.cbr *.zip *.rar *.pdf')
|
|
else:
|
|
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
|
|
'*.cbz *.zip *.pdf')
|
|
# Lame UTF-8 security measure
|
|
try:
|
|
str(fname)
|
|
except Exception:
|
|
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
|
QtGui.QMessageBox.Ok)
|
|
return
|
|
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
|
|
GUI.JobList.addItem(fname)
|
|
self.clearEmptyJobs()
|
|
|
|
def clearJobs(self):
|
|
GUI.JobList.clear()
|
|
|
|
def clearEmptyJobs(self):
|
|
# Sometimes empty records appear. Dirty workaround.
|
|
for i in range(GUI.JobList.count()):
|
|
if str(GUI.JobList.item(i).text()) == '':
|
|
GUI.JobList.takeItem(i)
|
|
GUI.JobList.scrollToBottom()
|
|
|
|
def modeBasic(self):
|
|
self.currentMode = 1
|
|
MainWindow.setMinimumSize(QtCore.QSize(420, 270))
|
|
MainWindow.setMaximumSize(QtCore.QSize(420, 270))
|
|
MainWindow.resize(420, 270)
|
|
GUI.BasicModeButton.setStyleSheet('font-weight:Bold;')
|
|
GUI.AdvModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.ExpertModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.FormatBox.setCurrentIndex(0)
|
|
GUI.FormatBox.setEnabled(False)
|
|
GUI.OptionsAdvanced.setEnabled(False)
|
|
GUI.OptionsAdvancedGamma.setEnabled(False)
|
|
GUI.OptionsExpert.setEnabled(False)
|
|
GUI.ProcessingBox.hide()
|
|
GUI.UpscaleBox.hide()
|
|
GUI.NoRotateBox.hide()
|
|
GUI.ProcessingBox.setChecked(False)
|
|
GUI.UpscaleBox.setChecked(False)
|
|
GUI.NoRotateBox.setChecked(False)
|
|
GUI.BorderBox.setChecked(False)
|
|
GUI.StretchBox.setChecked(False)
|
|
GUI.NoDitheringBox.setChecked(False)
|
|
GUI.GammaSlider.setValue(0)
|
|
GUI.customWidth.setText('')
|
|
GUI.customHeight.setText('')
|
|
|
|
def modeAdvanced(self):
|
|
self.currentMode = 2
|
|
MainWindow.setMinimumSize(QtCore.QSize(420, 345))
|
|
MainWindow.setMaximumSize(QtCore.QSize(420, 345))
|
|
MainWindow.resize(420, 345)
|
|
GUI.BasicModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.AdvModeButton.setStyleSheet('font-weight:Bold;')
|
|
GUI.ExpertModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.FormatBox.setEnabled(True)
|
|
GUI.ProcessingBox.show()
|
|
GUI.UpscaleBox.show()
|
|
GUI.NoRotateBox.show()
|
|
GUI.OptionsAdvancedGamma.setEnabled(True)
|
|
GUI.OptionsAdvanced.setEnabled(True)
|
|
GUI.OptionsExpert.setEnabled(False)
|
|
GUI.customWidth.setText('')
|
|
GUI.customHeight.setText('')
|
|
|
|
def modeExpert(self):
|
|
self.modeAdvanced()
|
|
self.currentMode = 3
|
|
MainWindow.setMinimumSize(QtCore.QSize(420, 380))
|
|
MainWindow.setMaximumSize(QtCore.QSize(420, 380))
|
|
MainWindow.resize(420, 380)
|
|
GUI.BasicModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.AdvModeButton.setStyleSheet('font-weight:Normal;')
|
|
GUI.ExpertModeButton.setStyleSheet('font-weight:Bold;')
|
|
GUI.OptionsExpert.setEnabled(True)
|
|
|
|
def modeConvert(self, enable):
|
|
GUI.BasicModeButton.setEnabled(enable)
|
|
GUI.AdvModeButton.setEnabled(enable)
|
|
GUI.ExpertModeButton.setEnabled(enable)
|
|
GUI.DirectoryButton.setEnabled(enable)
|
|
GUI.ClearButton.setEnabled(enable)
|
|
GUI.FileButton.setEnabled(enable)
|
|
GUI.DeviceBox.setEnabled(enable)
|
|
GUI.ConvertButton.setEnabled(enable)
|
|
GUI.FormatBox.setEnabled(enable)
|
|
GUI.OptionsBasic.setEnabled(enable)
|
|
GUI.OptionsAdvanced.setEnabled(enable)
|
|
GUI.OptionsAdvancedGamma.setEnabled(enable)
|
|
GUI.OptionsExpert.setEnabled(enable)
|
|
if enable:
|
|
if self.currentMode == 1:
|
|
self.modeBasic()
|
|
elif self.currentMode == 2:
|
|
self.modeAdvanced()
|
|
elif self.currentMode == 3:
|
|
self.modeExpert()
|
|
|
|
def changeGamma(self, value):
|
|
if value <= 9:
|
|
value = 'Auto'
|
|
else:
|
|
value = float(value)
|
|
value = '%.2f' % (value/100)
|
|
self.GammaValue = value
|
|
GUI.GammaLabel.setText('Gamma: ' + str(value))
|
|
|
|
def addMessage(self, message, icon=None, replace=False):
|
|
if icon:
|
|
icon = eval('icons.' + icon)
|
|
item = QtGui.QListWidgetItem(icon, message)
|
|
else:
|
|
item = QtGui.QListWidgetItem(message)
|
|
if replace:
|
|
GUI.JobList.takeItem(GUI.JobList.count()-1)
|
|
GUI.JobList.addItem(item)
|
|
GUI.JobList.scrollToBottom()
|
|
|
|
def updateProgressbar(self, new=False, status=False):
|
|
if new == "status":
|
|
pass
|
|
GUI.ProgressBar.setFormat(status)
|
|
elif new:
|
|
GUI.ProgressBar.setMaximum(new - 1)
|
|
GUI.ProgressBar.reset()
|
|
GUI.ProgressBar.show()
|
|
else:
|
|
GUI.ProgressBar.setValue(GUI.ProgressBar.value() + 1)
|
|
|
|
def convertStart(self):
|
|
if self.needClean:
|
|
self.needClean = False
|
|
GUI.JobList.clear()
|
|
if GUI.JobList.count() == 0:
|
|
self.addMessage('No files selected! Please choose files to convert.', 'error')
|
|
self.needClean = True
|
|
return
|
|
self.thread.start()
|
|
|
|
def hideProgressBar(self):
|
|
GUI.ProgressBar.hide()
|
|
|
|
# noinspection PyUnusedLocal
|
|
def saveSettings(self, event):
|
|
self.settings.setValue('lastPath', self.lastPath)
|
|
self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex())
|
|
self.settings.sync()
|
|
|
|
def __init__(self, ui, KCC):
|
|
global GUI, MainWindow, icons
|
|
GUI = ui
|
|
MainWindow = KCC
|
|
profiles = sorted(ProfileData.ProfileLabels.iterkeys())
|
|
icons = Icons()
|
|
self.settings = QtCore.QSettings('KindleComicConverter', 'KindleComicConverter')
|
|
self.lastPath = self.settings.value('lastPath', '', type=str)
|
|
self.lastDevice = self.settings.value('lastDevice', 10, type=int)
|
|
self.thread = WorkerThread(self)
|
|
self.needClean = True
|
|
self.GammaValue = 0
|
|
|
|
self.addMessage('Welcome!', 'info')
|
|
self.addMessage('Remember: All options have additional informations in tooltips.', 'info')
|
|
if call('kindlegen', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
|
|
self.KindleGen = True
|
|
formats = ['MOBI', 'EPUB', 'CBZ']
|
|
else:
|
|
self.KindleGen = False
|
|
formats = ['EPUB', 'CBZ']
|
|
self.addMessage('Not found KindleGen! Creating MOBI files is disabled.', 'warning')
|
|
if call('unrar', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
|
|
self.UnRAR = True
|
|
else:
|
|
self.UnRAR = False
|
|
self.addMessage('Not found UnRAR! Processing of CBR/RAR files is disabled.', 'warning')
|
|
|
|
GUI.BasicModeButton.clicked.connect(self.modeBasic)
|
|
GUI.AdvModeButton.clicked.connect(self.modeAdvanced)
|
|
GUI.ExpertModeButton.clicked.connect(self.modeExpert)
|
|
GUI.DirectoryButton.clicked.connect(self.selectDir)
|
|
GUI.ClearButton.clicked.connect(self.clearJobs)
|
|
GUI.FileButton.clicked.connect(self.selectFile)
|
|
GUI.ConvertButton.clicked.connect(self.convertStart)
|
|
GUI.GammaSlider.valueChanged.connect(self.changeGamma)
|
|
KCC.connect(self.thread, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar)
|
|
KCC.connect(self.thread, QtCore.SIGNAL("modeConvert"), self.modeConvert)
|
|
KCC.connect(self.thread, QtCore.SIGNAL("addMessage"), self.addMessage)
|
|
KCC.connect(self.thread, QtCore.SIGNAL("hideProgressBar"), self.hideProgressBar)
|
|
KCC.closeEvent = self.saveSettings
|
|
|
|
for profile in profiles:
|
|
GUI.DeviceBox.addItem(icons.deviceKindle, profile)
|
|
GUI.DeviceBox.setCurrentIndex(self.lastDevice)
|
|
for f in formats:
|
|
GUI.FormatBox.addItem(eval('icons.' + f + 'Format'), f)
|
|
GUI.FormatBox.setCurrentIndex(0)
|
|
|
|
self.modeBasic()
|
|
GUI.ProgressBar.hide()
|