mirror of
https://github.com/ciromattia/kcc
synced 2026-09-16 15:57:09 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fd24d6a3 | ||
|
|
4f06f4ef90 | ||
|
|
c611cf6dc7 | ||
|
|
80fe280b50 | ||
|
|
ff9a5e518d | ||
|
|
05e5844779 | ||
|
|
aaf3548595 | ||
|
|
ea8a4db26e | ||
|
|
aa652ff6c0 | ||
|
|
a310d4c2b1 | ||
|
|
7245fdf2e1 | ||
|
|
fe85274105 | ||
|
|
cb7592dcd0 | ||
|
|
ea532c709b | ||
|
|
1ce92bac83 | ||
|
|
5b67d4019e | ||
|
|
2423e2e44a | ||
|
|
eb694b2dc3 | ||
|
|
04aac3ffc0 | ||
|
|
0f630c5804 | ||
|
|
d272073748 |
@@ -58,7 +58,7 @@ jobs:
|
||||
name: windows-build-${{ matrix.entry }}
|
||||
path: dist/*.exe
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v2.3
|
||||
uses: signpath/github-action-submit-signing-request@v3.0
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
name: windows7-build
|
||||
path: dist/*.exe
|
||||
- id: optional_step_id
|
||||
uses: signpath/github-action-submit-signing-request@v2.3
|
||||
uses: signpath/github-action-submit-signing-request@v3.0
|
||||
if: ${{ github.repository == 'ciromattia/kcc' }}
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
|
||||
@@ -55,19 +55,23 @@ You can view the split halves either before or after combined spreads:
|
||||
|
||||
Just drop your input files into the KCC window, hit convert, and USB drop the output files onto your device's `documents` folder!
|
||||
|
||||
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
|
||||

|
||||
|
||||
There are a lot of options, but most people only care about the ones checked above.
|
||||
|
||||
Hover over each option to see details in a tooltip.
|
||||
|
||||
NOTE: some checkboxes can have a half-check marks.
|
||||
|
||||
Then all your usb loaded comic files will show up next to all your normal books in your ereader library!
|
||||
|
||||
Here's a quick video demo of how the process works (using expert mode):
|
||||
|
||||
https://github.com/user-attachments/assets/da73d625-e082-482d-91a4-ae4765e96fd7
|
||||
|
||||
The final result looks incredible on my 10" Kindle Scribe: https://www.youtube.com/watch?v=2CIHW2N9Enc
|
||||
|
||||
**WARNING**: Kindle Scribe 2025 MOBI support may have blank pages. Use PDF instead.
|
||||
|
||||
The GUI looks like this, built in Qt6.
|
||||
There are a lot of options, but most people only care about the ones checked below.
|
||||
Hover over each option to see details in a tooltip.
|
||||
|
||||

|
||||
**WARNING**: Kindle Scribe 2025 MOBI support is limited, converted books may have blank pages. Use PDF instead.
|
||||
|
||||
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
|
||||
|
||||
@@ -77,7 +81,7 @@ Installation tutorial: https://www.youtube.com/watch?v=IR2Fhcm9658
|
||||
|
||||
## Kindle Jailbreak for KOreader
|
||||
|
||||
Jailbreaking to use KOreader is optional. KCC works just fine with the native mobi manga viewer.
|
||||
Jailbreaking to use KOreader is optional. MOBI manga converted by KCC can be opened in Kindle’s native reader just fine
|
||||
|
||||
But if you jailbreak your Kindle, you'll unlock additional features like:
|
||||
|
||||
@@ -86,6 +90,10 @@ But if you jailbreak your Kindle, you'll unlock additional features like:
|
||||
2) no dependency on kindlegen, which can mean **twice as fast conversions** since you can convert to CBZ directly.
|
||||
3) You can dither by partially checking the JPG/PNG box and can crop in KCC instead of KOreader. This may increase page turn speed and battery.
|
||||
|
||||
Common KOreader manga mistakes: https://youtu.be/gaakIh9p8tw?si=X7OgPJlAvL9XGbLR
|
||||
|
||||
Set per folder setting defaults: https://youtu.be/O9p6HVWIQ6s?si=Jtx607erCBA_auWM
|
||||
|
||||
### A word of warning
|
||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||
@@ -175,8 +183,6 @@ You'll need to install various tools to access important but optional features.
|
||||
|
||||
### KindleGen
|
||||
|
||||
(This is not needed for Koreader users on jailbroken Kindle. Just set output to CBZ instead.)
|
||||
|
||||
On Windows and macOS, install [Kindle Previewer](https://www.amazon.com/Kindle-Previewer/b?ie=UTF8&node=21381691011) and `kindlegen` will be autodetected from it.
|
||||
|
||||
If you have issues detecting it, get stuck on the MOBI conversion step, or use Linux AppImage or Flatpak, refer to the wiki: https://github.com/ciromattia/kcc/wiki/Installation#kindlegen
|
||||
|
||||
+686
-664
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 536 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 619 KiB |
@@ -33,6 +33,7 @@ def modify_path():
|
||||
mac_paths = [
|
||||
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
|
||||
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
|
||||
'/Applications/Kindle Previewer 4.app/Contents/Resources/KFXGen/bin/'
|
||||
]
|
||||
if getattr(sys, 'frozen', False):
|
||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
|
||||
@@ -68,6 +69,7 @@ def modify_path():
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\KC2'),
|
||||
os.path.expandvars('%LOCALAPPDATA%\\Amazon\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
os.path.expandvars('%UserProfile%\\Kindle Previewer 3\\lib\\fc\\bin\\'),
|
||||
'C:\\Tools',
|
||||
'C:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'D:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
'E:\\Apps\\Kindle Previewer 3\\lib\\fc\\bin',
|
||||
|
||||
+115
-37
@@ -32,7 +32,6 @@ import sys
|
||||
from urllib.parse import unquote
|
||||
from time import sleep
|
||||
from shutil import move, rmtree
|
||||
from subprocess import STDOUT, PIPE, CalledProcessError
|
||||
|
||||
import requests
|
||||
from xml.sax.saxutils import escape
|
||||
@@ -45,9 +44,9 @@ from PIL.Image import Dither
|
||||
|
||||
from .KCC_spread_label import LabelSpreadsDialog
|
||||
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel, subprocess_run
|
||||
from .shared import HTMLStripper, sanitizeTrace, walkLevel
|
||||
from .comicarchive import SEVENZIP, TAR, available_archive_tools
|
||||
from .comic2ebook import OS_SORT_KEY, flattenTree, getWorkFolder, removeNonImages, sanitizeTree
|
||||
from .comic2ebook import OS_SORT_KEY, flattenTree, getWorkFolder, removeNonImages, sanitizeTree, detectKindleGen
|
||||
from . import __version__
|
||||
from . import comic2ebook
|
||||
from . import metadata
|
||||
@@ -155,17 +154,21 @@ class Icons:
|
||||
|
||||
|
||||
class VersionThread(QThread):
|
||||
def __init__(self):
|
||||
def __init__(self, startNumber2):
|
||||
QThread.__init__(self)
|
||||
self.newVersion = ''
|
||||
self.md5 = ''
|
||||
self.barProgress = 0
|
||||
self.answer = None
|
||||
self.startNumber2 = startNumber2
|
||||
|
||||
def __del__(self):
|
||||
self.wait()
|
||||
|
||||
def run(self):
|
||||
if self.startNumber2 < 5:
|
||||
return
|
||||
|
||||
try:
|
||||
# unauthenticated API requests limit is 60 req/hour
|
||||
if getattr(sys, 'frozen', False):
|
||||
@@ -414,6 +417,7 @@ class WorkerThread(QThread):
|
||||
if GUI.jobList.item(i).icon().isNull():
|
||||
currentJobs.append(str(GUI.jobList.item(i).text()))
|
||||
GUI.jobList.clear()
|
||||
fusion_cover_path = None
|
||||
if options.filefusion:
|
||||
bookDir = []
|
||||
MW.addMessage.emit('Attempting file fusion', 'info', False)
|
||||
@@ -425,7 +429,8 @@ class WorkerThread(QThread):
|
||||
if options.output is None:
|
||||
options.output = fusion_source_parent
|
||||
currentJobs.clear()
|
||||
currentJobs.append(comic2ebook.makeFusion(bookDir))
|
||||
job, fusion_cover_path = comic2ebook.makeFusion(bookDir)
|
||||
currentJobs.append(job)
|
||||
MW.addMessage.emit('Created fusion at ' + currentJobs[0], 'info', False)
|
||||
except Exception as e:
|
||||
print('Fusion Failed. ' + str(e))
|
||||
@@ -459,7 +464,7 @@ class WorkerThread(QThread):
|
||||
jobargv.append(job)
|
||||
try:
|
||||
comic2ebook.options = comic2ebook.checkOptions(copy(options))
|
||||
outputPath = comic2ebook.makeBook(job, self, job_progress_number)
|
||||
outputPath = comic2ebook.makeBook(job, fusion_cover_path, self, job_progress_number)
|
||||
MW.hideProgressBar.emit()
|
||||
except UserWarning as warn:
|
||||
if not self.conversionAlive:
|
||||
@@ -823,6 +828,91 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://humblebundleinc.sjv.io/3JaR3A'))
|
||||
|
||||
def toggleExpert(self):
|
||||
# noinspection PyCallByClass
|
||||
if self.expertMode:
|
||||
self.hide_expert_options()
|
||||
self.expertMode = False
|
||||
else:
|
||||
self.show_expert_options()
|
||||
self.expertMode = True
|
||||
|
||||
def show_expert_options(self):
|
||||
GUI.spreadShiftBox.show()
|
||||
GUI.fileFusionBox.show()
|
||||
GUI.jpegQualityBox.show()
|
||||
GUI.pngLegacyBox.show()
|
||||
GUI.webpBox.show()
|
||||
GUI.lightnovelBox.show()
|
||||
GUI.wallpaperBox.show()
|
||||
GUI.outputSplit.show()
|
||||
GUI.maximizeStrips.show()
|
||||
GUI.noRotateBox.show()
|
||||
GUI.rotateFirstBox.show()
|
||||
GUI.coverFillBox.show()
|
||||
GUI.rotateRightBox.show()
|
||||
GUI.smartCoverCropBox.show()
|
||||
GUI.onePageLandscapeBox.show()
|
||||
GUI.qualityBox.show()
|
||||
GUI.gammaBox.show()
|
||||
GUI.interPanelCropBox.show()
|
||||
GUI.autoLevelBox.show()
|
||||
GUI.autocontrastBox.show()
|
||||
GUI.vertical4PanelBox.show()
|
||||
GUI.tempDirBox.show()
|
||||
GUI.deleteBox.show()
|
||||
GUI.metadataTitleBox.show()
|
||||
GUI.keepComicInfoBox.show()
|
||||
GUI.pdfWidthBox.show()
|
||||
GUI.chunkSizeCheckBox.show()
|
||||
GUI.disableProcessingBox.show()
|
||||
GUI.noQuantizeBox.show()
|
||||
GUI.forcePngRgbBox.show()
|
||||
GUI.invertDirectionBox.show()
|
||||
GUI.ebokBox.show()
|
||||
GUI.languageEdit.show()
|
||||
GUI.labelSpreadsButton.show()
|
||||
GUI.editorButton.show()
|
||||
GUI.easyLabel.hide()
|
||||
|
||||
def hide_expert_options(self):
|
||||
GUI.spreadShiftBox.hide()
|
||||
GUI.fileFusionBox.hide()
|
||||
GUI.jpegQualityBox.hide()
|
||||
GUI.pngLegacyBox.hide()
|
||||
GUI.webpBox.hide()
|
||||
GUI.lightnovelBox.hide()
|
||||
GUI.wallpaperBox.hide()
|
||||
GUI.outputSplit.hide()
|
||||
GUI.maximizeStrips.hide()
|
||||
GUI.noRotateBox.hide()
|
||||
GUI.rotateFirstBox.hide()
|
||||
GUI.coverFillBox.hide()
|
||||
GUI.rotateRightBox.hide()
|
||||
GUI.smartCoverCropBox.hide()
|
||||
GUI.onePageLandscapeBox.hide()
|
||||
GUI.qualityBox.hide()
|
||||
GUI.gammaBox.hide()
|
||||
GUI.interPanelCropBox.hide()
|
||||
GUI.autoLevelBox.hide()
|
||||
GUI.autocontrastBox.hide()
|
||||
GUI.vertical4PanelBox.hide()
|
||||
GUI.tempDirBox.hide()
|
||||
GUI.deleteBox.hide()
|
||||
GUI.metadataTitleBox.hide()
|
||||
GUI.keepComicInfoBox.hide()
|
||||
GUI.pdfWidthBox.hide()
|
||||
GUI.chunkSizeCheckBox.hide()
|
||||
GUI.disableProcessingBox.hide()
|
||||
GUI.noQuantizeBox.hide()
|
||||
GUI.forcePngRgbBox.hide()
|
||||
GUI.invertDirectionBox.hide()
|
||||
GUI.ebokBox.hide()
|
||||
GUI.languageEdit.hide()
|
||||
GUI.labelSpreadsButton.hide()
|
||||
GUI.editorButton.hide()
|
||||
GUI.easyLabel.show()
|
||||
|
||||
def openYouTube(self):
|
||||
# noinspection PyCallByClass
|
||||
QDesktopServices.openUrl(QUrl('https://www.youtube.com/@eink-dude'))
|
||||
@@ -1164,8 +1254,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.needClean = True
|
||||
return
|
||||
if 'MOBI' in GUI.formats[str(GUI.formatBox.currentText())]['format'] and not self.kindleGen:
|
||||
self.detectKindleGen()
|
||||
if not self.kindleGen:
|
||||
if not detectKindleGen(GUI):
|
||||
self.progress.stop()
|
||||
GUI.jobList.clear()
|
||||
self.display_kindlegen_missing()
|
||||
self.needClean = True
|
||||
@@ -1188,11 +1278,13 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if not GUI.convertButton.isEnabled():
|
||||
event.ignore()
|
||||
self.settings.setValue('settingsVersion', __version__)
|
||||
self.settings.setValue('expertMode', self.expertMode)
|
||||
self.settings.setValue('lastPath', self.lastPath)
|
||||
self.settings.setValue('defaultOutputFolder', self.defaultOutputFolder)
|
||||
self.settings.setValue('lastDevice', GUI.deviceBox.currentIndex())
|
||||
self.settings.setValue('currentFormat', GUI.formatBox.currentIndex())
|
||||
self.settings.setValue('startNumber', self.startNumber + 1)
|
||||
self.settings.setValue('startNumber2', self.startNumber2 + 1)
|
||||
self.settings.setValue('windowSize', str(MW.size().width()) + 'x' + str(MW.size().height()))
|
||||
self.settings.setValue('options', {'mangaBox': GUI.mangaBox.checkState(),
|
||||
'lightnovelBox': GUI.lightnovelBox.checkState(),
|
||||
@@ -1291,32 +1383,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
self.saveSettings(None)
|
||||
sys.exit(0)
|
||||
|
||||
def detectKindleGen(self, startup=False):
|
||||
if not sys.platform.startswith('win'):
|
||||
try:
|
||||
os.chmod('/usr/local/bin/kindlegen', 0o755)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||
self.kindleGen = True
|
||||
for line in versionCheck.stdout.splitlines():
|
||||
if 'Amazon kindlegen' in line:
|
||||
versionCheck = line.split('V')[1].split(' ')[0]
|
||||
if Version(versionCheck) < Version('2.9'):
|
||||
self.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
|
||||
' is outdated! MOBI conversion might fail.', 'warning')
|
||||
break
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
self.kindleGen = False
|
||||
if startup:
|
||||
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):
|
||||
global APP, MW, GUI
|
||||
APP = kccapp
|
||||
@@ -1332,11 +1398,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if not os.path.exists(self.defaultOutputFolder):
|
||||
self.defaultOutputFolder = ''
|
||||
|
||||
self.expertMode = self.settings.value('expertMode', False, type=bool)
|
||||
if not self.expertMode:
|
||||
GUI.hide_expert_options()
|
||||
else:
|
||||
GUI.easyLabel.hide()
|
||||
|
||||
# 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.startNumber = self.settings.value('startNumber', 0, type=int)
|
||||
self.startNumber2 = self.settings.value('startNumber2', 0, type=int)
|
||||
self.windowSize = self.settings.value('windowSize', '0x0', type=str)
|
||||
default_options = {'gammaSlider': 0, 'croppingBox': 2, 'croppingPowerSlider': 100, 'rotateBox': 1, 'mangaBox': 2}
|
||||
try:
|
||||
@@ -1344,8 +1417,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
except Exception:
|
||||
self.options = default_options
|
||||
self.worker = WorkerThread()
|
||||
self.versionCheck = VersionThread()
|
||||
self.worker.setObjectName('worker')
|
||||
self.versionCheck = VersionThread(self.startNumber2)
|
||||
self.versionCheck.setObjectName('version')
|
||||
self.progress = ProgressThread()
|
||||
self.progress.setObjectName('progress')
|
||||
self.tray = SystemTrayIcon()
|
||||
self.conversionAlive = False
|
||||
self.needClean = True
|
||||
@@ -1562,8 +1638,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
statusBarLabel.setOpenExternalLinks(True)
|
||||
GUI.statusBar.addPermanentWidget(statusBarLabel, 1)
|
||||
|
||||
self.addMessage('<b>Tip:</b> Hover mouse over options to see additional information in tooltips.', 'info')
|
||||
self.addMessage('<b>Tip:</b> Hover mouse over options/buttons to see explanations. Boxes can be partially/fully checked.', 'info')
|
||||
self.addMessage('<b>Tip:</b> You can drag and drop image folders or comic files/archives into this window to convert.', 'info')
|
||||
self.addMessage("<b>Tip:</b> Calibre may add margins! USB drop directly into the device's documents folder instead.", 'info')
|
||||
self.addMessage("<b>Tip:</b> You can toggle easy/expert mode using button at top right.", 'info')
|
||||
if self.startNumber < 5:
|
||||
self.addMessage('Since you are a new user of <b>KCC</b> please see few '
|
||||
'<a href="https://github.com/ciromattia/kcc/wiki/Important-tips">important tips</a>.',
|
||||
@@ -1574,7 +1652,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
if not any([self.tar, self.sevenzip]):
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc#7-zip">Install 7z (link)</a>'
|
||||
' to enable CBZ/CBR/ZIP/etc processing.', 'warning')
|
||||
self.detectKindleGen(True)
|
||||
|
||||
APP.messageFromOtherInstance.connect(self.handleMessage)
|
||||
GUI.defaultOutputFolderButton.clicked.connect(self.selectDefaultOutputFolder)
|
||||
@@ -1584,6 +1661,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
||||
GUI.editorButton.clicked.connect(self.selectFileMetaEditor)
|
||||
GUI.kofiButton.clicked.connect(self.openKofi)
|
||||
GUI.humbleButton.clicked.connect(self.openHumble)
|
||||
GUI.expertButton.clicked.connect(self.toggleExpert)
|
||||
GUI.convertButton.clicked.connect(self.convertStart)
|
||||
GUI.labelSpreadsButton.clicked.connect(self.labelSpreadsStart)
|
||||
GUI.gammaSlider.valueChanged.connect(self.changeGamma)
|
||||
|
||||
+729
-716
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
__version__ = '11.1.0'
|
||||
__version__ = '11.3.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -40,6 +40,7 @@ from shutil import move, copytree, rmtree
|
||||
from multiprocessing import Pool, cpu_count
|
||||
from uuid import uuid4
|
||||
from natsort import os_sort_keygen, os_sorted
|
||||
from packaging.version import Version
|
||||
from slugify import slugify as slugify_ext
|
||||
from PIL import Image, ImageFile, ImageOps
|
||||
from pathlib import Path
|
||||
@@ -77,9 +78,10 @@ def main(argv=None):
|
||||
if len(sources) == 0:
|
||||
print('No matching files found.')
|
||||
return 1
|
||||
fusion_cover_path = None
|
||||
if options.filefusion:
|
||||
fusion_source_parent = str(Path(sources[0]).parent)
|
||||
fusion_path = makeFusion(list(sources))
|
||||
fusion_path, fusion_cover_path = makeFusion(list(sources))
|
||||
sources.clear()
|
||||
sources.append(fusion_path)
|
||||
for source in sources:
|
||||
@@ -89,7 +91,7 @@ def main(argv=None):
|
||||
options.output = fusion_source_parent
|
||||
options = checkOptions(options)
|
||||
print('Working on ' + source + '...')
|
||||
makeBook(source)
|
||||
makeBook(source, fusion_cover_path)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -1748,17 +1750,58 @@ def checkTools(source):
|
||||
print('ERROR: 7z is missing!')
|
||||
sys.exit(1)
|
||||
if options.format == 'MOBI':
|
||||
try:
|
||||
subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, check=True)
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
print('ERROR: KindleGen is missing!')
|
||||
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')
|
||||
if not detectKindleGen():
|
||||
sys.exit(1)
|
||||
|
||||
def detectKindleGen(GUI=None):
|
||||
if sys.platform.startswith('win'):
|
||||
if not _detectKindleGen():
|
||||
start = perf_counter()
|
||||
try:
|
||||
for i in range(0, 200):
|
||||
for j in range(0, 20):
|
||||
for k in range (0, 20):
|
||||
path = f'C:\\Program Files\\WindowsApps\\KindlePreviewerApp_4.{i}.{j}.{k}_x64__ek06e0aw29nma'
|
||||
if os.path.exists(path):
|
||||
kindlegen_path = os.path.join(path, 'KindlePreviewerApp\\resources\\KFXGen\\bin\\kindlegen.exe')
|
||||
tool_path = 'C:\\Tools'
|
||||
if not os.path.isdir(tool_path):
|
||||
os.mkdir(tool_path)
|
||||
shutil.copy2(kindlegen_path, tool_path)
|
||||
except Exception:
|
||||
pass
|
||||
end = perf_counter()
|
||||
print(f"Search for Windows KindleGen: {end - start} sec")
|
||||
return _detectKindleGen(GUI)
|
||||
else:
|
||||
try:
|
||||
os.chmod('/usr/local/bin/kindlegen', 0o755)
|
||||
except Exception:
|
||||
pass
|
||||
return _detectKindleGen(GUI)
|
||||
|
||||
def _detectKindleGen(GUI=None):
|
||||
try:
|
||||
versionCheck = subprocess_run(['kindlegen', '-locale', 'en'], stdout=PIPE, stderr=STDOUT, encoding='UTF-8', errors='ignore', check=True)
|
||||
for line in versionCheck.stdout.splitlines():
|
||||
if 'Amazon kindlegen' in line:
|
||||
versionCheck = line.split('V')[1].split(' ')[0]
|
||||
if Version(versionCheck) < Version('2.9'):
|
||||
if GUI:
|
||||
GUI.addMessage('Your <a href="https://www.amazon.com/b?node=23496309011">KindleGen</a>'
|
||||
' is outdated! MOBI conversion might fail.', 'warning')
|
||||
break
|
||||
return True
|
||||
except (FileNotFoundError, CalledProcessError):
|
||||
print('ERROR: KindleGen is missing!')
|
||||
return False
|
||||
except (OSError, Exception) as e:
|
||||
print(f"kindlegen: {e.strerror}")
|
||||
print('Re-install or re-open Rosetta/Kindle Previewer/other Intel app?')
|
||||
if GUI:
|
||||
error = f"kindlegen: {e.strerror}\n\n Re-install or re-open Rosetta/Kindle Previewer/other Intel app?"
|
||||
GUI.showDialog(error, 'error')
|
||||
return False
|
||||
|
||||
def checkPre(source='KCC-'):
|
||||
# Make sure that all temporary files are gone
|
||||
@@ -1773,6 +1816,19 @@ def makeFusion(sources: List[str]):
|
||||
start = perf_counter()
|
||||
first_path = Path(sources[0])
|
||||
|
||||
fusion_cover_path = None
|
||||
if first_path.parent.joinpath('Covers').is_dir():
|
||||
covers = os.listdir(first_path.parent.joinpath('Covers'))
|
||||
filtered_covers = []
|
||||
for cover in covers:
|
||||
_, cover_ext = getImageFileName(cover)
|
||||
if cover_ext in IMAGE_TYPES:
|
||||
filtered_covers.append(cover)
|
||||
try:
|
||||
fusion_cover_path = first_path.parent.joinpath('Covers', filtered_covers[0])
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if options.tempdir:
|
||||
fusion_parent = first_path.parent
|
||||
else:
|
||||
@@ -1814,10 +1870,10 @@ def makeFusion(sources: List[str]):
|
||||
print(f"makefusion: {end - start} seconds")
|
||||
print("Combined File: "+ str(fusion_path))
|
||||
|
||||
return str(fusion_path)
|
||||
return str(fusion_path), fusion_cover_path
|
||||
|
||||
|
||||
def makeBook(source, qtgui=None, job_progress=''):
|
||||
def makeBook(source, fusion_cover_path=None, qtgui=None, job_progress=''):
|
||||
start = perf_counter()
|
||||
global GUI
|
||||
GUI = qtgui
|
||||
@@ -1912,9 +1968,12 @@ def makeBook(source, qtgui=None, job_progress=''):
|
||||
options.customcover = True
|
||||
except IndexError:
|
||||
pass
|
||||
if fusion_cover_path:
|
||||
options.customcover = True
|
||||
cover_path = fusion_cover_path
|
||||
|
||||
cover = None
|
||||
if not options.webtoon:
|
||||
if not options.webtoon or options.customcover:
|
||||
cover = image.Cover(cover_path, options)
|
||||
|
||||
x, y = image.ProfileData.Profiles[options.profile][1]
|
||||
|
||||
@@ -99,7 +99,7 @@ class ComicArchive:
|
||||
pass
|
||||
|
||||
if missing:
|
||||
raise OSError(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
||||
raise UserWarning(f'Extraction failed, install <a href="https://github.com/ciromattia/kcc#7-zip">specialized extraction software.</a> ')
|
||||
else:
|
||||
raise OSError(EXTRACTION_ERROR)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from PIL import ImageOps, ImageFilter, ImageFile
|
||||
from PIL.Image import Image
|
||||
import numpy as np
|
||||
from .common_crop import threshold_from_power, group_close_values
|
||||
|
||||
@@ -149,22 +150,39 @@ def get_bbox_crop_margin(img, power=1, background_color='white'):
|
||||
|
||||
return bw_img.getbbox()
|
||||
|
||||
def ignore_pixels_near_edge(bw_img):
|
||||
def ignore_pixels_near_edge(bw_img: Image):
|
||||
w, h = bw_img.size
|
||||
# going 0.02-0.03 was too much
|
||||
if int(0.02 * h) == int(0.025 * h):
|
||||
return
|
||||
if int(0.02 * w) == int(0.025 * w):
|
||||
return
|
||||
edge_bbox = [
|
||||
(0, 0, w, int(0.02 * h)),
|
||||
(0, int(0.98 * h), w, h),
|
||||
(0, 0, int(0.02 * w), h),
|
||||
(int(0.98 * w), 0, w, h)
|
||||
]
|
||||
for box in edge_bbox:
|
||||
edge = bw_img.crop(box)
|
||||
h = edge.histogram()
|
||||
if not edge.height or not edge.width:
|
||||
continue
|
||||
imperfections = h[255] / (edge.height * edge.width)
|
||||
if imperfections > 0 and imperfections < .02:
|
||||
bw_img.paste(im=0, box=box)
|
||||
|
||||
inner_bbox = [
|
||||
(int(0.02 * w), int(0.02 * h), int(0.98 * w), int(0.025 * h)), # top
|
||||
(int(0.02 * w), int(0.975 * h), int(0.98 * w), int(0.98 * h)), # lower
|
||||
(int(0.02 * w), int(0.02 * h), int(0.025 * w), int(0.98 * h)), # left
|
||||
(int(0.975 * w), int(0.02 * h), int(0.98 * w), int(0.98 * h)), # right
|
||||
]
|
||||
|
||||
for edge_box, inner_box in zip(edge_bbox, inner_bbox):
|
||||
inner_edge = bw_img.crop(inner_box)
|
||||
h = inner_edge.histogram()
|
||||
imperfections = h[255] / (inner_edge.height * inner_edge.width)
|
||||
|
||||
if imperfections > 0 and imperfections < .001:
|
||||
bw_img.paste(im=0, box=inner_box)
|
||||
if imperfections < .001:
|
||||
edge = bw_img.crop(edge_box)
|
||||
edge_h = edge.histogram()
|
||||
if edge_h[-1] != 0:
|
||||
bw_img.paste(im=0, box=edge_box)
|
||||
|
||||
|
||||
def box_intersect(box1, box2, max_dist):
|
||||
|
||||
Reference in New Issue
Block a user