mirror of
https://github.com/ciromattia/kcc
synced 2026-07-11 15:33:17 +00:00
Compare commits
37 Commits
webtoon-8x
...
v10.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a52c0ce03a | |||
| 1c0506fe7d | |||
| 806c6232c3 | |||
| 6f9f5067d8 | |||
| 11be0dffcc | |||
| 4c10569ab1 | |||
| 2a5cd6718d | |||
| fd1468904f | |||
| feec40ffc8 | |||
| f6ae7661bc | |||
| ca14a4c813 | |||
| 0c1071a358 | |||
| 858251f509 | |||
| 3c66aa9a67 | |||
| 077957e665 | |||
| c3f86e29c8 | |||
| 72e1546c2c | |||
| 544e866b51 | |||
| 363a36a764 | |||
| bece5290a3 | |||
| cab3703217 | |||
| 3723bf9d52 | |||
| 2a6d61530f | |||
| 5396f1f9c4 | |||
| f1b58c83d6 | |||
| 0f009755b1 | |||
| ee5bd150e5 | |||
| a55c0ddb08 | |||
| 799961407e | |||
| bc28df1f53 | |||
| a8b2c055bf | |||
| afa9c7e7e6 | |||
| 1f1b9a37fa | |||
| c4d9512cbc | |||
| 89fc6437dd | |||
| 1e57da08a9 | |||
| b4ef37dbb9 |
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v4
|
uses: docker/login-action@v4
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: '14.0'
|
MACOSX_DEPLOYMENT_TARGET: '14.0'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -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@v6
|
- uses: actions/checkout@v7
|
||||||
- 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
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
command: build_c2p
|
command: build_c2p
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
WINDOWS_7: 1
|
WINDOWS_7: 1
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v7
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ 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?
|
||||||
@@ -239,8 +241,12 @@ 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]
|
||||||
@@ -289,8 +295,10 @@ 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'
|
||||||
|
|||||||
+805
-784
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- 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__,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -273,6 +273,12 @@ 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:
|
||||||
@@ -281,6 +287,8 @@ 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:
|
||||||
@@ -338,6 +346,8 @@ 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():
|
||||||
@@ -379,6 +389,8 @@ 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())
|
||||||
|
|
||||||
@@ -468,7 +480,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')
|
||||||
@@ -509,7 +521,6 @@ 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)
|
||||||
@@ -530,8 +541,6 @@ 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:
|
||||||
@@ -551,8 +560,6 @@ 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):
|
||||||
@@ -743,7 +750,6 @@ 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)
|
||||||
@@ -832,7 +838,6 @@ 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):
|
||||||
@@ -1080,8 +1085,13 @@ 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(),
|
||||||
@@ -1101,6 +1111,7 @@ 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(),
|
||||||
@@ -1189,6 +1200,11 @@ 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
|
||||||
@@ -1198,17 +1214,20 @@ 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', 'kcc9')
|
self.settings = QSettings('ciromattia', 'kcc10')
|
||||||
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}
|
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
|
||||||
try:
|
try:
|
||||||
self.options = self.settings.value('options', default_options)
|
self.options = self.settings.value('options', default_options)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -1235,7 +1254,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', 'wikiButton', 'defaultOutputFolderButton', 'clearButton', 'fileButton', 'deviceBox',
|
for element in ['editorButton', '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)
|
||||||
@@ -1416,8 +1435,9 @@ 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",
|
||||||
'YOUTUBE': "https://youtu.be/IR2Fhcm9658?si=Z-2zzLaUFjmaEbrj",
|
'WIKI': "https://github.com/ciromattia/kcc/wiki",
|
||||||
'COMMISSIONS': "https://github.com/ciromattia/kcc?tab=readme-ov-file#commissions",
|
'TUTORIAL': "https://youtu.be/QQ6zJcMF2Iw?si=80rfm6DU6OUJdFqa",
|
||||||
|
'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",
|
||||||
@@ -1449,11 +1469,8 @@ 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)
|
||||||
@@ -1512,6 +1529,8 @@ 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]))
|
||||||
|
|||||||
+533
-507
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '10.2.0'
|
__version__ = '10.4.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'
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from datetime import datetime
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
@@ -34,12 +35,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, copyfile
|
from shutil import move, copytree, rmtree
|
||||||
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
|
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
|
||||||
@@ -226,7 +227,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",
|
||||||
"<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
|
f"<ncx version=\"2005-1\" xml:lang=\"{options.language}\" 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",
|
||||||
@@ -292,10 +293,22 @@ 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.righttoleft:
|
|
||||||
writingmode = "horizontal-rl"
|
if options.vertical4panel:
|
||||||
|
writingmode = "vertical"
|
||||||
else:
|
else:
|
||||||
writingmode = "horizontal-lr"
|
writingmode = "horizontal"
|
||||||
|
if options.invertdirection:
|
||||||
|
if options.righttoleft:
|
||||||
|
writingmode += "-lr"
|
||||||
|
else:
|
||||||
|
writingmode += "-rl"
|
||||||
|
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\" ",
|
||||||
@@ -303,7 +316,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",
|
||||||
"<dc:language>en-US</dc:language>\n",
|
f"<dc:language>{options.language}</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:
|
||||||
@@ -394,13 +407,22 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
|
|||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if options.righttoleft:
|
if options.invertdirection:
|
||||||
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
|
if options.righttoleft:
|
||||||
pageside = "right"
|
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:
|
else:
|
||||||
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
|
if options.righttoleft:
|
||||||
pageside = "left"
|
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
|
||||||
if originalpath.lower().endswith('.pdf'):
|
pageside = "right"
|
||||||
|
else:
|
||||||
|
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
|
||||||
|
pageside = "left"
|
||||||
|
|
||||||
|
if originalpath.lower().endswith('.pdf') or originalpath.lower().endswith('.epub'):
|
||||||
if pageside == "right":
|
if pageside == "right":
|
||||||
pageside = "left"
|
pageside = "left"
|
||||||
else:
|
else:
|
||||||
@@ -944,6 +966,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)
|
||||||
@@ -1080,6 +1104,7 @@ 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 = ''
|
||||||
@@ -1136,6 +1161,10 @@ 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'):
|
||||||
@@ -1178,11 +1207,19 @@ 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 files and not dirs:
|
if not os.listdir(root):
|
||||||
os.rmdir(root)
|
os.rmdir(root)
|
||||||
|
|
||||||
if not os.listdir(Path(filetree).parent):
|
if not os.listdir(Path(filetree).parent):
|
||||||
raise UserWarning('No images detected, nested archives are not supported.')
|
warning = [
|
||||||
|
'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'):
|
||||||
@@ -1231,6 +1268,7 @@ 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)
|
||||||
@@ -1385,7 +1423,6 @@ 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')
|
||||||
@@ -1428,10 +1465,18 @@ 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,
|
||||||
@@ -1445,8 +1490,12 @@ 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]")
|
||||||
@@ -1554,6 +1603,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')
|
||||||
|
|
||||||
@@ -1673,6 +1725,11 @@ 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-'):
|
||||||
@@ -1718,7 +1775,9 @@ 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}')
|
||||||
|
|
||||||
getWorkFolder(source, str(targetpath))
|
path = 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)
|
||||||
@@ -1744,6 +1803,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)
|
||||||
@@ -1833,8 +1921,11 @@ 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))
|
||||||
makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images"), job_progress)
|
if options.comicinfo_xml:
|
||||||
|
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
|
||||||
@@ -1853,19 +1944,11 @@ 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(tome + '_comic', tome, job_progress, True)
|
makeZIP(filepath[-1], 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':
|
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:
|
||||||
@@ -1884,8 +1967,6 @@ 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:
|
||||||
@@ -1906,12 +1987,13 @@ 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 + '_toclean', mobiPath, bytes(uuid, 'UTF-8'), is_pdoc)
|
dualmetafix.DualMobiMetaFix(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)]
|
||||||
@@ -1921,8 +2003,14 @@ 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()
|
||||||
|
|
||||||
@@ -1932,33 +2020,58 @@ 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 = line
|
kindlegenError = '\n\n'.join(warnings + [line, 'kindlegen logs dumped'])
|
||||||
|
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]
|
return [0, '', item, warnings]
|
||||||
if ":I1037: Mobi file built with WARNINGS!" in line:
|
if ":I1037: Mobi file built with WARNINGS!" in line:
|
||||||
return [0, '', item]
|
return [0, '', item, warnings]
|
||||||
# ERROR: KCC unknown generic error
|
# ERROR: KCC unknown generic error
|
||||||
if kindlegenErrorCode == 0:
|
if kindlegenErrorCode == 0:
|
||||||
kindlegenErrorCode = err.returncode
|
kindlegenErrorCode = -1
|
||||||
kindlegenError = err.stdout
|
if err.returncode == 3221226505:
|
||||||
return [kindlegenErrorCode, kindlegenError, item]
|
kindlegenError = f'Error {err.returncode}: Unknown Windows error. Possibly filepath too long?'
|
||||||
|
else:
|
||||||
|
kindlegenError = f'Error {err.returncode}'
|
||||||
|
return [kindlegenErrorCode, kindlegenError, item, warnings]
|
||||||
|
|
||||||
|
|
||||||
def makeMOBI(work, qtgui=None):
|
def makeMOBI(work, qtgui=None):
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ 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
|
||||||
@@ -46,7 +45,7 @@ class ComicArchive:
|
|||||||
[SEVENZIP, 'l', '-y', '-p1', self.basename],
|
[SEVENZIP, 'l', '-y', '-p1', self.basename],
|
||||||
]
|
]
|
||||||
|
|
||||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
if platform.system() == 'Linux':
|
||||||
extraction_commands.append(
|
extraction_commands.append(
|
||||||
['unrar', 'l', '-y', '-p1', self.basename],
|
['unrar', 'l', '-y', '-p1', self.basename],
|
||||||
)
|
)
|
||||||
@@ -85,7 +84,7 @@ class ComicArchive:
|
|||||||
|
|
||||||
extraction_commands.reverse()
|
extraction_commands.reverse()
|
||||||
|
|
||||||
if distro.id() == 'fedora' or distro.like() == 'fedora':
|
if platform.system() == 'Linux':
|
||||||
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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -136,12 +136,11 @@ def del_exth(rec0, exth_num):
|
|||||||
|
|
||||||
|
|
||||||
class DualMobiMetaFix:
|
class DualMobiMetaFix:
|
||||||
def __init__(self, infile, outfile, asin, is_pdoc):
|
def __init__(self, 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)
|
||||||
|
|||||||
@@ -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((w, 0, .64 * w, h))
|
self.image = self.image.crop((0, 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:
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=7.2.2
|
||||||
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
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
PySide6==6.4.3
|
PySide6==6.4.3
|
||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=7.2.2
|
||||||
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
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
PySide6==6.1.3
|
PySide6==6.1.3
|
||||||
Pillow>=9
|
Pillow>=9
|
||||||
psutil>=5.9.5
|
psutil>=7.2.2
|
||||||
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
|
||||||
distro>=1.8.0
|
numpy==1.23.5
|
||||||
numpy==1.23.0
|
|
||||||
PyMuPDF>=1.16
|
PyMuPDF>=1.16
|
||||||
|
|||||||
+1
-2
@@ -1,11 +1,10 @@
|
|||||||
PySide6<6.10
|
PySide6<6.10
|
||||||
Pillow>=11.3.0
|
Pillow>=11.3.0
|
||||||
psutil>=5.9.5
|
psutil>=7.2.2
|
||||||
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
|
||||||
|
|||||||
@@ -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 --hidden-import=_cffi_backend -y -D -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
|
os.system('pyinstaller -y kcc-macos.spec')
|
||||||
# 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,7 +155,6 @@ 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',
|
||||||
|
|||||||
Reference in New Issue
Block a user