1
0
mirror of https://github.com/ciromattia/kcc synced 2026-07-11 15:33:17 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Xu 5e68ce380c double webtoon max height 2026-05-31 18:42:31 -07:00
23 changed files with 1399 additions and 1640 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v7 uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v6
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v4
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
build: build:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
env: env:
MACOSX_DEPLOYMENT_TARGET: '14.0' MACOSX_DEPLOYMENT_TARGET: '14.0'
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
PYTHON_VERSION: 3.11.9 PYTHON_VERSION: 3.11.9
MACOSX_DEPLOYMENT_TARGET: '10.14' MACOSX_DEPLOYMENT_TARGET: '10.14'
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Get Python - name: Get Python
run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg" run: curl https://www.python.org/ftp/python/3.11.9/python-3.11.9-macos11.pkg -o "python.pkg"
- name: Install Python - name: Install Python
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
command: build_c2p command: build_c2p
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
env: env:
WINDOWS_7: 1 WINDOWS_7: 1
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v6
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v6 uses: actions/setup-python@v6
with: with:
-8
View File
@@ -123,8 +123,6 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
Going back a few pages and exiting and re-entering book should fix it temporarily. Going back a few pages and exiting and re-entering book should fix it temporarily.
- What output format should I use? - What output format should I use?
- MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable or Kindle Scribe 2025. - MOBI for Kindles. CBZ for Kindle DX. CBZ for Koreader. KEPUB for Kobo. PDF for ReMarkable or Kindle Scribe 2025.
- Where is KEPUB option?
- Choosing a Kobo profile and EPUB output will output KEPUB.
- All options have additional information in tooltips if you hover over the option. - All options have additional information in tooltips if you hover over the option.
- To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB - To get the converted book onto your Kindle/Kobo, just drag and drop the mobi/kepub into the documents folder on your Kindle/Kobo via USB
- Kindle panel view not working? - Kindle panel view not working?
@@ -241,12 +239,8 @@ 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
--invertdirection Invert page turn direction
-q, --hq Try to increase the quality of magnification -q, --hq Try to increase the quality of magnification
-2, --two-panel Display two not four panels in Panel View mode -2, --two-panel Display two not four panels in Panel View mode
--vertical4panel Show side panels first in virtual panel view
-w, --webtoon Webtoon processing mode -w, --webtoon Webtoon processing mode
--ts TARGETSIZE, --targetsize TARGETSIZE --ts TARGETSIZE, --targetsize TARGETSIZE
the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others] the maximal size of output file in MB. [Default=100MB for webtoon and 400MB for others]
@@ -295,10 +289,8 @@ OUTPUT SETTINGS:
-t TITLE, --title TITLE -t TITLE, --title TITLE
Comic title [Default=filename or directory name] Comic title [Default=filename or directory name]
--metadatatitle Write title using ComicInfo.xml or other embedded metadata. 0: Don't use Title from metadata 1: Combine Title with default schema 2: Use Title only [Default=0] --metadatatitle Write title using ComicInfo.xml or other embedded metadata. 0: Don't use Title from metadata 1: Combine Title with default schema 2: Use Title only [Default=0]
--keepcomicinfo Keep any original ComicInfo.xml files [Default=0]
-a AUTHOR, --author AUTHOR -a AUTHOR, --author AUTHOR
Author name [Default=KCC] Author name [Default=KCC]
--language EPUB language [Default=en-US]
-f FORMAT, --format FORMAT -f FORMAT, --format FORMAT
Output format (Available options: Auto, MOBI, EPUB, CBZ, PDF, KFX, MOBI+EPUB) [Default=Auto] Output format (Available options: Auto, MOBI, EPUB, CBZ, PDF, KFX, MOBI+EPUB) [Default=Auto]
--nokepub If format is EPUB, output file with '.epub' extension rather than '.kepub.epub' --nokepub If format is EPUB, output file with '.epub' extension rather than '.kepub.epub'
+782 -803
View File
File diff suppressed because it is too large Load Diff
-61
View File
@@ -1,61 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
# macOS GUI build, used by setup.py build_binary
import sys
sys.path.insert(0, SPECPATH)
from kindlecomicconverter import __version__
a = Analysis(
['kcc.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=['_cffi_backend'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='Kindle Comic Converter',
debug=False,
bootloader_ignore_signals=False,
strip=True,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['icons/comic2ebook.icns'],
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=True,
upx=True,
upx_exclude=[],
name='Kindle Comic Converter',
)
app = BUNDLE(
coll,
name='Kindle Comic Converter.app',
icon='icons/comic2ebook.icns',
bundle_identifier=None,
info_plist={
'CFBundleShortVersionString': __version__,
'CFBundleVersion': __version__,
},
)
+17 -36
View File
@@ -273,12 +273,6 @@ 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():
options.ebok = True
if GUI.invertDirectionBox.isChecked():
options.invertdirection = True
if GUI.rotateBox.checkState() == Qt.CheckState.PartiallyChecked: if GUI.rotateBox.checkState() == Qt.CheckState.PartiallyChecked:
options.splitter = 2 options.splitter = 2
elif GUI.rotateBox.checkState() == Qt.CheckState.Checked: elif GUI.rotateBox.checkState() == Qt.CheckState.Checked:
@@ -287,8 +281,6 @@ class WorkerThread(QThread):
options.autoscale = True options.autoscale = True
elif GUI.qualityBox.checkState() == Qt.CheckState.Checked: elif GUI.qualityBox.checkState() == Qt.CheckState.Checked:
options.hq = True options.hq = True
if GUI.vertical4PanelBox.isChecked():
options.vertical4panel = True
if GUI.webtoonBox.isChecked(): if GUI.webtoonBox.isChecked():
options.webtoon = True options.webtoon = True
if GUI.upscaleBox.checkState() == Qt.CheckState.PartiallyChecked: if GUI.upscaleBox.checkState() == Qt.CheckState.PartiallyChecked:
@@ -346,8 +338,6 @@ class WorkerThread(QThread):
options.metadatatitle = 1 options.metadatatitle = 1
elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked: elif GUI.metadataTitleBox.checkState() == Qt.CheckState.Checked:
options.metadatatitle = 2 options.metadatatitle = 2
if GUI.keepComicInfoBox.isChecked():
options.keepcomicinfo = True
if GUI.deleteBox.isChecked(): if GUI.deleteBox.isChecked():
options.delete = True options.delete = True
if GUI.tempDirBox.isChecked(): if GUI.tempDirBox.isChecked():
@@ -389,8 +379,6 @@ class WorkerThread(QThread):
options.title = str(GUI.titleEdit.text()) options.title = str(GUI.titleEdit.text())
if GUI.authorEdit.text(): if GUI.authorEdit.text():
options.author = str(GUI.authorEdit.text()) options.author = str(GUI.authorEdit.text())
if GUI.languageEdit.text():
options.language = str(GUI.languageEdit.text())
if GUI.chunkSizeCheckBox.isChecked(): if GUI.chunkSizeCheckBox.isChecked():
options.targetsize = int(GUI.chunkSizeBox.value()) options.targetsize = int(GUI.chunkSizeBox.value())
@@ -480,7 +468,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 and not options.lightnovel: if 'MOBI' in gui_current_format:
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')
@@ -521,6 +509,7 @@ class WorkerThread(QThread):
for item in outputPath: for item in outputPath:
GUI.progress.content = '' GUI.progress.content = ''
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
os.remove(mobiPath + '_toclean')
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath): if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath):
try: try:
move(mobiPath, GUI.targetDirectory) move(mobiPath, GUI.targetDirectory)
@@ -541,6 +530,8 @@ class WorkerThread(QThread):
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
if os.path.exists(mobiPath): if os.path.exists(mobiPath):
os.remove(mobiPath) os.remove(mobiPath)
if os.path.exists(mobiPath + '_toclean'):
os.remove(mobiPath + '_toclean')
MW.addMessage.emit('Failed to process MOBI file!', 'error', False) MW.addMessage.emit('Failed to process MOBI file!', 'error', False)
MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical') MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
else: else:
@@ -560,6 +551,8 @@ class WorkerThread(QThread):
MW.addMessage.emit('Created EPUB file was too big. Weird file structure?', 'error', False) MW.addMessage.emit('Created EPUB file was too big. Weird file structure?', 'error', False)
MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error', MW.addMessage.emit('EPUB file: ' + str(epubSize) + 'MB. Supported size: ~350MB.', 'error',
False) False)
if self.kindlegenErrorCode[0] == 3221226505:
MW.addMessage.emit('Unknown Windows error. Possibly filepath too long?', 'error', False)
else: else:
for item in outputPath: for item in outputPath:
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item): if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(item):
@@ -750,6 +743,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
else: else:
status = True status = True
GUI.editorButton.setEnabled(status) GUI.editorButton.setEnabled(status)
GUI.wikiButton.setEnabled(status)
GUI.deviceBox.setEnabled(status) GUI.deviceBox.setEnabled(status)
GUI.defaultOutputFolderButton.setEnabled(status) GUI.defaultOutputFolderButton.setEnabled(status)
GUI.clearButton.setEnabled(status) GUI.clearButton.setEnabled(status)
@@ -838,6 +832,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.interPanelCropBox.setEnabled(True) GUI.interPanelCropBox.setEnabled(True)
GUI.autoLevelBox.setEnabled(True) GUI.autoLevelBox.setEnabled(True)
GUI.autocontrastBox.setEnabled(True) GUI.autocontrastBox.setEnabled(True)
GUI.autocontrastBox.setChecked(True)
def togglequalityBox(self, value): def togglequalityBox(self, value):
@@ -1085,13 +1080,8 @@ 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(),
'invertDirectionBox': GUI.invertDirectionBox.checkState(),
'languageEdit': GUI.languageEdit.text(),
'rotateBox': GUI.rotateBox.checkState(), 'rotateBox': GUI.rotateBox.checkState(),
'qualityBox': GUI.qualityBox.checkState(), 'qualityBox': GUI.qualityBox.checkState(),
'vertical4PanelBox': GUI.vertical4PanelBox.checkState(),
'gammaBox': GUI.gammaBox.checkState(), 'gammaBox': GUI.gammaBox.checkState(),
'autoLevelBox': GUI.autoLevelBox.checkState(), 'autoLevelBox': GUI.autoLevelBox.checkState(),
'autocontrastBox': GUI.autocontrastBox.checkState(), 'autocontrastBox': GUI.autocontrastBox.checkState(),
@@ -1111,7 +1101,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'smartCoverCropBox': GUI.smartCoverCropBox.checkState(), 'smartCoverCropBox': GUI.smartCoverCropBox.checkState(),
'coverFillBox': GUI.coverFillBox.checkState(), 'coverFillBox': GUI.coverFillBox.checkState(),
'metadataTitleBox': GUI.metadataTitleBox.checkState(), 'metadataTitleBox': GUI.metadataTitleBox.checkState(),
'keepComicInfoBox': GUI.keepComicInfoBox.checkState(),
'mozJpegBox': GUI.mozJpegBox.checkState(), 'mozJpegBox': GUI.mozJpegBox.checkState(),
'forcePngRgbBox': GUI.forcePngRgbBox.checkState(), 'forcePngRgbBox': GUI.forcePngRgbBox.checkState(),
'webpBox': GUI.webpBox.checkState(), 'webpBox': GUI.webpBox.checkState(),
@@ -1200,11 +1189,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.kindleGen = False self.kindleGen = False
if startup: if startup:
self.display_kindlegen_missing() self.display_kindlegen_missing()
except OSError as e:
self.kindleGen = False
if startup:
error = f"kindlegen: {e.strerror}\n\n Re-install Rosetta/Kindle Previewer/other Intel app?\n\nPlease email Amazon to make Kindle Previewer Apple silicon native at amazon.com/kindle-help"
self.showDialog(error, 'error')
def __init__(self, kccapp, kccwindow): def __init__(self, kccapp, kccwindow):
global APP, MW, GUI global APP, MW, GUI
@@ -1214,20 +1198,17 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.setupUi(MW) self.setupUi(MW)
self.editor = KCCGUI_MetaEditor() self.editor = KCCGUI_MetaEditor()
self.icons = Icons() self.icons = Icons()
self.settings = QSettings('ciromattia', 'kcc10') self.settings = QSettings('ciromattia', 'kcc9')
self.settingsVersion = self.settings.value('settingsVersion', '', type=str) self.settingsVersion = self.settings.value('settingsVersion', '', type=str)
self.lastPath = self.settings.value('lastPath', '', type=str) self.lastPath = self.settings.value('lastPath', '', type=str)
self.defaultOutputFolder = str(self.settings.value('defaultOutputFolder', '', type=str)) self.defaultOutputFolder = str(self.settings.value('defaultOutputFolder', '', type=str))
if not os.path.exists(self.defaultOutputFolder): if not os.path.exists(self.defaultOutputFolder):
self.defaultOutputFolder = '' self.defaultOutputFolder = ''
self.lastDevice = self.settings.value('lastDevice', 0, type=int)
# default is Kindle Paperwhite 12th Gen
self.lastDevice = self.settings.value('lastDevice', 3, type=int)
self.currentFormat = self.settings.value('currentFormat', 0, type=int) self.currentFormat = self.settings.value('currentFormat', 0, type=int)
self.startNumber = self.settings.value('startNumber', 0, type=int) self.startNumber = self.settings.value('startNumber', 0, type=int)
self.windowSize = self.settings.value('windowSize', '0x0', type=str) self.windowSize = self.settings.value('windowSize', '0x0', type=str)
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2} default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100}
try: try:
self.options = self.settings.value('options', default_options) self.options = self.settings.value('options', default_options)
except Exception: except Exception:
@@ -1254,7 +1235,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if self.windowSize == '0x0': if self.windowSize == '0x0':
MW.resize(500, 500) MW.resize(500, 500)
elif sys.platform.startswith('darwin'): elif sys.platform.startswith('darwin'):
for element in ['editorButton', 'defaultOutputFolderButton', 'clearButton', 'fileButton', 'deviceBox', for element in ['editorButton', 'wikiButton', 'defaultOutputFolderButton', 'clearButton', 'fileButton', 'deviceBox',
'convertButton', 'formatBox']: 'convertButton', 'formatBox']:
getattr(GUI, element).setMinimumSize(QSize(0, 0)) getattr(GUI, element).setMinimumSize(QSize(0, 0))
GUI.gridLayout.setContentsMargins(-1, -1, -1, -1) GUI.gridLayout.setContentsMargins(-1, -1, -1, -1)
@@ -1435,9 +1416,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
link_dict = { link_dict = {
'README': "https://github.com/ciromattia/kcc?tab=readme-ov-file#kcc", 'README': "https://github.com/ciromattia/kcc?tab=readme-ov-file#kcc",
'FAQ': "https://github.com/ciromattia/kcc/blob/master/README.md#faq", 'FAQ': "https://github.com/ciromattia/kcc/blob/master/README.md#faq",
'WIKI': "https://github.com/ciromattia/kcc/wiki", 'YOUTUBE': "https://youtu.be/IR2Fhcm9658?si=Z-2zzLaUFjmaEbrj",
'TUTORIAL': "https://youtu.be/QQ6zJcMF2Iw?si=80rfm6DU6OUJdFqa", 'COMMISSIONS': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions",
'EMAIL': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions",
'DONATE': "https://github.com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations", 'DONATE': "https://github.com/ciromattia/kcc/blob/master/README.md#issues--new-features--donations",
'FORUM': "http://www.mobileread.com/forums/showthread.php?t=207461", 'FORUM': "http://www.mobileread.com/forums/showthread.php?t=207461",
'DISCORD': "https://discord.com/invite/qj7wpnUHav", 'DISCORD': "https://discord.com/invite/qj7wpnUHav",
@@ -1469,8 +1449,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.fileButton.clicked.connect(self.selectFile) GUI.fileButton.clicked.connect(self.selectFile)
GUI.directoryButton.clicked.connect(self.selectDir) GUI.directoryButton.clicked.connect(self.selectDir)
GUI.editorButton.clicked.connect(self.selectFileMetaEditor) GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
GUI.wikiButton.clicked.connect(self.openWiki)
GUI.kofiButton.clicked.connect(self.openKofi) GUI.kofiButton.clicked.connect(self.openKofi)
GUI.humbleButton.clicked.connect(self.openHumble) GUI.humbleButton.clicked.connect(self.openHumble)
GUI.youtubeButton.clicked.connect(self.openYouTube)
GUI.discordButton.clicked.connect(self.openDiscord)
GUI.convertButton.clicked.connect(self.convertStart) GUI.convertButton.clicked.connect(self.convertStart)
GUI.gammaSlider.valueChanged.connect(self.changeGamma) GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox) GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
@@ -1529,8 +1512,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.widthBox.setValue(int(self.options[option])) GUI.widthBox.setValue(int(self.options[option]))
elif str(option) == "heightBox": elif str(option) == "heightBox":
GUI.heightBox.setValue(int(self.options[option])) GUI.heightBox.setValue(int(self.options[option]))
elif str(option) == "languageEdit":
GUI.languageEdit.setText(str(self.options[option]))
elif str(option) == "gammaSlider": elif str(option) == "gammaSlider":
if GUI.gammaSlider.isEnabled(): if GUI.gammaSlider.isEnabled():
GUI.gammaSlider.setValue(int(self.options[option])) GUI.gammaSlider.setValue(int(self.options[option]))
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
__version__ = '10.4.0' __version__ = '10.2.0'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi' __copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
+34 -147
View File
@@ -19,7 +19,6 @@
# #
from collections import Counter from collections import Counter
from datetime import datetime
import os import os
import pathlib import pathlib
import re import re
@@ -35,12 +34,12 @@ from stat import S_IWRITE, S_IREAD, S_IEXEC
from typing import List from typing import List
from zipfile import ZipFile, ZIP_STORED from zipfile import ZipFile, ZIP_STORED
from tempfile import mkdtemp, gettempdir from tempfile import mkdtemp, gettempdir
from shutil import move, copytree, rmtree from shutil import move, copytree, rmtree, copyfile
from multiprocessing import Pool, cpu_count 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, ImageOps from PIL import Image, ImageFile
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
@@ -227,7 +226,7 @@ def buildNCX(dstdir, title, chapters, chapternames):
ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx') ncxfile = os.path.join(dstdir, 'OEBPS', 'toc.ncx')
f = open(ncxfile, "w", encoding='UTF-8') f = open(ncxfile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
f"<ncx version=\"2005-1\" xml:lang=\"{options.language}\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n", "<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
"<head>\n", "<head>\n",
"<meta name=\"dtb:uid\" content=\"urn:uuid:", options.uuid, "\"/>\n", "<meta name=\"dtb:uid\" content=\"urn:uuid:", options.uuid, "\"/>\n",
"<meta name=\"dtb:depth\" content=\"1\"/>\n", "<meta name=\"dtb:depth\" content=\"1\"/>\n",
@@ -293,22 +292,10 @@ def buildNAV(dstdir, title, chapters, chapternames):
def buildOPF(dstdir, title, filelist, originalpath, cover=None): def buildOPF(dstdir, title, filelist, originalpath, cover=None):
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf') opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
deviceres = options.profileData[1] deviceres = options.profileData[1]
if options.vertical4panel:
writingmode = "vertical"
else:
writingmode = "horizontal"
if options.invertdirection:
if options.righttoleft: if options.righttoleft:
writingmode += "-lr" writingmode = "horizontal-rl"
else: else:
writingmode += "-rl" writingmode = "horizontal-lr"
else:
if options.righttoleft:
writingmode += "-rl"
else:
writingmode += "-lr"
f = open(opffile, "w", encoding='UTF-8') f = open(opffile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<package version=\"3.0\" unique-identifier=\"BookID\" ", "<package version=\"3.0\" unique-identifier=\"BookID\" ",
@@ -316,7 +303,7 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
"<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ", "<metadata xmlns:opf=\"http://www.idpf.org/2007/opf\" ",
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n", "xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n",
"<dc:title>", hescape(title), "</dc:title>\n", "<dc:title>", hescape(title), "</dc:title>\n",
f"<dc:language>{options.language}</dc:language>\n", "<dc:language>en-US</dc:language>\n",
"<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n", "<dc:identifier id=\"BookID\">urn:uuid:", options.uuid, "</dc:identifier>\n",
"<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"]) "<dc:contributor id=\"contributor\">KindleComicConverter-" + __version__ + "</dc:contributor>\n"])
if len(options.summary) > 0: if len(options.summary) > 0:
@@ -407,22 +394,13 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
else: else:
return "" return ""
if options.invertdirection:
if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
pageside = "left"
else:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
pageside = "right"
else:
if options.righttoleft: if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
pageside = "right" pageside = "right"
else: else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
pageside = "left" pageside = "left"
if originalpath.lower().endswith('.pdf'):
if originalpath.lower().endswith('.pdf') or originalpath.lower().endswith('.epub'):
if pageside == "right": if pageside == "right":
pageside = "left" pageside = "left"
else: else:
@@ -966,8 +944,6 @@ 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)
@@ -1104,7 +1080,6 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber):
def getMetadata(path, originalpath): def getMetadata(path, originalpath):
xmlPath = os.path.join(path, 'ComicInfo.xml') xmlPath = os.path.join(path, 'ComicInfo.xml')
options.comicinfo_chapters = [] options.comicinfo_chapters = []
options.comicinfo_xml = None
options.summary = '' options.summary = ''
titleSuffix = '' titleSuffix = ''
options.volume = '' options.volume = ''
@@ -1161,10 +1136,6 @@ def getMetadata(path, originalpath):
options.summary = xml.data['Summary'] options.summary = xml.data['Summary']
if xml.data['Series']: if xml.data['Series']:
options.series = xml.data['Series'] options.series = xml.data['Series']
# ComicInfo.xml in output may break readers like the Kobo native CBZ reader
if options.keepcomicinfo and options.format == 'CBZ':
with open(xmlPath, 'rb') as f:
options.comicinfo_xml = f.read()
os.remove(xmlPath) os.remove(xmlPath)
if originalpath.lower().endswith('.pdf'): if originalpath.lower().endswith('.pdf'):
@@ -1207,19 +1178,11 @@ def removeNonImages(filetree):
os.remove(os.path.join(root, name)) os.remove(os.path.join(root, name))
# remove empty nested folders # remove empty nested folders
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
if not os.listdir(root): if not files and not dirs:
os.rmdir(root) os.rmdir(root)
if not os.listdir(Path(filetree).parent): if not os.listdir(Path(filetree).parent):
warning = [ raise UserWarning('No images detected, nested archives are not supported.')
'No images detected.',
'',
'Possible causes:',
'',
'1) Incompatible image file extension like .jxl. Convert to .png first outside of KCC.',
'2) Nested archive: Either extract the nested archive outside of KCC or use File Fusion option.',
]
raise RuntimeError('\n'.join(warning))
def sanitizeTree(filetree, prefix='kcc'): def sanitizeTree(filetree, prefix='kcc'):
@@ -1268,7 +1231,6 @@ def flattenTree(filetree):
def sanitizePermissions(filetree): def sanitizePermissions(filetree):
os.chmod(filetree, S_IWRITE | S_IREAD | S_IEXEC)
for root, dirs, files in os.walk(filetree, False): for root, dirs, files in os.walk(filetree, False):
for name in files: for name in files:
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD) os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD)
@@ -1423,6 +1385,7 @@ def slugify(value, is_natural_sorted):
def makeZIP(zipfilename, basedir, job_progress='', isepub=False): def makeZIP(zipfilename, basedir, job_progress='', isepub=False):
start = perf_counter() start = perf_counter()
zipfilename = os.path.abspath(zipfilename) + '.zip'
if SEVENZIP in available_archive_tools(): if SEVENZIP in available_archive_tools():
if isepub: if isepub:
mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w') mimetypeFile = open(os.path.join(basedir, '!mimetype'), 'w')
@@ -1465,18 +1428,10 @@ 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,
help="Force EBOK tag instead of PDOC for MOBI")
main_options.add_argument("--invertdirection", action="store_true", dest="invertdirection", default=False,
help="Invert page turn direction")
main_options.add_argument("-q", "--hq", action="store_true", dest="hq", default=False, main_options.add_argument("-q", "--hq", action="store_true", dest="hq", default=False,
help="Try to increase the quality of magnification") help="Try to increase the quality of magnification")
main_options.add_argument("-2", "--two-panel", action="store_true", dest="autoscale", default=False, main_options.add_argument("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
help="Display two not four panels in Panel View mode") help="Display two not four panels in Panel View mode")
main_options.add_argument("--vertical4panel", action="store_true", dest="vertical4panel", default=False,
help="Display side panels first in virtual panel view")
main_options.add_argument("-w", "--webtoon", action="store_true", dest="webtoon", default=False, main_options.add_argument("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"), help="Webtoon processing mode"),
main_options.add_argument("--ts", "--targetsize", type=int, dest="targetsize", default=None, main_options.add_argument("--ts", "--targetsize", type=int, dest="targetsize", default=None,
@@ -1490,12 +1445,8 @@ def makeParser():
output_options.add_argument("--metadatatitle", type=int, dest="metadatatitle", default=0, output_options.add_argument("--metadatatitle", type=int, dest="metadatatitle", default=0,
help="Write title using ComicInfo.xml or other embedded metadata. 1: Combine Title with default schema " help="Write title using ComicInfo.xml or other embedded metadata. 1: Combine Title with default schema "
"2: Use Title only") "2: Use Title only")
output_options.add_argument("--keepcomicinfo", type=int, dest="keepcomicinfo", default=0,
help="Keep any original ComicInfo.xml files")
output_options.add_argument("-a", "--author", action="store", dest="author", default="defaultauthor", output_options.add_argument("-a", "--author", action="store", dest="author", default="defaultauthor",
help="Author name [Default=KCC]") help="Author name [Default=KCC]")
output_options.add_argument("--language", action="store", dest="language", default="en-US",
help="EPUB language [Default=en-US]")
output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto", output_options.add_argument("-f", "--format", action="store", dest="format", default="Auto",
help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB, PDF) " help="Output format (Available options: Auto, MOBI, EPUB, CBZ, KFX, MOBI+EPUB, PDF) "
"[Default=Auto]") "[Default=Auto]")
@@ -1603,9 +1554,6 @@ 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')
@@ -1725,11 +1673,6 @@ def checkTools(source):
except (FileNotFoundError, CalledProcessError): except (FileNotFoundError, CalledProcessError):
print('ERROR: KindleGen is missing!') print('ERROR: KindleGen is missing!')
sys.exit(1) sys.exit(1)
except OSError as e:
print(f"kindlegen: {e.strerror}")
print('Re-install Rosetta/Kindle Previewer/other Intel app?')
print('Please email Amazon to make Kindle Previewer Apple silicon native at amazon.com/kindle-help')
sys.exit(1)
def checkPre(source='KCC-'): def checkPre(source='KCC-'):
@@ -1775,9 +1718,7 @@ def makeFusion(sources: List[str]):
else: else:
targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}') targetpath = fusion_path.joinpath(f'{prefix}{source_path.name}')
path = getWorkFolder(source, str(targetpath)) getWorkFolder(source, str(targetpath))
if path != str(targetpath):
move(os.path.join(path, 'OEBPS', 'Images'), targetpath)
sanitizeTree(targetpath, prefix='fusion') sanitizeTree(targetpath, prefix='fusion')
# TODO: remove flattenTree when subchapters are supported # TODO: remove flattenTree when subchapters are supported
flattenTree(targetpath) flattenTree(targetpath)
@@ -1803,35 +1744,6 @@ 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)
@@ -1921,11 +1833,8 @@ def makeBook(source, qtgui=None, job_progress=''):
else: else:
filepath.append(getOutputFilename(source, options.output, '.cbz', '')) filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
if cover and cover.smartcover: if cover and cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', '##cover.jpg'), tomeNumber, len(tomes)) cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
if options.comicinfo_xml: makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
with open(os.path.join(tome, 'OEBPS', 'Images', 'ComicInfo.xml'), 'wb') as xmlOutput:
xmlOutput.write(options.comicinfo_xml)
makeZIP(filepath[-1], os.path.join(tome, "OEBPS", "Images"), job_progress)
elif options.format == 'PDF': elif options.format == 'PDF':
print(f"{job_progress}Creating PDF file with PyMuPDF...") print(f"{job_progress}Creating PDF file with PyMuPDF...")
# determine output filename based on source and tome count # determine output filename based on source and tome count
@@ -1944,11 +1853,19 @@ def makeBook(source, qtgui=None, job_progress=''):
else: else:
buildEPUB(tome, chapterNames, tomeNumber, False, cover, source, job_progress) buildEPUB(tome, chapterNames, tomeNumber, False, cover, source, job_progress)
filepath.append(getOutputFilename(source, options.output, '.epub', '')) filepath.append(getOutputFilename(source, options.output, '.epub', ''))
makeZIP(filepath[-1], tome, job_progress, True) makeZIP(tome + '_comic', tome, job_progress, True)
# Copy files to final destination (PDF files are already saved directly)
if options.format != 'PDF':
copyfile(tome + '_comic.zip', filepath[-1])
try:
os.remove(tome + '_comic.zip')
except FileNotFoundError:
# newly temporary created file is not found. It might have been already deleted
pass
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' and not options.lightnovel: if not GUI and options.format == 'MOBI':
print(f"{job_progress}Creating MOBI files...") print(f"{job_progress}Creating MOBI files...")
work = [] work = []
for i in filepath: for i in filepath:
@@ -1967,6 +1884,8 @@ def makeBook(source, qtgui=None, job_progress=''):
if not output[0]: if not output[0]:
print(f'{job_progress}Error: Failed to tweak KindleGen output!') print(f'{job_progress}Error: Failed to tweak KindleGen output!')
return filepath return filepath
else:
os.remove(i.replace('.epub', '.mobi') + '_toclean')
if cover and k.path and k.coverSupport: if cover and k.path and k.coverSupport:
options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1]) options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1])
if options.delete: if options.delete:
@@ -1987,13 +1906,12 @@ def makeBook(source, qtgui=None, job_progress=''):
def makeMOBIFix(item, uuid): def makeMOBIFix(item, uuid):
is_pdoc = options.profile in image.ProfileData.ProfilesKindlePDOC.keys() is_pdoc = options.profile in image.ProfileData.ProfilesKindlePDOC.keys()
if options.ebok:
is_pdoc = False
if not options.keep_epub: if not options.keep_epub:
os.remove(item) os.remove(item)
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean')
try: try:
dualmetafix.DualMobiMetaFix(mobiPath, bytes(uuid, 'UTF-8'), is_pdoc) dualmetafix.DualMobiMetaFix(mobiPath + '_toclean', mobiPath, bytes(uuid, 'UTF-8'), is_pdoc)
return [True] return [True]
except Exception as err: except Exception as err:
return [False, format(err)] return [False, format(err)]
@@ -2003,14 +1921,8 @@ def makeMOBIWorkerTick(output):
makeMOBIWorkerOutput.append(output) makeMOBIWorkerOutput.append(output)
if output[0] != 0: if output[0] != 0:
makeMOBIWorkerPool.terminate() makeMOBIWorkerPool.terminate()
for warning in output[3]:
print(warning)
if GUI: if GUI:
GUI.progressBarTick.emit('tick') GUI.progressBarTick.emit('tick')
if output[3]:
for warning in output[3]:
GUI.addMessage.emit(warning, 'warning', False)
GUI.addMessage.emit('', '', False)
if not GUI.conversionAlive: if not GUI.conversionAlive:
makeMOBIWorkerPool.terminate() makeMOBIWorkerPool.terminate()
@@ -2020,58 +1932,33 @@ def makeMOBIWorker(item):
kindlegenErrorCode = 0 kindlegenErrorCode = 0
kindlegenError = '' kindlegenError = ''
try: try:
# TODO: This size check is incorrect, I think kindlegen increased the limit
if os.path.getsize(item) < 629145600: if os.path.getsize(item) < 629145600:
start = perf_counter()
# TODO: should anything be done with the kindlegen output during successes?
output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item], output = subprocess_run(['kindlegen', '-dont_append_source', '-locale', 'en', item],
stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True) stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
end = perf_counter()
print(f"kindlegen: {end - start} sec")
else: else:
# ERROR: EPUB too big # ERROR: EPUB too big
kindlegenErrorCode = 23026 kindlegenErrorCode = 23026
return [kindlegenErrorCode, kindlegenError, item, []] return [kindlegenErrorCode, kindlegenError, item]
except CalledProcessError as err: except CalledProcessError as err:
warnings = []
for line in err.stdout.splitlines(): for line in err.stdout.splitlines():
# ERROR: Generic error # ERROR: Generic error
if "Error(" in line: if "Error(" in line:
kindlegenErrorCode = 1 kindlegenErrorCode = 1
kindlegenError = '\n\n'.join(warnings + [line, 'kindlegen logs dumped']) kindlegenError = line
try:
timestamp = datetime.now().isoformat(timespec='milliseconds').replace(':', '-').replace('.', '-')
with open(os.path.join(os.path.dirname(item), f'kindlegen-log-{timestamp}.txt'), 'w') as f:
f.write(err.stdout)
except Exception as e:
print(e)
# examples
# Warning(prcgen):W14019: Cover is too small
if "Warning(" in line:
if ":W14016: Cover not specified" in line and options.webtoon:
pass
else:
warnings.append(line)
# ERROR: EPUB too big # ERROR: EPUB too big
if ":E23026:" in line: if ":E23026:" in line:
kindlegenErrorCode = 23026 kindlegenErrorCode = 23026
if ":E23028:" in line:
kindlegenErrorCode = 23028
if kindlegenErrorCode > 0: if kindlegenErrorCode > 0:
break break
if ":I1036: Mobi file built successfully" in line: if ":I1036: Mobi file built successfully" in line:
return [0, '', item, warnings] return [0, '', item]
if ":I1037: Mobi file built with WARNINGS!" in line: if ":I1037: Mobi file built with WARNINGS!" in line:
return [0, '', item, warnings] return [0, '', item]
# ERROR: KCC unknown generic error # ERROR: KCC unknown generic error
if kindlegenErrorCode == 0: if kindlegenErrorCode == 0:
kindlegenErrorCode = -1 kindlegenErrorCode = err.returncode
if err.returncode == 3221226505: kindlegenError = err.stdout
kindlegenError = f'Error {err.returncode}: Unknown Windows error. Possibly filepath too long?' return [kindlegenErrorCode, kindlegenError, item]
else:
kindlegenError = f'Error {err.returncode}'
return [kindlegenErrorCode, kindlegenError, item, warnings]
def makeMOBI(work, qtgui=None): def makeMOBI(work, qtgui=None):
+1 -1
View File
@@ -62,7 +62,7 @@ def mergeDirectory(work):
imagesValid.append(i[0]) imagesValid.append(i[0])
# Silently drop directories that contain too many images # Silently drop directories that contain too many images
# 131072 = GIMP_MAX_IMAGE_SIZE / 4 # 131072 = GIMP_MAX_IMAGE_SIZE / 4
if targetHeight > 131072 * 4: if targetHeight > 131072 * 8:
raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.') raise RuntimeError(f'Image too tall at {targetHeight} pixels. {targetWidth} pixels wide. Try using separate chapter folders or file fusion.')
result = Image.new('RGB', (targetWidth, targetHeight)) result = Image.new('RGB', (targetWidth, targetHeight))
y = 0 y = 0
+3 -2
View File
@@ -22,6 +22,7 @@ from functools import cached_property, lru_cache
import os import os
from pathlib import Path from pathlib import Path
import platform import platform
import distro
from subprocess import STDOUT, PIPE, CalledProcessError from subprocess import STDOUT, PIPE, CalledProcessError
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
@@ -45,7 +46,7 @@ class ComicArchive:
[SEVENZIP, 'l', '-y', '-p1', self.basename], [SEVENZIP, 'l', '-y', '-p1', self.basename],
] ]
if platform.system() == 'Linux': if distro.id() == 'fedora' or distro.like() == 'fedora':
extraction_commands.append( extraction_commands.append(
['unrar', 'l', '-y', '-p1', self.basename], ['unrar', 'l', '-y', '-p1', self.basename],
) )
@@ -84,7 +85,7 @@ class ComicArchive:
extraction_commands.reverse() extraction_commands.reverse()
if platform.system() == 'Linux': if distro.id() == 'fedora' or distro.like() == 'fedora':
extraction_commands.append( extraction_commands.append(
['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.basename, targetdir] ['unrar', 'x', '-y', '-x__MACOSX', '-x.DS_Store', '-xthumbs.db', '-xThumbs.db', self.basename, targetdir]
) )
+2 -1
View File
@@ -136,11 +136,12 @@ def del_exth(rec0, exth_num):
class DualMobiMetaFix: class DualMobiMetaFix:
def __init__(self, outfile, asin, is_pdoc): def __init__(self, infile, outfile, asin, is_pdoc):
cdetype = b'EBOK' cdetype = b'EBOK'
if is_pdoc: if is_pdoc:
cdetype = b'PDOC' cdetype = b'PDOC'
shutil.copyfile(infile, outfile)
f = open(outfile, "r+b") f = open(outfile, "r+b")
self.datain = mmap.mmap(f.fileno(), 0) self.datain = mmap.mmap(f.fileno(), 0)
self.datain_rec0 = readsection(self.datain, 0) self.datain_rec0 = readsection(self.datain, 0)
+1 -1
View File
@@ -648,7 +648,7 @@ class Cover:
if self.options.righttoleft: if self.options.righttoleft:
self.image = self.image.crop((w * .36, 0, w, h)) self.image = self.image.crop((w * .36, 0, w, h))
else: else:
self.image = self.image.crop((0, 0, .64 * w, h)) self.image = self.image.crop((w, 0, .64 * w, h))
def save_to_folder(self, target, tomeid, len_tomes=0): def save_to_folder(self, target, tomeid, len_tomes=0):
try: try:
+2 -1
View File
@@ -1,10 +1,11 @@
Pillow>=11.3.0 Pillow>=11.3.0
psutil>=7.2.2 psutil>=5.9.5
requests>=2.34.2 requests>=2.34.2
python-slugify>=8.0.4 python-slugify>=8.0.4
packaging>=26.2 packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0 natsort>=8.4.0
distro>=1.8.0
# Below requirements are compiled in Dockefile # Below requirements are compiled in Dockefile
# numpy==2.3.4 # numpy==2.3.4
# PyMuPDF==1.26.6 # PyMuPDF==1.26.6
+2 -1
View File
@@ -1,10 +1,11 @@
PySide6==6.4.3 PySide6==6.4.3
Pillow>=11.3.0 Pillow>=11.3.0
psutil>=7.2.2 psutil>=5.9.5
requests>=2.34.2 requests>=2.34.2
python-slugify>=8.0.4 python-slugify>=8.0.4
packaging>=26.2 packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0 natsort>=8.4.0
distro>=1.8.0
numpy<2 numpy<2
PyMuPDF==1.25.5 PyMuPDF==1.25.5
+3 -2
View File
@@ -1,10 +1,11 @@
PySide6==6.1.3 PySide6==6.1.3
Pillow>=9 Pillow>=9
psutil>=7.2.2 psutil>=5.9.5
requests>=2.32.4 requests>=2.32.4
python-slugify>=8.0.4 python-slugify>=8.0.4
packaging>=26.2 packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0 natsort>=8.4.0
numpy==1.23.5 distro>=1.8.0
numpy==1.23.0
PyMuPDF>=1.16 PyMuPDF>=1.16
+2 -1
View File
@@ -1,10 +1,11 @@
PySide6<6.10 PySide6<6.10
Pillow>=11.3.0 Pillow>=11.3.0
psutil>=7.2.2 psutil>=5.9.5
requests>=2.34.2 requests>=2.34.2
python-slugify>=8.0.4,<9.0.0 python-slugify>=8.0.4,<9.0.0
packaging>=26.2 packaging>=26.2
mozjpeg-lossless-optimization>=1.2.0 mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0 natsort>=8.4.0
distro>=1.8.0
numpy>=1.22.4 numpy>=1.22.4
PyMuPDF>=1.18.0 PyMuPDF>=1.18.0
+2 -1
View File
@@ -38,7 +38,7 @@ class BuildBinaryCommand(setuptools.Command):
def run(self): def run(self):
VERSION = __version__ VERSION = __version__
if sys.platform == 'darwin': if sys.platform == 'darwin':
os.system('pyinstaller -y kcc-macos.spec') os.system('pyinstaller --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
# TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v # TODO /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime dist/Applications/Kindle\ Comic\ Converter.app -v
min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '') min_os = os.getenv('MACOSX_DEPLOYMENT_TARGET', '')
if min_os.startswith('10.1'): if min_os.startswith('10.1'):
@@ -155,6 +155,7 @@ setuptools.setup(
'python-slugify>=1.2.1,<9.0.0', 'python-slugify>=1.2.1,<9.0.0',
'mozjpeg-lossless-optimization>=1.2.0', 'mozjpeg-lossless-optimization>=1.2.0',
'natsort>=8.4.0', 'natsort>=8.4.0',
'distro>=1.8.0',
'numpy>=1.22.4', 'numpy>=1.22.4',
'packaging>=23.2', 'packaging>=23.2',
'PyMuPDF>=1.16.1', 'PyMuPDF>=1.16.1',