1
0
mirror of https://github.com/ciromattia/kcc synced 2026-07-15 17:26:09 +00:00

Version 1.2 - comic optimization, mangling, and more coherent codebase

This commit is contained in:
Ciro Mattia Gonano
2012-12-06 11:21:56 +01:00
parent 06675dd68f
commit 5fe3aabc4f
10 changed files with 751 additions and 234 deletions
+3 -3
View File
@@ -55,11 +55,11 @@
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>0.0</real>
<real>568</real>
<key>savedFrame</key>
<string>444 56 1021 972 0 0 1680 1028 </string>
<string>144 338 889 690 0 0 1680 1028 </string>
<key>selectedTabView</key>
<string>event log</string>
<string>result</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,81 @@
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
__version__ = '1.0'
import os
class CBxArchive:
def __init__(self, origFileName):
self.cbxexts = ['.zip','.cbz','.rar','.cbr']
self.origFileName = origFileName
self.filename = os.path.splitext(origFileName)
self.path = self.filename[0]
def isCbxFile(self):
result = (self.filename[1].lower() in self.cbxexts)
if result == True:
return result
return False
def getPath(self):
return self.path
def extractCBZ(self):
try:
from zipfile import ZipFile
except ImportError:
self.cbzFile = None
cbzFile = ZipFile(self.origFileName)
for f in cbzFile.namelist():
if (f.startswith('__MACOSX') or f.endswith('.DS_Store')):
pass # skip MacOS special files
elif f.endswith('/'):
try:
os.makedirs(self.path+'/'+f)
except:
pass #the dir exists so we are going to extract the images only.
else:
cbzFile.extract(f, self.path)
def extractCBR(self):
try:
import rarfile
except ImportError:
self.cbrFile = None
cbrFile = rarfile.RarFile(self.origFileName)
for f in cbrFile.namelist():
if (f.startswith('__MACOSX') or f.endswith('.DS_Store')):
pass # skip MacOS special files
elif f.endswith('/'):
try:
os.makedirs(self.path+'/'+f)
except:
pass #the dir exists so we are going to extract the images only.
else:
cbrFile.extract(f, self.path)
def extract(self):
if ('.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower()):
self.extractCBR()
elif ('.cbz' == self.filename[1].lower() or '.zip' == self.filename[1].lower()):
self.extractCBZ()
dir = os.listdir(self.path)
if (len(dir) == 1):
import shutil
for f in os.listdir(self.path + "/" + dir[0]):
shutil.move(self.path + "/" + dir[0] + "/" + f,self.path)
os.rmdir(self.path + "/" + dir[0])
@@ -19,113 +19,48 @@
# Changelog
# 1.00 - Initial version
# 1.10 - Added support for CBZ/CBR files
# 1.11 - Added support for ZIP/RAR extensions
# 1.20 - Comic optimizations! Split pages not target-oriented (landscape
# with portrait target or portrait with landscape target), add palette
# and other image optimizations from Mangle.
# WARNING: PIL is required for all image mangling!
#
# Todo:
# - Add gracefully exit for CBR if no rarfile.py and no unrar
# executable are found
# - Improve error reporting
#
# - recurse into dirtree for multiple comics
__version__ = '1.10'
__version__ = '1.20'
import os
import sys
class Unbuffered:
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
class CBxArchive:
def __init__(self, origFileName):
self.cbxexts = ['.cbz', '.cbr']
self.origFileName = origFileName
self.filename = os.path.splitext(origFileName)
self.path = self.filename[0]
def isCbxFile(self):
result = (self.filename[1].lower() in self.cbxexts)
if result == True:
return result
return False
def getPath(self):
return self.path
def extractCBZ(self):
try:
from zipfile import ZipFile
except ImportError:
self.cbzFile = None
cbzFile = ZipFile(self.origFileName)
for f in cbzFile.namelist():
if (f.startswith('__MACOSX') or f.endswith('.DS_Store')):
pass # skip MacOS special files
elif f.endswith('/'):
try:
os.makedirs(self.path+f)
except:
pass #the dir exists so we are going to extract the images only.
else:
cbzFile.extract(f, self.path)
def extractCBR(self):
try:
import rarfile
except ImportError:
self.cbrFile = None
cbrFile = rarfile.RarFile(self.origFileName)
for f in cbrFile.namelist():
if f.endswith('/'):
try:
os.makedirs(self.path+f)
except:
pass #the dir exists so we are going to extract the images only.
else:
cbrFile.extract(f, self.path)
def extract(self):
if ('.cbr' == self.filename[1].lower()):
self.extractCBR()
elif ('.cbz' == self.filename[1].lower()):
self.extractCBZ()
dir = os.listdir(self.path)
if (len(dir) == 1):
import shutil
for f in os.listdir(self.path + "/" + dir[0]):
shutil.move(self.path + "/" + dir[0] + "/" + f,self.path)
os.rmdir(self.path + "/" + dir[0])
import cbxarchive
class HTMLbuilder:
def getResult(self):
if (self.filename[0].startswith('.') or (self.filename[1] != '.png' and self.filename[1] != '.jpg' and self.filename[1] != '.jpeg')):
return None
return self.filename
return getImageFileName(self.file)
def __init__(self, dstdir, file):
self.filename = os.path.splitext(file)
basefilename = self.filename[0]
ext = self.filename[1]
if (basefilename.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg')):
return
htmlfile = dstdir + '/' + basefilename + '.html'
f = open(htmlfile, "w");
f.writelines(["<!DOCTYPE html SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
"<head>\n",
"<title>",basefilename,"</title>\n",
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
"</head>\n",
"<body>\n",
"<div><img src=\"",file,"\" /></div>\n",
"</body>\n",
"</html>"
])
f.close()
self.file = file
filename = getImageFileName(file)
if (filename != None):
htmlfile = dstdir + '/' + filename[0] + '.html'
f = open(htmlfile, "w");
f.writelines(["<!DOCTYPE html SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n",
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n",
"<head>\n",
"<title>",filename[0],"</title>\n",
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
"</head>\n",
"<body>\n",
"<div><img src=\"",file,"\" /></div>\n",
"</body>\n",
"</html>"
])
f.close()
return None
class NCXbuilder:
def __init__(self, dstdir, title):
@@ -151,7 +86,7 @@ class OPFBuilder:
width, height = im.size
imgres = str(width) + "x" + str(height)
except ImportError:
print "Could not load PIL, falling back on default HD"
print "Could not load PIL, falling back on default HD"
imgres = "758x1024"
f = open(opffile, "w");
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
@@ -181,32 +116,65 @@ class OPFBuilder:
f.close()
return
def getImageFileName(file):
filename = os.path.splitext(file)
if (filename[0].startswith('.') or (filename[1].lower() != '.png' and filename[1].lower() != '.jpg' and filename[1].lower() != '.jpeg')):
return None
return filename
def isInFilelist(file,list):
filename = os.path.splitext(file)
seen = False
for item in list:
if filename[0] == item[0]:
seen = True
return seen
if __name__ == "__main__":
sys.stdout=Unbuffered(sys.stdout)
print ('comic2ebook v%(__version__)s. '
'Written 2012 by Ciro Mattia Gonano.' % globals())
if len(sys.argv)<2 or len(sys.argv)>3:
if len(sys.argv)<3 or len(sys.argv)>4:
print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images"
print "Optimized for creating Mobipockets to be read into Kindle Paperwhite"
print "Usage:"
print " %s <dir> <title>" % sys.argv[0]
print " %s <profile> <dir> <title>" % sys.argv[0]
print " <title> is optional"
sys.exit(1)
else:
dir = sys.argv[1]
cbx = CBxArchive(dir)
profile = sys.argv[1]
dir = sys.argv[2]
cbx = cbxarchive.CBxArchive(dir)
if cbx.isCbxFile():
cbx.extract()
dir = cbx.getPath()
if len(sys.argv)==3:
title = sys.argv[2]
if len(sys.argv)==4:
title = sys.argv[3]
else:
title = "comic"
filelist = []
try:
import image
print "Splitting double pages..."
for file in os.listdir(dir):
if (getImageFileName(file) != None):
img = image.ComicPage(dir+'/'+file, profile)
img.splitPage(dir)
for file in os.listdir(dir):
if (getImageFileName(file) != None):
print "Optimizing " + file + " for " + profile
img = image.ComicPage(dir+'/'+file, profile)
img.resizeImage()
img.frameImage()
img.quantizeImage()
img.saveToDir(dir)
except ImportError:
print "Could not load PIL, not optimizing image"
for file in os.listdir(dir):
filename = HTMLbuilder(dir,file).getResult()
if (filename != None):
filelist.append(filename)
if (getImageFileName(file) != None and isInFilelist(file,filelist) == False):
filename = HTMLbuilder(dir,file).getResult()
if (filename != None):
filelist.append(filename)
NCXbuilder(dir,title)
OPFBuilder(dir,title,filelist)
sys.exit(0)
@@ -5,8 +5,18 @@
\f0\fs24 \cf2 \CocoaLigature0 Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>\
\
This script heavily relies on KindleStrip (C) by Paul Durrant and released in public domain (http://www.mobileread.com/forums/showthread.php?t=96903)\
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\
\
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
\
This script heavily relies on KindleStrip (C) by Paul Durrant and released in public domain (http://www.mobileread.com/forums/showthread.php?t=96903)\
Also, you need to have kindlegen v2.7 (with KF8 support) which is downloadable from Amazon website.\
\
This script is released under The MIT License (http://opensource.org/licenses/MIT)\
}
Changelog:\
1.0: first release\
1.10: add CBZ/CBR support to comic2ebook.py\
1.11: add CBZ/CBR support to KindleComicConverter\
1.2: added image page splitting and optimizations\
\
Todo:\
- bundle a script to manipulate images (to get rid of Mangle/E-nki/whatsoever)}
+207
View File
@@ -0,0 +1,207 @@
# Copyright (C) 2010 Alex Yatskov
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from PIL import Image, ImageDraw
class ImageFlags:
Orient = 1 << 0
Resize = 1 << 1
Frame = 1 << 2
Quantize = 1 << 3
Stretch = 1 << 4
class KindleData:
Palette4 = [
0x00, 0x00, 0x00,
0x55, 0x55, 0x55,
0xaa, 0xaa, 0xaa,
0xff, 0xff, 0xff
]
Palette15a = [
0x00, 0x00, 0x00,
0x11, 0x11, 0x11,
0x22, 0x22, 0x22,
0x33, 0x33, 0x33,
0x44, 0x44, 0x44,
0x55, 0x55, 0x55,
0x66, 0x66, 0x66,
0x77, 0x77, 0x77,
0x88, 0x88, 0x88,
0x99, 0x99, 0x99,
0xaa, 0xaa, 0xaa,
0xbb, 0xbb, 0xbb,
0xcc, 0xcc, 0xcc,
0xdd, 0xdd, 0xdd,
0xff, 0xff, 0xff,
]
Palette15b = [
0x00, 0x00, 0x00,
0x11, 0x11, 0x11,
0x22, 0x22, 0x22,
0x33, 0x33, 0x33,
0x44, 0x44, 0x44,
0x55, 0x55, 0x55,
0x77, 0x77, 0x77,
0x88, 0x88, 0x88,
0x99, 0x99, 0x99,
0xaa, 0xaa, 0xaa,
0xbb, 0xbb, 0xbb,
0xcc, 0xcc, 0xcc,
0xdd, 0xdd, 0xdd,
0xee, 0xee, 0xee,
0xff, 0xff, 0xff,
]
Profiles = {
'K1': ((600, 800), Palette4),
'K2': ((600, 800), Palette15a),
'K3': ((600, 800), Palette15a),
'K4': ((600, 800), Palette15b),
'KHD': ((758, 1024), Palette15b),
'KDX': ((824, 1200), Palette15a)
}
class ComicPage:
def __init__(self,source,device):
try:
self.size, self.palette = KindleData.Profiles[device]
except KeyError:
raise RuntimeError('Unexpected output device %s' % device)
try:
self.origFileName = source
self.image = Image.open(source)
except IOError:
raise RuntimeError('Cannot read image file %s' % source)
self.image = self.image.convert('RGB')
def saveToDir(self,targetdir):
filename = os.path.basename(self.origFileName)
print "Saving to " + targetdir + '/' + filename
try:
self.image = self.image.convert('L') # convert to grayscale
self.image.save(targetdir + '/' + filename,"JPEG")
except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e))
def quantizeImage(self):
colors = len(self.palette) / 3
if colors < 256:
palette = self.palette + self.palette[:3] * (256 - colors)
palImg = Image.new('P', (1, 1))
palImg.putpalette(palette)
self.image = self.image.quantize(palette=palImg)
def stretchImage(self):
widthDev, heightDev = self.size
self.image = self.image.resize((widthDev, heightDev), Image.ANTIALIAS)
def resizeImage(self):
widthDev, heightDev = self.size
widthImg, heightImg = self.image.size
if widthImg <= widthDev and heightImg <= heightDev:
return self.image
ratioImg = float(widthImg) / float(heightImg)
ratioWidth = float(widthImg) / float(widthDev)
ratioHeight = float(heightImg) / float(heightDev)
if ratioWidth > ratioHeight:
widthImg = widthDev
heightImg = int(widthDev / ratioImg)
elif ratioWidth < ratioHeight:
heightImg = heightDev
widthImg = int(heightDev * ratioImg)
else:
widthImg, heightImg = self.size
self.image = self.image.resize((widthImg, heightImg), Image.ANTIALIAS)
def orientImage(self):
widthDev, heightDev = self.size
widthImg, heightImg = self.image.size
if (widthImg > heightImg) != (widthDev > heightDev):
self.image = self.image.rotate(90, Image.BICUBIC, True)
def splitPage(self, targetdir, righttoleft=False):
width, height = self.image.size
dstwidth, dstheight = self.size
print "Image is %d x %d" % (width,height)
# only split if origin is not oriented the same as target
if (width > height) != (dstwidth > dstheight):
if (width > height):
# source is landscape, so split by the width
leftbox = (0, 0, width/2, height)
rightbox = (width/2, 0, width, height)
else:
# source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, height/2)
rightbox = (0, height/2, width, height)
filename = os.path.splitext(os.path.basename(self.origFileName))
fileone = targetdir + '/' + filename[0] + '-1' + filename[1]
filetwo = targetdir + '/' + filename[0] + '-2' + filename[1]
try:
if (righttoleft == True):
pageone = self.image.crop(rightbox)
pagetwo = self.image.crop(leftbox)
else:
pageone = self.image.crop(leftbox)
pagetwo = self.image.crop(rightbox)
pageone.save(fileone)
pagetwo.save(filetwo)
os.remove(self.origFileName)
except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e))
return (fileone,filetwo)
return None
def frameImage(self):
foreground = tuple(self.palette[:3])
background = tuple(self.palette[-3:])
widthDev, heightDev = self.size
widthImg, heightImg = self.image.size
pastePt = (
max(0, (widthDev - widthImg) / 2),
max(0, (heightDev - heightImg) / 2)
)
corner1 = (
pastePt[0] - 1,
pastePt[1] - 1
)
corner2 = (
pastePt[0] + widthImg + 1,
pastePt[1] + heightImg + 1
)
imageBg = Image.new(self.image.mode, self.size, background)
imageBg.paste(self.image, pastePt)
draw = ImageDraw.Draw(imageBg)
draw.rectangle([corner1, corner2], outline=foreground)
self.image = imageBg
# for debug purposes (this file is not meant to be called directly
if __name__ == "__main__":
import sys
imgfile = sys.argv[1]
img = ComicPage(imgfile, "KHD")
pages = img.splitPage('temp/',False)
if (pages != None):
print "%s, %s" % pages
sys.exit(0)
img.orientImage()
img.resizeImage()
img.frameImage()
img.quantizeImage()
img.saveToDir("temp/")
sys.exit(0)