mirror of
https://github.com/ciromattia/kcc
synced 2025-12-13 17:56:30 +00:00
Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
Fixed "add folders" from GUI.
This commit is contained in:
10
README.md
10
README.md
@@ -12,8 +12,8 @@ It also optimizes comic images by:
|
|||||||
|
|
||||||
## BINARY RELEASES
|
## BINARY RELEASES
|
||||||
You can find the latest released binary at the following links:
|
You can find the latest released binary at the following links:
|
||||||
- OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.3.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.3.zip)
|
- OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.4.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.4.zip)
|
||||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.3.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.3.zip)
|
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.4.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.4.zip)
|
||||||
- Linux: just download sourcecode and launch `python kcc.py` *(provided you have Python and Pillow installed)*
|
- Linux: just download sourcecode and launch `python kcc.py` *(provided you have Python and Pillow installed)*
|
||||||
|
|
||||||
## INPUT FORMATS
|
## INPUT FORMATS
|
||||||
@@ -40,6 +40,8 @@ As of v. 1.50, KCC supports subfolders!
|
|||||||
### GUI
|
### GUI
|
||||||
|
|
||||||
Should be pretty self-explanatory, just keep in mind that it's still in development ;)
|
Should be pretty self-explanatory, just keep in mind that it's still in development ;)
|
||||||
|
While working it seems frozen, I'll try to fix the aesthetics later.
|
||||||
|
Conversion being done, you should find an .epub and a .mobi files alongside the original input file (same directory)
|
||||||
|
|
||||||
### Standalone `comic2ebook.py` usage:
|
### Standalone `comic2ebook.py` usage:
|
||||||
|
|
||||||
@@ -104,7 +106,9 @@ and installed in a directory reachable by your PATH (e.g. `/usr/local/bin/` or `
|
|||||||
- 2.1: Added basic error reporting
|
- 2.1: Added basic error reporting
|
||||||
- 2.2: Added (valid!) ePub 2.0 output
|
- 2.2: Added (valid!) ePub 2.0 output
|
||||||
Rename .zip files to .cbz to avoid overwriting
|
Rename .zip files to .cbz to avoid overwriting
|
||||||
- 2.3: Fixed win32 ePub generation, folder handling, filenames with spaces and subfolders.
|
- 2.3: Fixed win32 ePub generation, folder handling, filenames with spaces and subfolders
|
||||||
|
- 2.4: Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||||
|
Fixed "add folders" from GUI.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- Add gracefully exit for CBR if no rarfile.py and no unrar executable are found
|
- Add gracefully exit for CBR if no rarfile.py and no unrar executable are found
|
||||||
|
|||||||
2
kcc.py
2
kcc.py
@@ -16,7 +16,7 @@
|
|||||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
__version__ = '2.3'
|
__version__ = '2.4'
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|||||||
@@ -25,35 +25,28 @@ class CBxArchive:
|
|||||||
self.cbxexts = ['.zip','.cbz','.rar','.cbr']
|
self.cbxexts = ['.zip','.cbz','.rar','.cbr']
|
||||||
self.origFileName = origFileName
|
self.origFileName = origFileName
|
||||||
self.filename = os.path.splitext(origFileName)
|
self.filename = os.path.splitext(origFileName)
|
||||||
self.path = self.filename[0]
|
|
||||||
|
|
||||||
def isCbxFile(self):
|
def isCbxFile(self):
|
||||||
result = (self.filename[1].lower() in self.cbxexts)
|
return self.filename[1].lower() in self.cbxexts
|
||||||
if result == True:
|
|
||||||
return result
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getPath(self):
|
def extractCBZ(self,targetdir):
|
||||||
return self.path
|
|
||||||
|
|
||||||
def extractCBZ(self):
|
|
||||||
try:
|
try:
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
except ImportError:
|
except ImportError:
|
||||||
self.cbzFile = None
|
self.cbzFile = None
|
||||||
cbzFile = ZipFile(self.origFileName)
|
cbzFile = ZipFile(self.origFileName)
|
||||||
for f in cbzFile.namelist():
|
for f in cbzFile.namelist():
|
||||||
if (f.startswith('__MACOSX') or f.endswith('.DS_Store')):
|
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
||||||
pass # skip MacOS special files
|
pass # skip MacOS special files
|
||||||
elif f.endswith('/'):
|
elif f.endswith('/'):
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.path+'/'+f)
|
os.makedirs(os.path.join(targetdir,f))
|
||||||
except:
|
except:
|
||||||
pass #the dir exists so we are going to extract the images only.
|
pass #the dir exists so we are going to extract the images only.
|
||||||
else:
|
else:
|
||||||
cbzFile.extract(f, self.path)
|
cbzFile.extract(f, targetdir)
|
||||||
|
|
||||||
def extractCBR(self):
|
def extractCBR(self,targetdir):
|
||||||
try:
|
try:
|
||||||
import rarfile
|
import rarfile
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -61,24 +54,25 @@ class CBxArchive:
|
|||||||
return
|
return
|
||||||
cbrFile = rarfile.RarFile(self.origFileName)
|
cbrFile = rarfile.RarFile(self.origFileName)
|
||||||
for f in cbrFile.namelist():
|
for f in cbrFile.namelist():
|
||||||
if (f.startswith('__MACOSX') or f.endswith('.DS_Store')):
|
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
||||||
pass # skip MacOS special files
|
pass # skip MacOS special files
|
||||||
elif f.endswith('/'):
|
elif f.endswith('/'):
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.path+'/'+f)
|
os.makedirs(os.path.join(targetdir,f))
|
||||||
except:
|
except:
|
||||||
pass #the dir exists so we are going to extract the images only.
|
pass #the dir exists so we are going to extract the images only.
|
||||||
else:
|
else:
|
||||||
cbrFile.extract(f, self.path)
|
cbrFile.extract(f, targetdir)
|
||||||
|
|
||||||
def extract(self):
|
def extract(self,targetdir):
|
||||||
if ('.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower()):
|
if '.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower():
|
||||||
self.extractCBR()
|
self.extractCBR(targetdir)
|
||||||
elif ('.cbz' == self.filename[1].lower() or '.zip' == self.filename[1].lower()):
|
elif '.cbz' == self.filename[1].lower() or '.zip' == self.filename[1].lower():
|
||||||
self.extractCBZ()
|
self.extractCBZ(targetdir)
|
||||||
dir = os.listdir(self.path)
|
dir = os.listdir(targetdir)
|
||||||
if (len(dir) == 1):
|
if len(dir) == 1 and os.path.isdir(os.path.join(targetdir,dir[0])):
|
||||||
import shutil
|
import shutil
|
||||||
for f in os.listdir(self.path + "/" + dir[0]):
|
for f in os.listdir(os.path.join(targetdir,dir[0])):
|
||||||
shutil.move(self.path + "/" + dir[0] + "/" + f,self.path)
|
shutil.move(os.path.join(targetdir,dir[0],f),targetdir)
|
||||||
os.rmdir(self.path + "/" + dir[0])
|
os.rmdir(os.path.join(targetdir,dir[0]))
|
||||||
|
return targetdir
|
||||||
|
|||||||
@@ -16,39 +16,20 @@
|
|||||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
# PERFORMANCE OF THIS SOFTWARE.
|
# PERFORMANCE OF THIS SOFTWARE.
|
||||||
#
|
#
|
||||||
# Changelog
|
__version__ = '2.4'
|
||||||
# 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!
|
|
||||||
# 1.30 - Fixed an issue in OPF generation for device resolution
|
|
||||||
# Reworked options system (call with -h option to get the inline help)
|
|
||||||
# 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
|
|
||||||
# executable are found
|
|
||||||
# - Improve error reporting
|
|
||||||
|
|
||||||
__version__ = '2.3'
|
|
||||||
__license__ = 'ISC'
|
__license__ = 'ISC'
|
||||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, sys
|
import os, sys, tempfile
|
||||||
from shutil import move,copyfile,make_archive,rmtree
|
from shutil import move,copyfile,copytree,rmtree,make_archive
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
import image, cbxarchive, pdfjpgextract
|
import image, cbxarchive, pdfjpgextract
|
||||||
|
|
||||||
def buildHTML(path,file):
|
def buildHTML(path,file):
|
||||||
filename = getImageFileName(file)
|
filename = getImageFileName(file)
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
|
htmlpath = ''
|
||||||
postfix = ''
|
postfix = ''
|
||||||
backref = 1
|
backref = 1
|
||||||
head = path
|
head = path
|
||||||
@@ -203,9 +184,9 @@ def dirImgProcess(path):
|
|||||||
if options.verbose:
|
if options.verbose:
|
||||||
print "Splitted " + file
|
print "Splitted " + file
|
||||||
img0 = image.ComicPage(split[0],options.profile)
|
img0 = image.ComicPage(split[0],options.profile)
|
||||||
img1 = image.ComicPage(split[1],options.profile)
|
|
||||||
applyImgOptimization(img0)
|
applyImgOptimization(img0)
|
||||||
img0.saveToDir(dirpath)
|
img0.saveToDir(dirpath)
|
||||||
|
img1 = image.ComicPage(split[1],options.profile)
|
||||||
applyImgOptimization(img1)
|
applyImgOptimization(img1)
|
||||||
img1.saveToDir(dirpath)
|
img1.saveToDir(dirpath)
|
||||||
else:
|
else:
|
||||||
@@ -238,35 +219,31 @@ def genEpubStruct(path):
|
|||||||
if cover is None:
|
if cover is None:
|
||||||
cover = os.path.join(filelist[-1][0],'cover' + getImageFileName(filelist[-1][1])[1])
|
cover = os.path.join(filelist[-1][0],'cover' + getImageFileName(filelist[-1][1])[1])
|
||||||
copyfile(os.path.join(filelist[-1][0],filelist[-1][1]), cover)
|
copyfile(os.path.join(filelist[-1][0],filelist[-1][1]), cover)
|
||||||
if options.title == 'defaulttitle':
|
|
||||||
options.title = os.path.basename(path)
|
|
||||||
buildNCX(path,options.title,chapterlist)
|
buildNCX(path,options.title,chapterlist)
|
||||||
# ensure we're sorting files alphabetically
|
# ensure we're sorting files alphabetically
|
||||||
filelist = sorted(filelist, key=lambda name: (name[0].lower(), name[1].lower()))
|
filelist = sorted(filelist, key=lambda name: (name[0].lower(), name[1].lower()))
|
||||||
buildOPF(options.profile,path,options.title,filelist,cover)
|
buildOPF(options.profile,path,options.title,filelist,cover)
|
||||||
|
|
||||||
def getWorkFolder(file):
|
def getWorkFolder(file):
|
||||||
|
workdir = tempfile.mkdtemp()
|
||||||
fname = os.path.splitext(file)
|
fname = os.path.splitext(file)
|
||||||
if fname[1].lower() == '.pdf':
|
if os.path.isdir(file):
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
os.rmdir(workdir) # needed for copytree() fails if dst already exists
|
||||||
|
copytree(file, workdir)
|
||||||
|
path = workdir
|
||||||
|
except OSError:
|
||||||
|
raise
|
||||||
|
elif fname[1].lower() == '.pdf':
|
||||||
pdf = pdfjpgextract.PdfJpgExtract(file)
|
pdf = pdfjpgextract.PdfJpgExtract(file)
|
||||||
pdf.extract()
|
path = pdf.extract(workdir)
|
||||||
path = pdf.getPath()
|
|
||||||
else:
|
else:
|
||||||
if fname[1].lower() == '.zip':
|
|
||||||
move(file,fname[0] + '.cbz')
|
|
||||||
file = fname[0] + '.cbz'
|
|
||||||
cbx = cbxarchive.CBxArchive(file)
|
cbx = cbxarchive.CBxArchive(file)
|
||||||
if cbx.isCbxFile():
|
if cbx.isCbxFile():
|
||||||
cbx.extract()
|
path = cbx.extract(workdir)
|
||||||
path = cbx.getPath()
|
|
||||||
else:
|
else:
|
||||||
try:
|
raise TypeError
|
||||||
import shutil
|
|
||||||
if not os.path.isdir(file + "_orig"):
|
|
||||||
shutil.copytree(file, file + "_orig")
|
|
||||||
path = file
|
|
||||||
except OSError:
|
|
||||||
raise
|
|
||||||
move(path,path + "_temp")
|
move(path,path + "_temp")
|
||||||
move(path + "_temp",os.path.join(path,'OEBPS','Images'))
|
move(path + "_temp",os.path.join(path,'OEBPS','Images'))
|
||||||
return path
|
return path
|
||||||
@@ -304,18 +281,23 @@ def main(argv=None):
|
|||||||
if len(args) != 1:
|
if len(args) != 1:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
return
|
return
|
||||||
path = args[0]
|
path = getWorkFolder(args[0])
|
||||||
path = getWorkFolder(path)
|
if options.title == 'defaulttitle':
|
||||||
|
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||||
if options.imgproc:
|
if options.imgproc:
|
||||||
print "Processing images..."
|
print "Processing images..."
|
||||||
dirImgProcess(path + "/OEBPS/Images/")
|
dirImgProcess(path + "/OEBPS/Images/")
|
||||||
print "Creating ePub structure..."
|
print "Creating ePub structure..."
|
||||||
genEpubStruct(path)
|
genEpubStruct(path)
|
||||||
# actually zip the ePub
|
# actually zip the ePub
|
||||||
make_archive(path,'zip',path)
|
if os.path.isdir(args[0]):
|
||||||
move(path + '.zip', path + '.epub')
|
epubpath = args[0] + '.epub'
|
||||||
|
else:
|
||||||
|
epubpath = os.path.splitext(args[0])[0] + '.epub'
|
||||||
|
make_archive(os.path.join(path,'comic'),'zip',path)
|
||||||
|
move(os.path.join(path,'comic') + '.zip', epubpath)
|
||||||
rmtree(path)
|
rmtree(path)
|
||||||
epub_path = path + '.epub'
|
return epubpath
|
||||||
|
|
||||||
def getEpubPath():
|
def getEpubPath():
|
||||||
global epub_path
|
global epub_path
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ class MainWindow:
|
|||||||
self.refresh_list()
|
self.refresh_list()
|
||||||
|
|
||||||
def open_folder(self):
|
def open_folder(self):
|
||||||
self.filelist = tkFileDialog.askdirectory(title="Choose a folder...")
|
f = tkFileDialog.askdirectory(title="Choose a folder...")
|
||||||
|
self.filelist.extend([f])
|
||||||
self.refresh_list()
|
self.refresh_list()
|
||||||
|
|
||||||
def refresh_list(self):
|
def refresh_list(self):
|
||||||
@@ -146,8 +147,7 @@ class MainWindow:
|
|||||||
try:
|
try:
|
||||||
subargv = list(argv)
|
subargv = list(argv)
|
||||||
subargv.append(entry)
|
subargv.append(entry)
|
||||||
comic2ebook.main(subargv)
|
epub_path = comic2ebook.main(subargv)
|
||||||
epub_path = comic2ebook.getEpubPath()
|
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
tkMessageBox.showerror('Error comic2ebook', "Error on file %s:\n%s" % (subargv[-1], str(err)))
|
tkMessageBox.showerror('Error comic2ebook', "Error on file %s:\n%s" % (subargv[-1], str(err)))
|
||||||
errors = True
|
errors = True
|
||||||
|
|||||||
20
kcc/image.py
20
kcc/image.py
@@ -110,10 +110,9 @@ class ComicPage:
|
|||||||
|
|
||||||
def saveToDir(self,targetdir):
|
def saveToDir(self,targetdir):
|
||||||
filename = os.path.basename(self.origFileName)
|
filename = os.path.basename(self.origFileName)
|
||||||
#print "Saving to " + targetdir + '/' + filename
|
|
||||||
try:
|
try:
|
||||||
self.image = self.image.convert('L') # convert to grayscale
|
self.image = self.image.convert('L') # convert to grayscale
|
||||||
self.image.save(targetdir + '/' + filename,"JPEG")
|
self.image.save(os.path.join(targetdir,filename),"JPEG")
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e))
|
raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e))
|
||||||
|
|
||||||
@@ -133,11 +132,9 @@ class ComicPage:
|
|||||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||||
if not upscale:
|
if not upscale:
|
||||||
# do not upscale but center image in a device-sized image
|
# do not upscale but center image in a device-sized image
|
||||||
newImage = Image.new('RGB', (self.size[0], self.size[1]), (255,255,255))
|
borderw = (self.size[0] - self.image.size[0]) / 2
|
||||||
newImage.paste(self.image, (
|
borderh = (self.size[1] - self.image.size[1]) / 2
|
||||||
(self.size[0] - self.image.size[0]) / 2,
|
self.image = ImageOps.expand(self.image, border=(borderw,borderh), fill='white')
|
||||||
(self.size[1] - self.image.size[1]) / 2))
|
|
||||||
self.image = newImage
|
|
||||||
return self.image
|
return self.image
|
||||||
else:
|
else:
|
||||||
method = Image.NEAREST
|
method = Image.NEAREST
|
||||||
@@ -149,14 +146,10 @@ class ComicPage:
|
|||||||
ratioDev = float(self.size[0]) / float(self.size[1])
|
ratioDev = float(self.size[0]) / float(self.size[1])
|
||||||
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
|
||||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||||
newImage = Image.new('RGB', (self.image.size[0] + diff, self.image.size[1]), (255,255,255))
|
self.image = ImageOps.expand(self.image, border=(diff/2,0), fill='white')
|
||||||
newImage.paste(self.image, (diff / 2, 0, diff / 2 + self.image.size[0], self.image.size[1]))
|
|
||||||
self.image = newImage
|
|
||||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
||||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
||||||
newImage = Image.new('RGB', (self.image.size[0], self.image.size[1] + diff), (255,255,255))
|
self.image = ImageOps.expand(self.image, border=(0,diff/2), fill='white')
|
||||||
newImage.paste(self.image, (0, diff / 2, self.image.size[0], diff / 2 + self.image.size[1]))
|
|
||||||
self.image = newImage
|
|
||||||
self.image = ImageOps.fit(self.image, self.size, method = method, centering = (0.5,0.5))
|
self.image = ImageOps.fit(self.image, self.size, method = method, centering = (0.5,0.5))
|
||||||
return self.image
|
return self.image
|
||||||
|
|
||||||
@@ -338,4 +331,3 @@ class ComicPage:
|
|||||||
if i==5:
|
if i==5:
|
||||||
draw.rectangle([(widthImg/2-1,heightImg-5), (widthImg/2+1,heightImg)],outline=black,fill=notch_colour)
|
draw.rectangle([(widthImg/2-1,heightImg-5), (widthImg/2+1,heightImg)],outline=black,fill=notch_colour)
|
||||||
return self.image
|
return self.image
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user