mirror of
https://github.com/ciromattia/kcc
synced 2026-09-15 07:17:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74042791f6 | ||
|
|
7245fdf2e1 | ||
|
|
fe85274105 | ||
|
|
cb7592dcd0 | ||
|
|
ea532c709b | ||
|
|
1ce92bac83 | ||
|
|
5b67d4019e | ||
|
|
2423e2e44a |
@@ -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!
|
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!
|
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
|
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.
|
**WARNING**: Kindle Scribe 2025 MOBI support is limited, converted books 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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you are on macOS and use a 2022+ Kindle, you may need to use Amazon USB File Manager for Mac or OpenMTP.
|
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
|
## 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:
|
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.
|
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.
|
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
|
### 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.
|
**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.
|
Amazon's tool is for comic publishers and involves a lot of manual effort, while **KCC** is for comic/manga readers.
|
||||||
@@ -175,7 +183,11 @@ You'll need to install various tools to access important but optional features.
|
|||||||
|
|
||||||
### KindleGen
|
### KindleGen
|
||||||
|
|
||||||
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.
|
On Windows and macOS, install Kindle Previewer and `kindlegen` will be autodetected from it.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> On Sept 14, 2026, Kindle Previewer was updated to version 4, which made kindlegen harder to use.
|
||||||
|
> In the meantime, please download the older version 3: https://web.archive.org/web/20260819184257/https://kdp.amazon.com/en_US/help/topic/G202131170
|
||||||
|
|
||||||
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
|
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
|
||||||
|
|
||||||
|
|||||||
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 = [
|
mac_paths = [
|
||||||
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
|
'/Applications/Kindle Comic Creator/Kindle Comic Creator.app/Contents/MacOS',
|
||||||
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
|
'/Applications/Kindle Previewer 3.app/Contents/lib/fc/bin/',
|
||||||
|
'/Applications/Kindle Previewer 4.app/Contents/Resources/KFXGen/bin/'
|
||||||
]
|
]
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
|
os.environ['PATH'] += os.pathsep + os.pathsep.join(mac_paths +
|
||||||
|
|||||||
@@ -418,6 +418,7 @@ class WorkerThread(QThread):
|
|||||||
if GUI.jobList.item(i).icon().isNull():
|
if GUI.jobList.item(i).icon().isNull():
|
||||||
currentJobs.append(str(GUI.jobList.item(i).text()))
|
currentJobs.append(str(GUI.jobList.item(i).text()))
|
||||||
GUI.jobList.clear()
|
GUI.jobList.clear()
|
||||||
|
fusion_cover_path = None
|
||||||
if options.filefusion:
|
if options.filefusion:
|
||||||
bookDir = []
|
bookDir = []
|
||||||
MW.addMessage.emit('Attempting file fusion', 'info', False)
|
MW.addMessage.emit('Attempting file fusion', 'info', False)
|
||||||
@@ -429,7 +430,8 @@ class WorkerThread(QThread):
|
|||||||
if options.output is None:
|
if options.output is None:
|
||||||
options.output = fusion_source_parent
|
options.output = fusion_source_parent
|
||||||
currentJobs.clear()
|
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)
|
MW.addMessage.emit('Created fusion at ' + currentJobs[0], 'info', False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('Fusion Failed. ' + str(e))
|
print('Fusion Failed. ' + str(e))
|
||||||
@@ -463,7 +465,7 @@ class WorkerThread(QThread):
|
|||||||
jobargv.append(job)
|
jobargv.append(job)
|
||||||
try:
|
try:
|
||||||
comic2ebook.options = comic2ebook.checkOptions(copy(options))
|
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()
|
MW.hideProgressBar.emit()
|
||||||
except UserWarning as warn:
|
except UserWarning as warn:
|
||||||
if not self.conversionAlive:
|
if not self.conversionAlive:
|
||||||
@@ -1405,7 +1407,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.kindleGen = False
|
self.kindleGen = False
|
||||||
if startup:
|
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"
|
error = f"kindlegen: {e.strerror}\n\n Re-install or re-open Rosetta/Kindle Previewer/other Intel app?"
|
||||||
self.showDialog(error, 'error')
|
self.showDialog(error, 'error')
|
||||||
|
|
||||||
def __init__(self, kccapp, kccwindow):
|
def __init__(self, kccapp, kccwindow):
|
||||||
|
|||||||
@@ -77,9 +77,10 @@ def main(argv=None):
|
|||||||
if len(sources) == 0:
|
if len(sources) == 0:
|
||||||
print('No matching files found.')
|
print('No matching files found.')
|
||||||
return 1
|
return 1
|
||||||
|
fusion_cover_path = None
|
||||||
if options.filefusion:
|
if options.filefusion:
|
||||||
fusion_source_parent = str(Path(sources[0]).parent)
|
fusion_source_parent = str(Path(sources[0]).parent)
|
||||||
fusion_path = makeFusion(list(sources))
|
fusion_path, fusion_cover_path = makeFusion(list(sources))
|
||||||
sources.clear()
|
sources.clear()
|
||||||
sources.append(fusion_path)
|
sources.append(fusion_path)
|
||||||
for source in sources:
|
for source in sources:
|
||||||
@@ -89,7 +90,7 @@ def main(argv=None):
|
|||||||
options.output = fusion_source_parent
|
options.output = fusion_source_parent
|
||||||
options = checkOptions(options)
|
options = checkOptions(options)
|
||||||
print('Working on ' + source + '...')
|
print('Working on ' + source + '...')
|
||||||
makeBook(source)
|
makeBook(source, fusion_cover_path)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -1755,8 +1756,7 @@ def checkTools(source):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(f"kindlegen: {e.strerror}")
|
print(f"kindlegen: {e.strerror}")
|
||||||
print('Re-install Rosetta/Kindle Previewer/other Intel app?')
|
print('Re-install or re-open 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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
@@ -1773,6 +1773,19 @@ def makeFusion(sources: List[str]):
|
|||||||
start = perf_counter()
|
start = perf_counter()
|
||||||
first_path = Path(sources[0])
|
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:
|
if options.tempdir:
|
||||||
fusion_parent = first_path.parent
|
fusion_parent = first_path.parent
|
||||||
else:
|
else:
|
||||||
@@ -1814,10 +1827,10 @@ def makeFusion(sources: List[str]):
|
|||||||
print(f"makefusion: {end - start} seconds")
|
print(f"makefusion: {end - start} seconds")
|
||||||
print("Combined File: "+ str(fusion_path))
|
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()
|
start = perf_counter()
|
||||||
global GUI
|
global GUI
|
||||||
GUI = qtgui
|
GUI = qtgui
|
||||||
@@ -1912,9 +1925,12 @@ def makeBook(source, qtgui=None, job_progress=''):
|
|||||||
options.customcover = True
|
options.customcover = True
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
if fusion_cover_path:
|
||||||
|
options.customcover = True
|
||||||
|
cover_path = fusion_cover_path
|
||||||
|
|
||||||
cover = None
|
cover = None
|
||||||
if not options.webtoon:
|
if not options.webtoon or options.customcover:
|
||||||
cover = image.Cover(cover_path, options)
|
cover = image.Cover(cover_path, options)
|
||||||
|
|
||||||
x, y = image.ProfileData.Profiles[options.profile][1]
|
x, y = image.ProfileData.Profiles[options.profile][1]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from PIL import ImageOps, ImageFilter, ImageFile
|
from PIL import ImageOps, ImageFilter, ImageFile
|
||||||
|
from PIL.Image import Image
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .common_crop import threshold_from_power, group_close_values
|
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()
|
return bw_img.getbbox()
|
||||||
|
|
||||||
def ignore_pixels_near_edge(bw_img):
|
def ignore_pixels_near_edge(bw_img: Image):
|
||||||
w, h = bw_img.size
|
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 = [
|
edge_bbox = [
|
||||||
(0, 0, w, int(0.02 * h)),
|
(0, 0, w, int(0.02 * h)),
|
||||||
(0, int(0.98 * h), w, h),
|
(0, int(0.98 * h), w, h),
|
||||||
(0, 0, int(0.02 * w), h),
|
(0, 0, int(0.02 * w), h),
|
||||||
(int(0.98 * w), 0, w, h)
|
(int(0.98 * w), 0, w, h)
|
||||||
]
|
]
|
||||||
for box in edge_bbox:
|
|
||||||
edge = bw_img.crop(box)
|
inner_bbox = [
|
||||||
h = edge.histogram()
|
(int(0.02 * w), int(0.02 * h), int(0.98 * w), int(0.025 * h)), # top
|
||||||
if not edge.height or not edge.width:
|
(int(0.02 * w), int(0.975 * h), int(0.98 * w), int(0.98 * h)), # lower
|
||||||
continue
|
(int(0.02 * w), int(0.02 * h), int(0.025 * w), int(0.98 * h)), # left
|
||||||
imperfections = h[255] / (edge.height * edge.width)
|
(int(0.975 * w), int(0.02 * h), int(0.98 * w), int(0.98 * h)), # right
|
||||||
if imperfections > 0 and imperfections < .02:
|
]
|
||||||
bw_img.paste(im=0, box=box)
|
|
||||||
|
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):
|
def box_intersect(box1, box2, max_dist):
|
||||||
|
|||||||
Reference in New Issue
Block a user