1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-23 09:28:59 +00:00

Compare commits

...

10 Commits

Author SHA1 Message Date
darodi
a822dfa3ae Version bump 2023-01-21 11:57:55 +01:00
clach04
70de379987 Document CLI tools debian quick install
Command Line Tools can be used with pre-built dependencies.
2023-01-21 11:36:52 +01:00
Frédéric Brière
16e275bb1f Added --croppingminimum option to set a cropping minimum area ratio
Closes #342
2023-01-21 11:30:30 +01:00
darodi
b225de7b97 Enable Synthetic Spreads for the Kobo Forma. 2023-01-21 11:20:14 +01:00
Michael Shavit
4a89446914 Enable Synthetic Spreads for the Kobo Forma.
Rotating the device into landscape mode will cause it to display two
pages side by side. This is particularly useful for input comics whose
spreads are already split into two files.

See https://github.com/kobolabs/epub-spec#synthetic-spreads for
additional information on synthetic spreads.

(Tiny drive-by: Add Kobo format to list of profiles for which the AUTO
format is EPUB)
2023-01-21 11:20:14 +01:00
StudioEtrange
64521de577 replace move with copyfile 2023-01-21 11:03:39 +01:00
catsout
38b14fd734 Using communicate instead of terminate (#459)
Fix kindlegen not clear temp files
2023-01-21 11:00:04 +01:00
Alice Charatonik
4fa72780a1 fix in fedora: 7z doesn't support rar archives, use unrar (#370)
see 
https://github.com/ttys3/fedora-rpm-p7zip
https://sourceforge.net/p/sevenzip/discussion/45798/thread/dc2d0438/#8f9f
2023-01-21 10:54:11 +01:00
Cory Kleinschmidt
c979486e28 Fix pillow backwards compatibility, add mozjpeg-lossless-optimization to setup.py (#461)
- Fix pillow backwards compatibility
- Add mozjpeg-lossless-optimization to setup.py
2023-01-21 00:35:59 +01:00
darodi
03bd67cf2f Update README.md 2023-01-20 13:08:20 +01:00
7 changed files with 102 additions and 24 deletions

View File

@@ -1,5 +1,12 @@
# CHANGELOG
#### 5.6.1:
* Fix pillow backwards compatibility, add mozjpeg-lossless-optimization to setup.py by @corylk in #461
* fix in fedora: 7z doesn't support rar archives, use unrar by @AlicesReflexion in #370
* Using communicate instead of terminate by @catsout in #459
* use copyfile and delete instead of shutil.move fix #386 by @StudioEtrange in #387
#### 5.6.0:
* Fix Docker 7z missing [darodi/kcc#31](https://github.com/darodi/kcc/issues/31), thanks [@darodi](https://github.com/darodi)
* update to python 3.11, thanks [@darodi](https://github.com/darodi)

View File

@@ -3,7 +3,7 @@
[![GitHub release](https://img.shields.io/github/v/release/ciromattia/kcc?display_name=tag&include_prereleases&label=pre-release)](https://github.com/ciromattia/kcc/releases)
[![PyPI](https://img.shields.io/github/v/release/darodi/kcc?display_name=tag&include_prereleases&label=testPypi)](https://test.pypi.org/project/KindleComicConverterDarodi/)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ciromattia/kcc/docker-publish.yml?label=docker%20build)](https://github.com/darodi/kcc/pkgs/container/kcc)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ciromattia/kcc/docker-publish.yml?label=docker%20build)](https://github.com/ciromattia/kcc/pkgs/container/kcc)
[//]: # ([![AUR](https://img.shields.io/aur/version/kcc-beta.svg?color=orange)](https://aur.archlinux.org/packages/kcc-beta))
@@ -286,6 +286,10 @@ After completed conversion, you should find ready file alongside the original in
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
CLI version of **KCC** is intended for power users. It allows using options that might not be compatible and decrease the quality of output.
CLI version has reduced dependencies, on Debian based distributions this commands should install all needed dependencies:
```
sudo apt-get install python3 p7zip-full python3-pil python3-psutil python3-slugify
```
### Profiles:

View File

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

View File

@@ -27,7 +27,7 @@ from re import sub
from stat import S_IWRITE, S_IREAD, S_IEXEC
from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED
from tempfile import mkdtemp, gettempdir, TemporaryFile
from shutil import move, copytree, rmtree
from shutil import move, copytree, rmtree, copyfile
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool
from uuid import uuid4
@@ -302,6 +302,11 @@ def buildOPF(dstdir, title, filelist, cover=None):
else:
f.writelines(["<meta name=\"orientation-lock\" content=\"portrait\"/>\n",
"<meta name=\"region-mag\" content=\"true\"/>\n"])
elif options.supportSyntheticSpread:
f.writelines([
"<meta property=\"rendition:spread\">landscape</meta>\n",
"<meta property=\"rendition:layout\">pre-paginated</meta>\n"
])
else:
f.writelines(["<meta property=\"rendition:orientation\">portrait</meta>\n",
"<meta property=\"rendition:spread\">portrait</meta>\n",
@@ -334,38 +339,64 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("<item id=\"img_" + str(uniqueid) + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\"" +
mt + "\"/>\n")
f.write("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
def pageSpreadProperty(pageside):
if options.iskindle:
return "linear=\"yes\" properties=\"page-spread-%s\"" % pageside
elif options.isKobo:
return "properties=\"rendition:page-spread-%s\"" % pageside
else:
return ""
if options.righttoleft:
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
pageside = "right"
else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
pageside = "left"
if options.iskindle:
if options.iskindle or options.supportSyntheticSpread:
for entry in reflist:
if options.righttoleft:
if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("right"))
)
pageside = "right"
elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("left"))
)
pageside = "right"
else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" +
pageside + "\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty(pageside))
)
if pageside == "right":
pageside = "left"
else:
pageside = "right"
else:
if entry.endswith("-b"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-left\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("left"))
)
pageside = "left"
elif entry.endswith("-c"):
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-right\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty("right"))
)
pageside = "left"
else:
f.write("<itemref idref=\"page_" + entry + "\" linear=\"yes\" properties=\"page-spread-" +
pageside + "\"/>\n")
f.write(
"<itemref idref=\"page_%s\" %s/>\n" % (entry,
pageSpreadProperty(pageside))
)
if pageside == "right":
pageside = "left"
else:
@@ -557,9 +588,9 @@ def imgFileProcessing(work):
for i in workImg.payload:
img = image.ComicPage(opt, *i)
if opt.cropping == 2 and not opt.webtoon:
img.cropPageNumber(opt.croppingp)
img.cropPageNumber(opt.croppingp, opt.croppingm)
if opt.cropping > 0 and not opt.webtoon:
img.cropMargin(opt.croppingp)
img.cropMargin(opt.croppingp, opt.croppingm)
img.autocontrastImage()
img.resizeImage()
if opt.forcepng and not opt.forcecolor:
@@ -951,6 +982,8 @@ def makeParser():
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--cp", "--croppingpower", type="float", dest="croppingp", default="1.0",
help="Set cropping power [Default=1.0]")
processingOptions.add_option("--cm", "--croppingminimum", type="float", dest="croppingm", default="0.0",
help="Set cropping minimum area ratio [Default=0.0]")
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
@@ -982,6 +1015,7 @@ def makeParser():
def checkOptions(options):
options.panelview = True
options.iskindle = False
options.isKobo = False
options.bordersColor = None
options.keep_epub = False
if options.format == 'EPUB-200MB':
@@ -993,6 +1027,7 @@ def checkOptions(options):
options.keep_epub = True
options.format = 'MOBI'
options.kfx = False
options.supportSyntheticSpread = False
if options.format == 'Auto':
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
options.format = 'MOBI'
@@ -1003,6 +1038,12 @@ def checkOptions(options):
options.format = 'CBZ'
if options.profile in ['K1', 'K2', 'K34', 'K578', 'KPW', 'KPW5', 'KV', 'KO', 'K11', 'KS']:
options.iskindle = True
elif options.profile in ['OTHER', 'KoMT', 'KoG', 'KoGHD', 'KoA', 'KoAHD', 'KoAH2O', 'KoAO', 'KoN', 'KoC', 'KoL', 'KoF', 'KoS', 'KoE']:
options.isKobo = True
# Other Kobo devices probably support synthetic spreads as well, but
# they haven't been tested.
if options.profile in ['KoF']:
options.supportSyntheticSpread = True
if options.white_borders:
options.bordersColor = 'white'
if options.black_borders:
@@ -1152,7 +1193,12 @@ def makeBook(source, qtgui=None):
else:
filepath.append(getOutputFilename(source, options.output, '.epub', ''))
makeZIP(tome + '_comic', tome, True)
move(tome + '_comic.zip', filepath[-1])
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)
if GUI:
GUI.progressBarTick.emit('tick')
@@ -1224,7 +1270,8 @@ def makeMOBIWorker(item):
if kindlegenErrorCode > 0:
break
if ":I1036: Mobi file built successfully" in line:
output.terminate()
output.communicate()
break
else:
# ERROR: EPUB too big
kindlegenErrorCode = 23026

View File

@@ -39,7 +39,14 @@ class ComicArchive:
break
process.communicate()
if process.returncode != 0:
raise OSError('Archive is corrupted or encrypted.')
process = Popen('unrar l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, stdin=PIPE, shell=True)
for line in process.stdout:
if b'Details: ' in line:
self.type = line.rstrip().decode().split(' ')[1].upper()
print(self.type)
break
if(self.type != 'RAR'):
raise OSError('Archive is corrupted or encrypted.')
elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']:
raise OSError('Unsupported archive format.')
@@ -50,7 +57,11 @@ class ComicArchive:
self.filepath + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate()
if process.returncode != 0:
raise OSError('Failed to extract archive.')
process = Popen('unrar x -y -x__MACOSX -x.DS_Store -xthumbs.db -xThumbs.db "' + self.filepath + '" "' +
targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True)
process.communicate()
if process.returncode != 0:
raise OSError('Failed to extract archive.')
tdir = os.listdir(targetdir)
if 'ComicInfo.xml' in tdir:
tdir.remove('ComicInfo.xml')

View File

@@ -115,10 +115,10 @@ class ComicPageParser:
self.image = Image.open(os.path.join(source[0], source[1])).convert('RGB')
self.color = self.colorCheck()
self.fill = self.fillCheck()
self.splitCheck()
# backwards compatibility for Pillow >9.1.0
if not hasattr(Image, 'Resampling'):
Image.Resampling = Image
self.splitCheck()
def getImageHistogram(self, image):
histogram = image.histogram()
@@ -350,7 +350,13 @@ class ComicPage:
)
return bbox
def cropPageNumber(self, power):
def maybeCrop(self, box, minimum):
box_area = (box[2] - box[0]) * (box[3] - box[1])
image_area = self.image.size[0] * self.image.size[1]
if (box_area / image_area) >= minimum:
self.image = self.image.crop(box)
def cropPageNumber(self, power, minimum):
if self.fill != 'white':
tmptmg = self.image.convert(mode='L')
else:
@@ -359,16 +365,18 @@ class ComicPage:
tmptmg = tmptmg.filter(ImageFilter.MinFilter(size=3))
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=5))
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(tmptmg.getbbox()) if tmptmg.getbbox() else self.image
if tmptmg.getbbox():
self.maybeCrop(tmptmg.getbbox(), minimum)
def cropMargin(self, power):
def cropMargin(self, power, minimum):
if self.fill != 'white':
tmptmg = self.image.convert(mode='L')
else:
tmptmg = ImageOps.invert(self.image.convert(mode='L'))
tmptmg = tmptmg.filter(ImageFilter.GaussianBlur(radius=3))
tmptmg = tmptmg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(self.getBoundingBox(tmptmg)) if tmptmg.getbbox() else self.image
if tmptmg.getbbox():
self.maybeCrop(self.getBoundingBox(tmptmg), minimum)
class Cover:
@@ -381,10 +389,10 @@ class Cover:
else:
self.tomeid = tomeid
self.image = Image.open(source)
self.process()
# backwards compatibility for Pillow >9.1.0
if not hasattr(Image, 'Resampling'):
Image.Resampling = Image
self.process()
def process(self):
self.image = self.image.convert('RGB')

View File

@@ -96,6 +96,7 @@ setuptools.setup(
'psutil>=5.0.0',
'python-slugify>=1.2.1,<8.0.0',
'raven>=6.0.0',
'mozjpeg-lossless-optimization>=1.1.2',
],
classifiers=[],
zip_safe=False,