1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-27 03:19:06 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Alex Xu
10037a3c9d remove original res 2026-04-20 12:20:22 -07:00
12 changed files with 46 additions and 129 deletions

View File

@@ -11,7 +11,7 @@
like Kindle, Kobo, ReMarkable, and more.
Pages display in fullscreen without margins,
with proper fixed layout support.
Supported input formats include JPG/PNG image files in folders, archives like CBZ, or PDFs.
Supported input formats include JPG/PNG image files in folders, archives, or PDFs.
Supported output formats include MOBI/AZW3, EPUB, KEPUB, CBZ, and PDF.
KCC runs on Windows, macOS, and Linux.
@@ -115,14 +115,15 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
## FAQ
- Should I use Calibre?
- No. Calibre doesn't properly support fixed layout EPUB/MOBI, so modifying KCC output (even just metadata!) in Calibre can break the formatting.
Additionally, it will break page numbers.
Viewing KCC output in Calibre will also not work properly.
Direct USB dropping is reccomended.
On 7th gen and later Kindles running firmware 5.15.1+, you can get cover thumbnails simply by USB dropping into documents folder.
On 6th gen and older, you can get cover thumbnails by keeping Kindle plugged in during conversion.
If you are careful to not modify the file however, you can still use Calibre, but direct USB dropping is reccomended.
- Blank pages?
- May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast. You can try PDF output.
- May happen when [using PNG with Kindle Scribe](https://github.com/ciromattia/kcc/issues/665) or [any format with a Kindle Colorsoft](https://github.com/ciromattia/kcc/issues/768). Solve by using JPG with Kindle Scribe or buying a Kobo Colour. Happens more often when turning pages really fast.
Going back a few pages and exiting and re-entering book should fix it temporarily.
- 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.
- 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
- Kindle panel view not working?
@@ -136,6 +137,9 @@ For flatpak, Docker, and AppImage versions, refer to the wiki: https://github.co
(no login required). Works much better than previously recommended Android File Transfer. Cannot run simutaneously with other transfer apps.
- How to make AZW3 instead of MOBI?
- The `.mobi` file generated by KCC is a dual filetype, it's both MOBI and AZW3. The file extension is `.mobi` for compatibility reasons.
- [Windows 7 support](https://github.com/ciromattia/kcc/issues/678)
- Image too dark?
- The default gamma correction of 1.8 makes the image darker, and is useful for faded/gray artwork/text. Disable by setting gamma = 1.0
- Huge margins / slow page turns?
- You likely modified the file during transfer using a 3rd party app. Try simply dragging and dropping the final mobi/kepub file into the Kindle documents folder via USB.
@@ -194,9 +198,8 @@ sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugi
'KV': ("Kindle Voyage", (1072, 1448), Palette16, 1.0),
'KPW34': ("Kindle Paperwhite 3/4", (1072, 1448), Palette16, 1.0),
'KPW5': ("Kindle Paperwhite 5/Signature Edition", (1236, 1648), Palette16, 1.0),
'KPW6': ("Kindle Paperwhite 6", (1272, 1696), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3", (1264, 1680), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1272, 1696), Palette16, 1.0),
'KO': ("Kindle Oasis 2/3/Paperwhite 12", (1264, 1680), Palette16, 1.0),
'KCS': ("Kindle Colorsoft", (1264, 1680), Palette16, 1.0),
'KS1860': ("Kindle 1860", (1860, 1920), Palette16, 1.0),
'KS1920': ("Kindle 1920", (1920, 1920), Palette16, 1.0),
'KS': ("Kindle Scribe 1/2", (1860, 2480), Palette16, 1.0),

View File

@@ -866,7 +866,7 @@ Useful if you plan to crop a little off the top and bottom to fill screen.</stri
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - BW only&lt;br/&gt;&lt;/span&gt;Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - Disabled&lt;br/&gt;&lt;/span&gt;Disable autocontrast&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - BW and Color&lt;br/&gt;&lt;/span&gt;BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Custom Autocontrast</string>
<string>Autocontrast</string>
</property>
<property name="tristate">
<bool>true</bool>

View File

@@ -795,8 +795,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.rotateBox.setChecked(False)
GUI.borderBox.setEnabled(False)
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
# GUI.upscaleBox.setEnabled(False)
# GUI.upscaleBox.setChecked(False)
GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(False)
GUI.croppingBox.setEnabled(False)
GUI.croppingBox.setChecked(False)
GUI.interPanelCropBox.setEnabled(False)
@@ -813,7 +813,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.rotateBox.setEnabled(True)
GUI.borderBox.setEnabled(True)
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if not profile['Label'].startswith('KS') or True:
if not profile['Label'].startswith('KS'):
GUI.upscaleBox.setEnabled(True)
GUI.croppingBox.setEnabled(True)
GUI.interPanelCropBox.setEnabled(True)
@@ -908,10 +908,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
if profile['Label'].startswith('KS') and False:
if profile['Label'].startswith('KS'):
GUI.upscaleBox.setDisabled(True)
else:
if not GUI.webtoonBox.isChecked() or True:
if not GUI.webtoonBox.isChecked():
GUI.upscaleBox.setEnabled(True)
if profile['Label'] == 'KCS':
current_format = GUI.formats[str(GUI.formatBox.currentText())]['format']
@@ -946,15 +946,10 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'MOBI+EPUB-200MB'):
GUI.chunkSizeCheckBox.setEnabled(False)
GUI.chunkSizeCheckBox.setChecked(False)
elif GUI.formats[str(GUI.formatBox.currentText())]['format'] == 'KFX':
GUI.mozJpegBox.setCheckState(Qt.CheckState.PartiallyChecked)
elif not GUI.webtoonBox.isChecked():
GUI.chunkSizeCheckBox.setEnabled(True)
if GUI.formats[str(GUI.formatBox.currentText())]['format'] in ('CBZ', 'PDF') and not GUI.webtoonBox.isChecked():
self.addMessage("Partially check W/B Margins if you don't want KCC to extend the image margins.", 'info')
GUI.borderBox.setCheckState(Qt.CheckState.PartiallyChecked)
else:
GUI.borderBox.setCheckState(Qt.CheckState.Unchecked)
def stripTags(self, html):
s = HTMLStripper()
@@ -1236,7 +1231,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
"CBZ": {'icon': 'CBZ', 'format': 'CBZ'},
"PDF": {'icon': 'EPUB', 'format': 'PDF'},
"PDF (200MB limit)": {'icon': 'EPUB', 'format': 'PDF-200MB'},
"KFX (Send to Kindle EPUB)": {'icon': 'KFX', 'format': 'KFX'},
"KFX (does not work)": {'icon': 'KFX', 'format': 'KFX'},
"MOBI + EPUB": {'icon': 'MOBI', 'format': 'MOBI+EPUB'},
"EPUB (200MB limit)": {'icon': 'EPUB', 'format': 'EPUB-200MB'},
"MOBI + EPUB (200MB limit)": {'icon': 'MOBI', 'format': 'MOBI+EPUB-200MB'},

View File

@@ -819,7 +819,7 @@ class Ui_mainWindow(object):
#if QT_CONFIG(tooltip)
self.autocontrastBox.setToolTip(QCoreApplication.translate("mainWindow", u"<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - BW only<br/></span>Only autocontrast bw pages. Ignored for pages where near blacks or whites don't exist.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Disabled<br/></span>Disable autocontrast</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - BW and Color<br/></span>BW and color images will be autocontrasted. Ignored for pages where near blacks or whites don't exist.</p></body></html>", None))
#endif // QT_CONFIG(tooltip)
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Custom Autocontrast", None))
self.autocontrastBox.setText(QCoreApplication.translate("mainWindow", u"Autocontrast", None))
#if QT_CONFIG(tooltip)
self.webpBox.setToolTip(QCoreApplication.translate("mainWindow", u"Replace JPG with lossy WebP and PNG with lossless WebP. This includes the JPG Quality.\n"
"\n"

View File

@@ -1,4 +1,4 @@
__version__ = '10.1.0'
__version__ = '9.7.2'
__license__ = 'ISC'
__copyright__ = '2012-2022, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>, darodi'
__docformat__ = 'restructuredtext en'

View File

@@ -18,7 +18,6 @@
# PERFORMANCE OF THIS SOFTWARE.
#
from collections import Counter
import os
import pathlib
import re
@@ -44,7 +43,7 @@ from psutil import virtual_memory, disk_usage
from html import escape as hescape
import pymupdf
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean, get_contain_resolution
from .shared import IMAGE_TYPES, getImageFileName, walkSort, walkLevel, sanitizeTrace, subprocess_run, dot_clean
from .comicarchive import SEVENZIP, available_archive_tools
from . import comic2panel
from . import image
@@ -137,14 +136,14 @@ def buildHTML(path, imgfile, imgfilepath, imgfile2=None):
"content=\"width=" + str(imgsizeframe[0]) + ", height=" + str(imgsizeframe[1]) + "\"/>\n"
"</head>\n",
"<body style=\"" + additionalStyle + "\">\n",
"<div style=\"text-align:center;\">\n",
"<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsizeframe) + "%;\">\n",
])
if options.iskindle:
# this display none div fixes formatting issues with virtual panel mode, for some reason
f.write('<div style="display:none;">.</div>\n')
f.write(f'<img width="{imgsize[0]}" height="{imgsize[1]}" src="{"../" * backref}Images/{postfix}{imgfile}"/>\n')
if imgfile2:
f.write(f'<img style="bottom: 0" width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
f.write(f'<img width="{imgsize2[0]}" height="{imgsize2[1]}" src="{"../" * backref}Images/{postfix}{imgfile2}"/>\n')
f.write("</div>\n")
if options.iskindle and options.panelview:
if options.autoscale:
@@ -325,19 +324,8 @@ def buildOPF(dstdir, title, filelist, originalpath, cover=None):
f.write("<meta name=\"cover\" content=\"cover\"/>\n")
if options.iskindle and options.profile != 'Custom':
f.writelines(["<meta name=\"fixed-layout\" content=\"true\"/>\n",
])
if not options.kfx_resolution:
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
])
else:
x, y = options.kfx_resolution
f.writelines([
"<meta name=\"original-resolution\" content=\"",
str(x) + "x" + str(y) + "\"/>\n",
])
f.writelines([
# "<meta name=\"original-resolution\" content=\"",
# str(deviceres[0]) + "x" + str(deviceres[1]) + "\"/>\n",
"<meta name=\"book-type\" content=\"comic\"/>\n",
"<meta name=\"primary-writing-mode\" content=\"" + writingmode + "\"/>\n",
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
@@ -885,18 +873,14 @@ def mupdf_pdf_process_pages_parallel(filename, output_dir, target_width, target_
def getWorkFolder(afile, workdir=None):
if not workdir:
workdir = mkdtemp('', 'KCC-')
if options.tempdir:
workdir = mkdtemp('', 'KCC-', os.path.dirname(afile))
else:
workdir = mkdtemp('', 'KCC-')
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
else:
fullPath = workdir
check_path = gettempdir()
if options.tempdir:
check_path = os.path.dirname(afile)
if os.path.isdir(afile):
if disk_usage(check_path)[2] < getDirectorySize(afile) * 2.5:
if disk_usage(gettempdir())[2] < getDirectorySize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
try:
copytree(afile, fullPath)
@@ -906,7 +890,7 @@ def getWorkFolder(afile, workdir=None):
rmtree(workdir, True)
raise UserWarning("Failed to prepare a workspace.")
elif os.path.isfile(afile):
if disk_usage(check_path)[2]< os.path.getsize(afile) * 2.5:
if disk_usage(gettempdir())[2] < os.path.getsize(afile) * 2.5:
raise UserWarning("Not enough disk space to perform conversion.")
if afile.lower().endswith('.pdf'):
if not os.path.exists(fullPath):
@@ -1091,6 +1075,11 @@ def getDirectorySize(start_path='.'):
return total_size
def getTopMargin(deviceres, size):
y = int((deviceres[1] - size[1]) / 2) / deviceres[1] * 100
return str(round(y, 1))
def getPanelViewResolution(imagesize, deviceres):
scale = float(deviceres[0]) / float(imagesize[0])
return int(deviceres[0]), int(scale * imagesize[1])
@@ -1482,15 +1471,6 @@ def checkOptions(options):
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
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')
if options.format == 'PDF-200MB':
options.targetsize = 195
options.format = 'PDF'
@@ -1522,7 +1502,10 @@ def checkOptions(options):
options.format = 'PDF'
else:
options.format = 'EPUB'
if options.profile in image.ProfileData.ProfilesKindle.keys():
options.iskindle = True
else:
options.isKobo = True
if options.white_borders:
options.bordersColor = 'white'
if options.black_borders:
@@ -1553,7 +1536,6 @@ def checkOptions(options):
options.hq = False
# KFX output create EPUB that might be can be by jhowell KFX Output Calibre plugin
if options.format == 'KFX':
options.targetsize = 195
options.format = 'EPUB'
options.kfx = True
options.panelview = False
@@ -1575,7 +1557,6 @@ def checkOptions(options):
options.jpegquality = 90
else:
options.jpegquality = 85
options.kindle_azw3 = options.iskindle and ('MOBI' in options.format or 'EPUB' in options.format)
options.kindle_scribe_azw3 = options.profile.startswith('KS') and options.kindle_azw3
@@ -1691,43 +1672,9 @@ def makeBook(source, qtgui=None, job_progress=''):
if not options.webtoon:
cover = image.Cover(cover_path, options)
x, y = image.ProfileData.Profiles[options.profile][1]
if options.webtoon:
x, y = image.ProfileData.Profiles[options.profile][1]
comic2panel.main(['-y ' + str(y), '-x' + str(x), '-i', '-m', path], job_progress, qtgui)
options.kfx_resolution = None
if options.kfx:
original_resolutions = []
normalized_resolutions = []
for root, _, files in os.walk(os.path.join(path, "OEBPS", "Images")):
for file in files:
with Image.open(os.path.join(root, file)) as imagef:
original_resolutions.append(imagef.size)
size = get_contain_resolution(imagef, (x, y))
normalized_resolutions.append(size)
counter = Counter(normalized_resolutions)
aspect_ratios = []
filtered_resolutions = []
for w, h in normalized_resolutions:
aspect_ratio = h / w
# page-like aspect ratios, could be improved
if aspect_ratio > 1.3 and aspect_ratio < 1.7:
aspect_ratios.append(aspect_ratio)
filtered_resolutions.append((w, h))
most_common_res, most_common_count = counter.most_common(1)[0]
options.kfx_resolution = most_common_res
if most_common_count / counter.total() > .6:
pass
#elif max(aspect_ratios) - min(aspect_ratios) < .2:
else:
# get the widest resolution
options.kfx_resolution = max(filtered_resolutions)
# else:
# raise UserWarning('Aspect ratio of pages too different for KFX conversion')
if options.noprocessing:
print(f"{job_progress}Do not process image, ignore any profile or processing option")
else:
@@ -1768,7 +1715,7 @@ def makeBook(source, qtgui=None, job_progress=''):
filepath.append(getOutputFilename(source, options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(source, options.output, '.cbz', ''))
if cover and cover.smartcover:
if cover.smartcover:
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)
elif options.format == 'PDF':
@@ -1776,7 +1723,7 @@ def makeBook(source, qtgui=None, job_progress=''):
# determine output filename based on source and tome count
suffix = (' ' + str(tomeNumber)) if len(tomes) > 1 else ''
output_file = getOutputFilename(source, options.output, '.pdf', suffix)
if cover and cover.smartcover:
if cover.smartcover:
cover.save_to_folder(os.path.join(tome, 'OEBPS', 'Images', 'cover.jpg'), tomeNumber, len(tomes))
# use optimized buildPDF logic with streaming and compression
output_pdf = buildPDF(tome, options.title, job_progress, None, output_file)

View File

@@ -29,7 +29,6 @@ from PIL import Image, ImageOps, ImageFile, ImageChops, ImageDraw
from .rainbow_artifacts_eraser import erase_rainbow_artifacts
from .page_number_crop_alg import get_bbox_crop_margin_page_number, get_bbox_crop_margin
from .inter_panel_crop_alg import crop_empty_inter_panel
from .shared import get_contain_resolution
AUTO_CROP_THRESHOLD = 0.015
ImageFile.LOAD_TRUNCATED_IMAGES = True
@@ -518,17 +517,7 @@ class ComicPage:
ratio_device = float(self.size[1]) / float(self.size[0])
ratio_image = float(self.image.size[1]) / float(self.image.size[0])
method = self.resize_method()
if self.opt.kfx:
ratio_kfx = self.opt.kfx_resolution[1] / self.opt.kfx_resolution[0]
contain_size = get_contain_resolution(self.image, self.size)
if abs(ratio_image - ratio_kfx) < AUTO_CROP_THRESHOLD:
if contain_size[0] > self.opt.kfx_resolution[0] or contain_size[1] > self.opt.kfx_resolution[1]:
self.image = ImageOps.fit(self.image, self.opt.kfx_resolution, method=method)
else:
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
else:
self.image = ImageOps.pad(self.image, self.opt.kfx_resolution, method=method, color=self.fill)
elif self.opt.stretch:
if self.opt.stretch:
self.image = self.image.resize(self.size, method)
elif method == Image.Resampling.BICUBIC and not self.opt.upscale:
pass
@@ -537,7 +526,7 @@ class ComicPage:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif abs(ratio_image - ratio_device) < AUTO_CROP_THRESHOLD:
self.image = ImageOps.fit(self.image, self.size, method=method)
elif (self.opt.format in ('CBZ', 'PDF')) and not self.opt.white_borders:
elif (self.opt.format in ('CBZ', 'PDF') or self.opt.kfx) and not self.opt.white_borders:
self.image = ImageOps.pad(self.image, self.size, method=method, color=self.fill)
else:
self.image = ImageOps.contain(self.image, self.size, method=method)

View File

@@ -61,23 +61,6 @@ def getImageFileName(imgfile):
ext = ext.lower()
return [name, ext]
def get_contain_resolution(image, size):
'''same code as Pillow ImageOps.contain()'''
im_ratio = image.width / image.height
dest_ratio = size[0] / size[1]
if im_ratio != dest_ratio:
if im_ratio > dest_ratio:
new_height = round(image.height / image.width * size[0])
if new_height != size[1]:
size = (size[0], new_height)
else:
new_width = round(image.width / image.height * size[1])
if new_width != size[0]:
size = (new_width, size[1])
return size
def walkSort(dirnames, filenames):
convert = lambda text: int(text) if text.isdigit() else text

View File

@@ -2,7 +2,7 @@ Pillow>=11.3.0
psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
packaging>=26.2
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
distro>=1.8.0

View File

@@ -4,7 +4,7 @@ psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
raven>=6.0.0
packaging>=26.2
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
distro>=1.8.0

View File

@@ -4,7 +4,7 @@ psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1
raven>=6.0.0
packaging>=26.2
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
distro>=1.8.0

View File

@@ -4,7 +4,7 @@ psutil>=5.9.5
requests>=2.31.0
python-slugify>=1.2.1,<9.0.0
raven>=6.0.0
packaging>=26.2
packaging>=23.2
mozjpeg-lossless-optimization>=1.2.0
natsort>=8.4.0
distro>=1.8.0