diff --git a/README.md b/README.md index 908d5f9..32b25d5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ It also optimizes comic images by: - flat folders - PDF *(extracting only contained JPG images)* -For now the script does not understand folder depth, so it will work on flat folders/archives only. +~~For now the script does not understand folder depth, so it will work on flat folders/archives only.~~ +As of v. 1.50, KCC supports subfolders! ## REQUIREMENTS - `kindlegen` in /usr/local/bin/ @@ -90,6 +91,7 @@ and installed in `/usr/local/bin/` - 1.40 - Added some options for controlling image optimization Further optimization (ImageOps, page numbering cut, autocontrast) - 1.41 - Fixed a serious bug on resizing when img ratio was bigger than device one + - 1.50 - Added subfolder support for multiple chapters. - 2.00 - GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support. ## TODO diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index 0220d67..8d4d428 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -29,6 +29,7 @@ # 1.40 - Added some options for controlling image optimization # Further optimization (ImageOps, page numbering cut, autocontrast) # 1.41 - Fixed a serious bug on resizing when img ratio was bigger than device one +# 1.50 - Support for subfolders # # Todo: # - Add gracefully exit for CBR if no rarfile.py and no unrar @@ -36,85 +37,84 @@ # - Improve error reporting # - recurse into dirtree for multiple comics -__version__ = '1.30' +__version__ = '1.50' import os import sys from optparse import OptionParser import image, cbxarchive, pdfjpgextract -class HTMLbuilder: - - def getResult(self): - return getImageFileName(self.file) - - def __init__(self, dstdir, file): - self.file = file - filename = getImageFileName(file) - if filename is not None: - htmlfile = dstdir + '/' + filename[0] + '.html' - f = open(htmlfile, "w") - f.writelines(["\n", - "\n", - "\n", - "",filename[0],"\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "" - ]) - f.close() - return - -class NCXbuilder: - def __init__(self, dstdir, title): - ncxfile = dstdir + '/content.ncx' - f = open(ncxfile, "w") - f.writelines(["\n", - "\n", - "\n", - "\n\n", - "",title,"\n", - "\n" - ]) +def buildHTML(path,file): + filename = getImageFileName(file) + if filename is not None: + htmlfile = os.path.join(path,filename[0] + '.html') + f = open(htmlfile, "w") + f.writelines(["\n", + "\n", + "\n", + "",filename[0],"\n", + "\n", + "\n", + "\n", + "
\n", + "\n", + "" + ]) f.close() - return + return path,file -class OPFBuilder: - def __init__(self, profile, dstdir, title, filelist): - opffile = dstdir + '/content.opf' - # read the first file resolution - profilelabel, deviceres, palette = image.ProfileData.Profiles[profile] - imgres = str(deviceres[0]) + "x" + str(deviceres[1]) - f = open(opffile, "w") - f.writelines(["\n", - "\n", - "\n", - "",title,"\n", - "en-US\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n"]) - for filename in filelist: - f.write("\n") - for filename in filelist: - if '.png' == filename[1]: - mt = 'image/png' - else: - mt = 'image/jpeg' - f.write("\n") - f.write("\n\n") - for filename in filelist: - f.write("\n") - f.write("\n\n\n\n") - f.close() - return +def buildNCX(dstdir, title): + ncxfile = dstdir + '/toc.ncx' + f = open(ncxfile, "w") + f.writelines(["\n", + "\n", + "\n", + "\n\n", + "",title,"\n", + "\n" + ]) + f.close() + return + +def buildOPF(profile, dstdir, title, filelist): + opffile = dstdir + '/content.opf' + # read the first file resolution + profilelabel, deviceres, palette = image.ProfileData.Profiles[profile] + imgres = str(deviceres[0]) + "x" + str(deviceres[1]) + f = open(opffile, "w") + f.writelines(["\n", + "\n", + "\n", + "",title,"\n", + "en-US\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n"]) + for path in filelist: + folder = path[0].replace(dstdir,'').lstrip('/') + filename = getImageFileName(path[1]) + uniqueid = os.path.join(folder,filename[0]).replace('/','_') + f.write("\n") + #for filename in filelist: + if '.png' == filename[1]: + mt = 'image/png' + else: + mt = 'image/jpeg' + f.write("\n") + f.write("\n\n") + for path in filelist: + folder = path[0].replace(dstdir,'').lstrip('/') + filename = getImageFileName(path[1]) + uniqueid = os.path.join(folder,filename[0]).replace('/','_') + f.write("\n") + f.write("\n\n\n\n") + f.close() + return def getImageFileName(file): filename = os.path.splitext(file) @@ -130,6 +130,78 @@ def isInFilelist(file,list): seen = True return seen +def applyImgOptimization(img): + img.optimizeImage() + img.cropWhiteSpace(10.0) + if options.cutpagenumbers: + img.cutPageNumber() + img.resizeImage(options.upscale,options.stretch) + img.quantizeImage() + + +def dirImgProcess(path): + global options + + for (dirpath, dirnames, filenames) in os.walk(path): + for file in filenames: + if getImageFileName(file) is not None: + if options.verbose: + print "Optimizing " + file + " for " + options.profile + else: + print ".", + img = image.ComicPage(os.path.join(dirpath,file), options.profile) + split = img.splitPage(dirpath, options.righttoleft) + if split is not None: + print "Splitted " + file + img0 = image.ComicPage(split[0],options.profile) + img1 = image.ComicPage(split[1],options.profile) + applyImgOptimization(img0) + img0.saveToDir(dirpath) + applyImgOptimization(img1) + img1.saveToDir(dirpath) + else: + applyImgOptimization(img) + img.saveToDir(dirpath) + +def genEpubStruct(path): + global options + filelist = [] + for (dirpath, dirnames, filenames) in os.walk(path): + for file in filenames: + if getImageFileName(file) is not None: + # put credits at the end + if "credits" in file.lower(): + os.rename(os.path.join(dirpath,file), os.path.join(dirpath,'ZZZ999_'+file)) + file = 'ZZZ999_'+file + filelist.append(buildHTML(dirpath,file)) + #filelist.extend(filenames) + if options.title == 'defaulttitle': + options.title = os.path.basename(path) + buildNCX(path,options.title) + # ensure we're sorting files alphabetically + filelist = sorted(filelist, key=lambda name: (name[0].lower(), name[1].lower())) + buildOPF(options.profile,path,options.title,filelist) + +def getWorkFolder(file): + fname = os.path.splitext(file) + if fname[1].lower() == '.pdf': + pdf = pdfjpgextract.PdfJpgExtract(file) + pdf.extract() + return pdf.getPath() + else: + cbx = cbxarchive.CBxArchive(file) + if cbx.isCbxFile(): + cbx.extract() + return cbx.getPath() + else: + try: + import shutil + if not os.path.isdir(file + "_orig"): + shutil.copytree(file, file + "_orig") + return file + except OSError: + raise + def Copyright(): print ('comic2ebook v%(__version__)s. ' 'Written 2012 by Ciro Mattia Gonano.' % globals()) @@ -140,7 +212,7 @@ def Usage(): parser.print_help() def main(argv=None): - global parser + global parser, options usage = "Usage: %prog [options] comic_file|comic_folder" parser = OptionParser(usage=usage, version=__version__) parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD", @@ -163,68 +235,14 @@ def main(argv=None): if len(args) != 1: parser.print_help() return - dir = args[0] - fname = os.path.splitext(dir) - if fname[1].lower() == '.pdf': - pdf = pdfjpgextract.PdfJpgExtract(dir) - pdf.extract() - dir = pdf.getPath() - else: - cbx = cbxarchive.CBxArchive(dir) - if cbx.isCbxFile(): - cbx.extract() - dir = cbx.getPath() - else: - try: - import shutil - shutil.copytree(dir, dir + "_orig") - #dir = dir + "_orig" - except OSError as exc: - raise - filelist = [] + path = args[0] + path = getWorkFolder(path) if options.imgproc: print "Processing images..." - try: - if options.verbose: - print "Splitting double pages..." - for file in os.listdir(dir): - if getImageFileName(file) is not None: - print ".", - img = image.ComicPage(dir+'/'+file, options.profile) - img.splitPage(dir, options.righttoleft) - for file in os.listdir(dir): - if getImageFileName(file) is not None: - if options.verbose: - print "Optimizing " + file + " for " + options.profile - else: - print ".", - img = image.ComicPage(dir+'/'+file, options.profile) - img.optimizeImage() - img.cropWhiteSpace(10.0) - if options.cutpagenumbers: - img.cutPageNumber() - img.resizeImage(options.upscale,options.stretch) - img.quantizeImage() - img.saveToDir(dir) - except ImportError: - print "Could not load PIL, not optimizing image" + dirImgProcess(path) print "Creating ePub structure..." - for file in os.listdir(dir): - if getImageFileName(file) is not None and isInFilelist(file,filelist) == False: - # put credits at the end - if "credits" in file.lower(): - os.rename(dir+'/'+file, dir+'/ZZZ999_'+file) - file = 'ZZZ999_'+file - filename = HTMLbuilder(dir,file).getResult() - if filename is not None: - filelist.append(filename) - if options.title == 'defaulttitle': - options.title = os.path.basename(dir) - NCXbuilder(dir,options.title) - # ensure we're sorting files alphabetically - filelist = sorted(filelist, key=lambda name: name[0].lower()) - OPFBuilder(options.profile,dir,options.title,filelist) + genEpubStruct(path) if __name__ == "__main__": diff --git a/kcc/image.py b/kcc/image.py index c7406bd..9a889a8 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -118,6 +118,12 @@ class ComicPage: method = Image.ANTIALIAS if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: if not upscale: + # do not upscale but center image in a device-sized image + newImage = Image.new('RGB', (self.size[0], self.size[1]), (255,255,255)) + newImage.paste(self.image, ( + (self.size[0] - self.image.size[0]) / 2, + (self.size[1] - self.image.size[1]) / 2)) + self.image = newImage return self.image else: method = Image.NEAREST @@ -170,7 +176,8 @@ class ComicPage: except IOError as e: raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e)) return fileone,filetwo - return None + else: + return None def frameImage(self): foreground = tuple(self.palette[:3])