IMP: Updated CT to a newer version - changed to use rarfile, fixes size invalid errors with some cbr's encountered during conversions, IMP: Search and Post-Processing Queues now added in order to queue up all searches/post-processing in sequence instead of loading lists and iterating through it and encountering various lock-related errors, IMP: In most cases, will now post-process directly against comicid/issueid/storyarcid instead of relying on relationship via nzb/torrent, FIX: realigned some checkboxes on series detail page, FIX: in configuration, indicated that ComicRN cannot be used when Completed Download Handling is enabled for a client now, FIX: Fix for issues named as 'special' that are part of the annuals section, but would not work in various portions (also will allow for naming differences in the future hopefully), FIX: Will now trap error properly when file cannot be located during dupecheck so that post-processing can continue without error, FIX: Fixed newznab test option on configuration screen so that it will return the correct response (previous was an invalid api endpoint), FIX: When retrieving image for storyarcs would error when no image was present and stop the process, FIX: accounted for some incorrect newznab responses that would return a non-api endpoint when retrieving via rss, which resulted in invalid nzbs, FIX: in some cases, weekly pull snatches would not update properly on the weekly page due to some db values not being written
2018-06-14 17:07:39 +00:00
|
|
|
"""A class to represent a single comic, be it file or folder of images"""
|
|
|
|
|
|
|
|
# Copyright 2012-2014 Anthony Beville
|
|
|
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
import zipfile
|
|
|
|
import os
|
|
|
|
import struct
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
import subprocess
|
|
|
|
import platform
|
|
|
|
import ctypes
|
|
|
|
import time
|
|
|
|
import StringIO
|
|
|
|
|
|
|
|
from natsort import natsorted
|
2019-01-14 14:46:48 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
site_root = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
parent_root = os.path.abspath(os.path.join(site_root, os.pardir))
|
|
|
|
lib_path = os.path.abspath(os.path.join(parent_root, os.pardir))
|
|
|
|
if lib_path not in sys.path:
|
|
|
|
sys.path.append(lib_path)
|
|
|
|
from rarfile import rarfile
|
|
|
|
except:
|
|
|
|
from lib.rarfile import rarfile
|
IMP: Updated CT to a newer version - changed to use rarfile, fixes size invalid errors with some cbr's encountered during conversions, IMP: Search and Post-Processing Queues now added in order to queue up all searches/post-processing in sequence instead of loading lists and iterating through it and encountering various lock-related errors, IMP: In most cases, will now post-process directly against comicid/issueid/storyarcid instead of relying on relationship via nzb/torrent, FIX: realigned some checkboxes on series detail page, FIX: in configuration, indicated that ComicRN cannot be used when Completed Download Handling is enabled for a client now, FIX: Fix for issues named as 'special' that are part of the annuals section, but would not work in various portions (also will allow for naming differences in the future hopefully), FIX: Will now trap error properly when file cannot be located during dupecheck so that post-processing can continue without error, FIX: Fixed newznab test option on configuration screen so that it will return the correct response (previous was an invalid api endpoint), FIX: When retrieving image for storyarcs would error when no image was present and stop the process, FIX: accounted for some incorrect newznab responses that would return a non-api endpoint when retrieving via rss, which resulted in invalid nzbs, FIX: in some cases, weekly pull snatches would not update properly on the weekly page due to some db values not being written
2018-06-14 17:07:39 +00:00
|
|
|
|
|
|
|
if platform.system() == "Windows":
|
|
|
|
import _subprocess
|
|
|
|
|
|
|
|
try:
|
|
|
|
import Image
|
|
|
|
pil_available = True
|
|
|
|
except ImportError:
|
|
|
|
pil_available = False
|
|
|
|
|
|
|
|
from comicinfoxml import ComicInfoXml
|
|
|
|
from comicbookinfo import ComicBookInfo
|
|
|
|
from comet import CoMet
|
|
|
|
from genericmetadata import GenericMetadata, PageType
|
|
|
|
from filenameparser import FileNameParser
|
|
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, os.path.abspath("."))
|
|
|
|
|
|
|
|
class MetaDataStyle:
|
|
|
|
CBI = 0
|
|
|
|
CIX = 1
|
|
|
|
COMET = 2
|
|
|
|
name = ['ComicBookLover', 'ComicRack', 'CoMet']
|
|
|
|
|
|
|
|
|
|
|
|
class ZipArchiver:
|
|
|
|
|
|
|
|
"""ZIP implementation"""
|
|
|
|
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
def getArchiveComment(self):
|
|
|
|
zf = zipfile.ZipFile(self.path, 'r')
|
|
|
|
comment = zf.comment
|
|
|
|
zf.close()
|
|
|
|
return comment
|
|
|
|
|
|
|
|
def setArchiveComment(self, comment):
|
|
|
|
return self.writeZipComment(self.path, comment)
|
|
|
|
|
|
|
|
def readArchiveFile(self, archive_file):
|
|
|
|
data = ""
|
|
|
|
zf = zipfile.ZipFile(self.path, 'r')
|
|
|
|
|
|
|
|
try:
|
|
|
|
data = zf.read(archive_file)
|
|
|
|
except zipfile.BadZipfile as e:
|
|
|
|
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(
|
|
|
|
e, self.path, archive_file)
|
|
|
|
zf.close()
|
|
|
|
raise IOError
|
|
|
|
except Exception as e:
|
|
|
|
zf.close()
|
|
|
|
print >> sys.stderr, u"bad zipfile [{0}]: {1} :: {2}".format(
|
|
|
|
e, self.path, archive_file)
|
|
|
|
raise IOError
|
|
|
|
finally:
|
|
|
|
zf.close()
|
|
|
|
return data
|
|
|
|
|
|
|
|
def removeArchiveFile(self, archive_file):
|
|
|
|
try:
|
|
|
|
self.rebuildZipFile([archive_file])
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def writeArchiveFile(self, archive_file, data):
|
|
|
|
# At the moment, no other option but to rebuild the whole
|
|
|
|
# zip archive w/o the indicated file. Very sucky, but maybe
|
|
|
|
# another solution can be found
|
|
|
|
try:
|
|
|
|
self.rebuildZipFile([archive_file])
|
|
|
|
|
|
|
|
# now just add the archive file as a new one
|
|
|
|
zf = zipfile.ZipFile(
|
|
|
|
self.path,
|
|
|
|
mode='a',
|
|
|
|
compression=zipfile.ZIP_DEFLATED)
|
|
|
|
zf.writestr(archive_file, data)
|
|
|
|
zf.close()
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def getArchiveFilenameList(self):
|
|
|
|
try:
|
|
|
|
zf = zipfile.ZipFile(self.path, 'r')
|
|
|
|
namelist = zf.namelist()
|
|
|
|
zf.close()
|
|
|
|
return namelist
|
|
|
|
except Exception as e:
|
|
|
|
print >> sys.stderr, u"Unable to get zipfile list [{0}]: {1}".format(
|
|
|
|
e, self.path)
|
|
|
|
return []
|
|
|
|
|
|
|
|
def rebuildZipFile(self, exclude_list):
|
|
|
|
"""Zip helper func
|
|
|
|
|
|
|
|
This recompresses the zip archive, without the files in the exclude_list
|
|
|
|
"""
|
|
|
|
|
|
|
|
# print ">> sys.stderr, Rebuilding zip {0} without {1}".format(
|
|
|
|
# self.path, exclude_list )
|
|
|
|
|
|
|
|
# generate temp file
|
|
|
|
tmp_fd, tmp_name = tempfile.mkstemp(dir=os.path.dirname(self.path))
|
|
|
|
os.close(tmp_fd)
|
|
|
|
|
|
|
|
zin = zipfile.ZipFile(self.path, 'r')
|
|
|
|
zout = zipfile.ZipFile(tmp_name, 'w')
|
|
|
|
for item in zin.infolist():
|
|
|
|
buffer = zin.read(item.filename)
|
|
|
|
if (item.filename not in exclude_list):
|
|
|
|
zout.writestr(item, buffer)
|
|
|
|
|
|
|
|
# preserve the old comment
|
|
|
|
zout.comment = zin.comment
|
|
|
|
|
|
|
|
zout.close()
|
|
|
|
zin.close()
|
|
|
|
|
|
|
|
# replace with the new file
|
|
|
|
os.remove(self.path)
|
|
|
|
os.rename(tmp_name, self.path)
|
|
|
|
|
|
|
|
def writeZipComment(self, filename, comment):
|
|
|
|
"""
|
|
|
|
This is a custom function for writing a comment to a zip file,
|
|
|
|
since the built-in one doesn't seem to work on Windows and Mac OS/X
|
|
|
|
|
|
|
|
Fortunately, the zip comment is at the end of the file, and it's
|
|
|
|
easy to manipulate. See this website for more info:
|
|
|
|
see: http://en.wikipedia.org/wiki/Zip_(file_format)#Structure
|
|
|
|
"""
|
|
|
|
|
|
|
|
# get file size
|
|
|
|
statinfo = os.stat(filename)
|
|
|
|
file_length = statinfo.st_size
|
|
|
|
|
|
|
|
try:
|
|
|
|
fo = open(filename, "r+b")
|
|
|
|
|
|
|
|
# the starting position, relative to EOF
|
|
|
|
pos = -4
|
|
|
|
|
|
|
|
found = False
|
|
|
|
value = bytearray()
|
|
|
|
|
|
|
|
# walk backwards to find the "End of Central Directory" record
|
|
|
|
while (not found) and (-pos != file_length):
|
|
|
|
# seek, relative to EOF
|
|
|
|
fo.seek(pos, 2)
|
|
|
|
|
|
|
|
value = fo.read(4)
|
|
|
|
|
|
|
|
# look for the end of central directory signature
|
|
|
|
if bytearray(value) == bytearray([0x50, 0x4b, 0x05, 0x06]):
|
|
|
|
found = True
|
|
|
|
else:
|
|
|
|
# not found, step back another byte
|
|
|
|
pos = pos - 1
|
|
|
|
# print pos,"{1} int: {0:x}".format(bytearray(value)[0], value)
|
|
|
|
|
|
|
|
if found:
|
|
|
|
|
|
|
|
# now skip forward 20 bytes to the comment length word
|
|
|
|
pos += 20
|
|
|
|
fo.seek(pos, 2)
|
|
|
|
|
|
|
|
# Pack the length of the comment string
|
|
|
|
format = "H" # one 2-byte integer
|
|
|
|
comment_length = struct.pack(
|
|
|
|
format,
|
|
|
|
len(comment)) # pack integer in a binary string
|
|
|
|
|
|
|
|
# write out the length
|
|
|
|
fo.write(comment_length)
|
|
|
|
fo.seek(pos + 2, 2)
|
|
|
|
|
|
|
|
# write out the comment itself
|
|
|
|
fo.write(comment)
|
|
|
|
fo.truncate()
|
|
|
|
fo.close()
|
|
|
|
else:
|
|
|
|
raise Exception('Failed to write comment to zip file!')
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def copyFromArchive(self, otherArchive):
|
|
|
|
"""Replace the current zip with one copied from another archive"""
|
|
|
|
|
|
|
|
try:
|
|
|
|
zout = zipfile.ZipFile(self.path, 'w')
|
|
|
|
for fname in otherArchive.getArchiveFilenameList():
|
|
|
|
data = otherArchive.readArchiveFile(fname)
|
|
|
|
if data is not None:
|
|
|
|
zout.writestr(fname, data)
|
|
|
|
zout.close()
|
|
|
|
|
|
|
|
# preserve the old comment
|
|
|
|
comment = otherArchive.getArchiveComment()
|
|
|
|
if comment is not None:
|
|
|
|
if not self.writeZipComment(self.path, comment):
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
print >> sys.stderr, u"Error while copying to {0}: {1}".format(
|
|
|
|
self.path, e)
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
#------------------------------------------
|
|
|
|
|
|
|
|
class RarArchiver:
|
|
|
|
|
|
|
|
"""RAR implementation"""
|
|
|
|
|
|
|
|
devnull = None
|
|
|
|
|
|
|
|
def __init__(self, path, rar_exe_path):
|
|
|
|
self.path = path
|
|
|
|
self.rar_exe_path = rar_exe_path
|
|
|
|
|
|
|
|
if RarArchiver.devnull is None:
|
|
|
|
RarArchiver.devnull = open(os.devnull, "w")
|
|
|
|
|
|
|
|
# windows only, keeps the cmd.exe from popping up
|
|
|
|
if platform.system() == "Windows":
|
|
|
|
self.startupinfo = subprocess.STARTUPINFO()
|
|
|
|
self.startupinfo.dwFlags |= _subprocess.STARTF_USESHOWWINDOW
|
|
|
|
else:
|
|
|
|
self.startupinfo = None
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
# RarArchiver.devnull.close()
|
|
|
|
pass
|
|
|
|
|
|
|
|
def getArchiveComment(self):
|
|
|
|
|
|
|
|
rarc = self.getRARObj()
|
|
|
|
return rarc.comment
|
|
|
|
|
|
|
|
def setArchiveComment(self, comment):
|
|
|
|
|
|
|
|
if self.rar_exe_path is not None:
|
|
|
|
try:
|
|
|
|
# write comment to temp file
|
|
|
|
tmp_fd, tmp_name = tempfile.mkstemp()
|
|
|
|
f = os.fdopen(tmp_fd, 'w+b')
|
|
|
|
f.write(comment)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
working_dir = os.path.dirname(os.path.abspath(self.path))
|
|
|
|
|
|
|
|
# use external program to write comment to Rar archive
|
|
|
|
subprocess.call([self.rar_exe_path,
|
|
|
|
'c',
|
|
|
|
'-w' + working_dir,
|
|
|
|
'-c-',
|
|
|
|
'-z' + tmp_name,
|
|
|
|
self.path],
|
|
|
|
startupinfo=self.startupinfo,
|
|
|
|
stdout=RarArchiver.devnull)
|
|
|
|
|
|
|
|
if platform.system() == "Darwin":
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
os.remove(tmp_name)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def readArchiveFile(self, archive_file):
|
|
|
|
|
|
|
|
# Make sure to escape brackets, since some funky stuff is going on
|
|
|
|
# underneath with "fnmatch"
|
|
|
|
#archive_file = archive_file.replace("[", '[[]')
|
|
|
|
entries = []
|
|
|
|
|
|
|
|
rarc = self.getRARObj()
|
|
|
|
|
|
|
|
tries = 0
|
|
|
|
while tries < 7:
|
|
|
|
try:
|
|
|
|
tries = tries + 1
|
|
|
|
#tmp_folder = tempfile.mkdtemp()
|
|
|
|
#tmp_file = os.path.join(tmp_folder, archive_file)
|
|
|
|
#rarc.extract(archive_file, tmp_folder)
|
|
|
|
data = rarc.open(archive_file).read()
|
|
|
|
#data = open(tmp_file).read()
|
|
|
|
entries = [(rarc.getinfo(archive_file), data)]
|
|
|
|
|
|
|
|
#shutil.rmtree(tmp_folder, ignore_errors=True)
|
|
|
|
|
|
|
|
#entries = rarc.read_files( archive_file )
|
|
|
|
|
|
|
|
if entries[0][0].file_size != len(entries[0][1]):
|
|
|
|
print >> sys.stderr, u"readArchiveFile(): [file is not expected size: {0} vs {1}] {2}:{3} [attempt # {4}]".format(
|
|
|
|
entries[0][0].file_size, len(
|
|
|
|
entries[0][1]), self.path, archive_file, tries)
|
|
|
|
continue
|
|
|
|
|
|
|
|
except (OSError, IOError) as e:
|
|
|
|
print >> sys.stderr, u"readArchiveFile(): [{0}] {1}:{2} attempt#{3}".format(
|
|
|
|
str(e), self.path, archive_file, tries)
|
|
|
|
time.sleep(1)
|
|
|
|
except Exception as e:
|
|
|
|
print >> sys.stderr, u"Unexpected exception in readArchiveFile(): [{0}] for {1}:{2} attempt#{3}".format(
|
|
|
|
str(e), self.path, archive_file, tries)
|
|
|
|
break
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Success"
|
|
|
|
# entries is a list of of tuples: ( rarinfo, filedata)
|
|
|
|
if tries > 1:
|
|
|
|
print >> sys.stderr, u"Attempted read_files() {0} times".format(
|
|
|
|
tries)
|
|
|
|
if (len(entries) == 1):
|
|
|
|
return entries[0][1]
|
|
|
|
else:
|
|
|
|
raise IOError
|
|
|
|
|
|
|
|
raise IOError
|
|
|
|
|
|
|
|
def writeArchiveFile(self, archive_file, data):
|
|
|
|
|
|
|
|
if self.rar_exe_path is not None:
|
|
|
|
try:
|
|
|
|
tmp_folder = tempfile.mkdtemp()
|
|
|
|
|
|
|
|
tmp_file = os.path.join(tmp_folder, archive_file)
|
|
|
|
|
|
|
|
working_dir = os.path.dirname(os.path.abspath(self.path))
|
|
|
|
|
|
|
|
# TODO: will this break if 'archive_file' is in a subfolder. i.e. "foo/bar.txt"
|
|
|
|
# will need to create the subfolder above, I guess...
|
|
|
|
f = open(tmp_file, 'w')
|
|
|
|
f.write(data)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
# use external program to write file to Rar archive
|
|
|
|
subprocess.call([self.rar_exe_path,
|
|
|
|
'a',
|
|
|
|
'-w' + working_dir,
|
|
|
|
'-c-',
|
|
|
|
'-ep',
|
|
|
|
self.path,
|
|
|
|
tmp_file],
|
|
|
|
startupinfo=self.startupinfo,
|
|
|
|
stdout=RarArchiver.devnull)
|
|
|
|
|
|
|
|
if platform.system() == "Darwin":
|
|
|
|
time.sleep(1)
|
|
|
|
os.remove(tmp_file)
|
|
|
|
os.rmdir(tmp_folder)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeArchiveFile(self, archive_file):
|
|
|
|
if self.rar_exe_path is not None:
|
|
|
|
try:
|
|
|
|
# use external program to remove file from Rar archive
|
|
|
|
subprocess.call([self.rar_exe_path,
|
|
|
|
'd',
|
|
|
|
'-c-',
|
|
|
|
self.path,
|
|
|
|
archive_file],
|
|
|
|
startupinfo=self.startupinfo,
|
|
|
|
stdout=RarArchiver.devnull)
|
|
|
|
|
|
|
|
if platform.system() == "Darwin":
|
|
|
|
time.sleep(1)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def getArchiveFilenameList(self):
|
|
|
|
|
|
|
|
rarc = self.getRARObj()
|
|
|
|
#namelist = [ item.filename for item in rarc.infolist() ]
|
|
|
|
# return namelist
|
|
|
|
|
|
|
|
tries = 0
|
|
|
|
while tries < 7:
|
|
|
|
try:
|
|
|
|
tries = tries + 1
|
|
|
|
#namelist = [ item.filename for item in rarc.infolist() ]
|
|
|
|
namelist = []
|
|
|
|
for item in rarc.infolist():
|
|
|
|
if item.file_size != 0:
|
|
|
|
namelist.append(item.filename)
|
|
|
|
|
|
|
|
except (OSError, IOError) as e:
|
|
|
|
print >> sys.stderr, u"getArchiveFilenameList(): [{0}] {1} attempt#{2}".format(
|
|
|
|
str(e), self.path, tries)
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Success"
|
|
|
|
return namelist
|
|
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
def getRARObj(self):
|
|
|
|
tries = 0
|
|
|
|
while tries < 7:
|
|
|
|
try:
|
|
|
|
tries = tries + 1
|
|
|
|
rarc = rarfile.RarFile( self.path )
|
|
|
|
|
|
|
|
except (OSError, IOError) as e:
|
|
|
|
print >> sys.stderr, u"getRARObj(): [{0}] {1} attempt#{2}".format(
|
|
|
|
str(e), self.path, tries)
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Success"
|
|
|
|
return rarc
|
|
|
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
class FolderArchiver:
|
|
|
|
|
|
|
|
"""Folder implementation"""
|
|
|
|
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
self.comment_file_name = "ComicTaggerFolderComment.txt"
|
|
|
|
|
|
|
|
def getArchiveComment(self):
|
|
|
|
return self.readArchiveFile(self.comment_file_name)
|
|
|
|
|
|
|
|
def setArchiveComment(self, comment):
|
|
|
|
return self.writeArchiveFile(self.comment_file_name, comment)
|
|
|
|
|
|
|
|
def readArchiveFile(self, archive_file):
|
|
|
|
|
|
|
|
data = ""
|
|
|
|
fname = os.path.join(self.path, archive_file)
|
|
|
|
try:
|
|
|
|
with open(fname, 'rb') as f:
|
|
|
|
data = f.read()
|
|
|
|
f.close()
|
|
|
|
except IOError as e:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
def writeArchiveFile(self, archive_file, data):
|
|
|
|
|
|
|
|
fname = os.path.join(self.path, archive_file)
|
|
|
|
try:
|
|
|
|
with open(fname, 'w+') as f:
|
|
|
|
f.write(data)
|
|
|
|
f.close()
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def removeArchiveFile(self, archive_file):
|
|
|
|
|
|
|
|
fname = os.path.join(self.path, archive_file)
|
|
|
|
try:
|
|
|
|
os.remove(fname)
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def getArchiveFilenameList(self):
|
|
|
|
return self.listFiles(self.path)
|
|
|
|
|
|
|
|
def listFiles(self, folder):
|
|
|
|
|
|
|
|
itemlist = list()
|
|
|
|
|
|
|
|
for item in os.listdir(folder):
|
|
|
|
itemlist.append(item)
|
|
|
|
if os.path.isdir(item):
|
|
|
|
itemlist.extend(self.listFiles(os.path.join(folder, item)))
|
|
|
|
|
|
|
|
return itemlist
|
|
|
|
|
|
|
|
|
|
|
|
class UnknownArchiver:
|
|
|
|
|
|
|
|
"""Unknown implementation"""
|
|
|
|
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
def getArchiveComment(self):
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def setArchiveComment(self, comment):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def readArchiveFile(self):
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def writeArchiveFile(self, archive_file, data):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeArchiveFile(self, archive_file):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def getArchiveFilenameList(self):
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
class PdfArchiver:
|
|
|
|
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
def getArchiveComment(self):
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def setArchiveComment(self, comment):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def readArchiveFile(self, page_num):
|
|
|
|
return subprocess.check_output(
|
|
|
|
['mudraw', '-o', '-', self.path, str(int(os.path.basename(page_num)[:-4]))])
|
|
|
|
|
|
|
|
def writeArchiveFile(self, archive_file, data):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeArchiveFile(self, archive_file):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def getArchiveFilenameList(self):
|
|
|
|
out = []
|
|
|
|
#pdf = PdfFileReader(open(self.path, 'rb'))
|
|
|
|
#for page in range(1, pdf.getNumPages() + 1):
|
|
|
|
# out.append("/%04d.jpg" % (page))
|
|
|
|
return out
|
|
|
|
|
|
|
|
#------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
class ComicArchive:
|
|
|
|
|
|
|
|
logo_data = None
|
|
|
|
|
|
|
|
class ArchiveType:
|
|
|
|
Zip, Rar, Folder, Pdf, Unknown = range(5)
|
|
|
|
|
|
|
|
def __init__(self, path, rar_exe_path=None, default_image_path=None):
|
|
|
|
self.path = path
|
|
|
|
|
|
|
|
self.rar_exe_path = rar_exe_path
|
|
|
|
self.ci_xml_filename = 'ComicInfo.xml'
|
|
|
|
self.comet_default_filename = 'CoMet.xml'
|
|
|
|
self.resetCache()
|
|
|
|
self.default_image_path = default_image_path
|
|
|
|
|
|
|
|
# Use file extension to decide which archive test we do first
|
|
|
|
ext = os.path.splitext(path)[1].lower()
|
|
|
|
|
|
|
|
self.archive_type = self.ArchiveType.Unknown
|
|
|
|
self.archiver = UnknownArchiver(self.path)
|
|
|
|
|
|
|
|
if ext == ".cbr" or ext == ".rar":
|
|
|
|
if self.rarTest():
|
|
|
|
self.archive_type = self.ArchiveType.Rar
|
|
|
|
self.archiver = RarArchiver(
|
|
|
|
self.path,
|
|
|
|
rar_exe_path=self.rar_exe_path)
|
|
|
|
|
|
|
|
elif self.zipTest():
|
|
|
|
self.archive_type = self.ArchiveType.Zip
|
|
|
|
self.archiver = ZipArchiver(self.path)
|
|
|
|
else:
|
|
|
|
if self.zipTest():
|
|
|
|
self.archive_type = self.ArchiveType.Zip
|
|
|
|
self.archiver = ZipArchiver(self.path)
|
|
|
|
|
|
|
|
elif self.rarTest():
|
|
|
|
self.archive_type = self.ArchiveType.Rar
|
|
|
|
self.archiver = RarArchiver(
|
|
|
|
self.path,
|
|
|
|
rar_exe_path=self.rar_exe_path)
|
|
|
|
elif os.path.basename(self.path)[-3:] == 'pdf':
|
|
|
|
self.archive_type = self.ArchiveType.Pdf
|
|
|
|
self.archiver = PdfArchiver(self.path)
|
|
|
|
|
|
|
|
if ComicArchive.logo_data is None:
|
|
|
|
#fname = ComicTaggerSettings.getGraphic('nocover.png')
|
|
|
|
fname = self.default_image_path
|
|
|
|
with open(fname, 'rb') as fd:
|
|
|
|
ComicArchive.logo_data = fd.read()
|
|
|
|
|
|
|
|
def resetCache(self):
|
|
|
|
"""Clears the cached data"""
|
|
|
|
|
|
|
|
self.has_cix = None
|
|
|
|
self.has_cbi = None
|
|
|
|
self.has_comet = None
|
|
|
|
self.comet_filename = None
|
|
|
|
self.page_count = None
|
|
|
|
self.page_list = None
|
|
|
|
self.cix_md = None
|
|
|
|
self.cbi_md = None
|
|
|
|
self.comet_md = None
|
|
|
|
|
|
|
|
def loadCache(self, style_list):
|
|
|
|
for style in style_list:
|
|
|
|
self.readMetadata(style)
|
|
|
|
|
|
|
|
def rename(self, path):
|
|
|
|
self.path = path
|
|
|
|
self.archiver.path = path
|
|
|
|
|
|
|
|
def zipTest(self):
|
|
|
|
return zipfile.is_zipfile(self.path)
|
|
|
|
|
|
|
|
def rarTest(self):
|
|
|
|
try:
|
|
|
|
rarc = rarfile.RarFile(self.path)
|
|
|
|
except: # InvalidRARArchive:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
def isZip(self):
|
|
|
|
return self.archive_type == self.ArchiveType.Zip
|
|
|
|
|
|
|
|
def isRar(self):
|
|
|
|
return self.archive_type == self.ArchiveType.Rar
|
|
|
|
|
|
|
|
def isPdf(self):
|
|
|
|
return self.archive_type == self.ArchiveType.Pdf
|
|
|
|
|
|
|
|
def isFolder(self):
|
|
|
|
return self.archive_type == self.ArchiveType.Folder
|
|
|
|
|
|
|
|
def isWritable(self, check_rar_status=True):
|
|
|
|
if self.archive_type == self.ArchiveType.Unknown:
|
|
|
|
return False
|
|
|
|
|
|
|
|
elif check_rar_status and self.isRar() and self.rar_exe_path is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
elif not os.access(self.path, os.W_OK):
|
|
|
|
return False
|
|
|
|
|
|
|
|
elif ((self.archive_type != self.ArchiveType.Folder) and
|
|
|
|
(not os.access(os.path.dirname(os.path.abspath(self.path)), os.W_OK))):
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def isWritableForStyle(self, data_style):
|
|
|
|
|
|
|
|
if self.isRar() and data_style == MetaDataStyle.CBI:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return self.isWritable()
|
|
|
|
|
|
|
|
def seemsToBeAComicArchive(self):
|
|
|
|
|
|
|
|
# Do we even care about extensions??
|
|
|
|
ext = os.path.splitext(self.path)[1].lower()
|
|
|
|
|
|
|
|
if (
|
|
|
|
# or self.isFolder() )
|
|
|
|
(self.isZip() or self.isRar() or self.isPdf())
|
|
|
|
and
|
|
|
|
(self.getNumberOfPages() > 0)
|
|
|
|
|
|
|
|
):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def readMetadata(self, style):
|
|
|
|
|
|
|
|
if style == MetaDataStyle.CIX:
|
|
|
|
return self.readCIX()
|
|
|
|
elif style == MetaDataStyle.CBI:
|
|
|
|
return self.readCBI()
|
|
|
|
elif style == MetaDataStyle.COMET:
|
|
|
|
return self.readCoMet()
|
|
|
|
else:
|
|
|
|
return GenericMetadata()
|
|
|
|
|
|
|
|
def writeMetadata(self, metadata, style):
|
|
|
|
|
|
|
|
retcode = None
|
|
|
|
if style == MetaDataStyle.CIX:
|
|
|
|
retcode = self.writeCIX(metadata)
|
|
|
|
elif style == MetaDataStyle.CBI:
|
|
|
|
retcode = self.writeCBI(metadata)
|
|
|
|
elif style == MetaDataStyle.COMET:
|
|
|
|
retcode = self.writeCoMet(metadata)
|
|
|
|
return retcode
|
|
|
|
|
|
|
|
def hasMetadata(self, style):
|
|
|
|
|
|
|
|
if style == MetaDataStyle.CIX:
|
|
|
|
return self.hasCIX()
|
|
|
|
elif style == MetaDataStyle.CBI:
|
|
|
|
return self.hasCBI()
|
|
|
|
elif style == MetaDataStyle.COMET:
|
|
|
|
return self.hasCoMet()
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeMetadata(self, style):
|
|
|
|
retcode = True
|
|
|
|
if style == MetaDataStyle.CIX:
|
|
|
|
retcode = self.removeCIX()
|
|
|
|
elif style == MetaDataStyle.CBI:
|
|
|
|
retcode = self.removeCBI()
|
|
|
|
elif style == MetaDataStyle.COMET:
|
|
|
|
retcode = self.removeCoMet()
|
|
|
|
return retcode
|
|
|
|
|
|
|
|
def getPage(self, index):
|
|
|
|
|
|
|
|
image_data = None
|
|
|
|
|
|
|
|
filename = self.getPageName(index)
|
|
|
|
|
|
|
|
if filename is not None:
|
|
|
|
try:
|
|
|
|
image_data = self.archiver.readArchiveFile(filename)
|
|
|
|
except IOError:
|
|
|
|
print >> sys.stderr, u"Error reading in page. Substituting logo page."
|
|
|
|
image_data = ComicArchive.logo_data
|
|
|
|
|
|
|
|
return image_data
|
|
|
|
|
|
|
|
def getPageName(self, index):
|
|
|
|
|
|
|
|
if index is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
page_list = self.getPageNameList()
|
|
|
|
|
|
|
|
num_pages = len(page_list)
|
|
|
|
if num_pages == 0 or index >= num_pages:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return page_list[index]
|
|
|
|
|
|
|
|
def getScannerPageIndex(self):
|
|
|
|
|
|
|
|
scanner_page_index = None
|
|
|
|
|
|
|
|
# make a guess at the scanner page
|
|
|
|
name_list = self.getPageNameList()
|
|
|
|
count = self.getNumberOfPages()
|
|
|
|
|
|
|
|
# too few pages to really know
|
|
|
|
if count < 5:
|
|
|
|
return None
|
|
|
|
|
|
|
|
# count the length of every filename, and count occurences
|
|
|
|
length_buckets = dict()
|
|
|
|
for name in name_list:
|
|
|
|
fname = os.path.split(name)[1]
|
|
|
|
length = len(fname)
|
|
|
|
if length in length_buckets:
|
|
|
|
length_buckets[length] += 1
|
|
|
|
else:
|
|
|
|
length_buckets[length] = 1
|
|
|
|
|
|
|
|
# sort by most common
|
|
|
|
sorted_buckets = sorted(
|
|
|
|
length_buckets.iteritems(),
|
|
|
|
key=lambda k_v: (
|
|
|
|
k_v[1],
|
|
|
|
k_v[0]),
|
|
|
|
reverse=True)
|
|
|
|
|
|
|
|
# statistical mode occurence is first
|
|
|
|
mode_length = sorted_buckets[0][0]
|
|
|
|
|
|
|
|
# we are only going to consider the final image file:
|
|
|
|
final_name = os.path.split(name_list[count - 1])[1]
|
|
|
|
|
|
|
|
common_length_list = list()
|
|
|
|
for name in name_list:
|
|
|
|
if len(os.path.split(name)[1]) == mode_length:
|
|
|
|
common_length_list.append(os.path.split(name)[1])
|
|
|
|
|
|
|
|
prefix = os.path.commonprefix(common_length_list)
|
|
|
|
|
|
|
|
if mode_length <= 7 and prefix == "":
|
|
|
|
# probably all numbers
|
|
|
|
if len(final_name) > mode_length:
|
|
|
|
scanner_page_index = count - 1
|
|
|
|
|
|
|
|
# see if the last page doesn't start with the same prefix as most
|
|
|
|
# others
|
|
|
|
elif not final_name.startswith(prefix):
|
|
|
|
scanner_page_index = count - 1
|
|
|
|
|
|
|
|
return scanner_page_index
|
|
|
|
|
|
|
|
def getPageNameList(self, sort_list=True):
|
|
|
|
|
|
|
|
if self.page_list is None:
|
|
|
|
# get the list file names in the archive, and sort
|
|
|
|
files = self.archiver.getArchiveFilenameList()
|
|
|
|
|
|
|
|
# seems like some archive creators are on Windows, and don't know
|
|
|
|
# about case-sensitivity!
|
|
|
|
if sort_list:
|
|
|
|
def keyfunc(k):
|
|
|
|
# hack to account for some weird scanner ID pages
|
|
|
|
# basename=os.path.split(k)[1]
|
|
|
|
# if basename < '0':
|
|
|
|
# k = os.path.join(os.path.split(k)[0], "z" + basename)
|
|
|
|
return k.lower()
|
|
|
|
|
|
|
|
files = natsorted(files, key=keyfunc, signed=False)
|
|
|
|
|
|
|
|
# make a sub-list of image files
|
|
|
|
self.page_list = []
|
|
|
|
for name in files:
|
|
|
|
if (name[-4:].lower() in [".jpg",
|
|
|
|
"jpeg",
|
|
|
|
".png",
|
|
|
|
".gif",
|
|
|
|
"webp"] and os.path.basename(name)[0] != "."):
|
|
|
|
self.page_list.append(name)
|
|
|
|
|
|
|
|
return self.page_list
|
|
|
|
|
|
|
|
def getNumberOfPages(self):
|
|
|
|
|
|
|
|
if self.page_count is None:
|
|
|
|
self.page_count = len(self.getPageNameList())
|
|
|
|
return self.page_count
|
|
|
|
|
|
|
|
def readCBI(self):
|
|
|
|
if self.cbi_md is None:
|
|
|
|
raw_cbi = self.readRawCBI()
|
|
|
|
if raw_cbi is None:
|
|
|
|
self.cbi_md = GenericMetadata()
|
|
|
|
else:
|
|
|
|
self.cbi_md = ComicBookInfo().metadataFromString(raw_cbi)
|
|
|
|
|
|
|
|
self.cbi_md.setDefaultPageList(self.getNumberOfPages())
|
|
|
|
|
|
|
|
return self.cbi_md
|
|
|
|
|
|
|
|
def readRawCBI(self):
|
|
|
|
if (not self.hasCBI()):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.archiver.getArchiveComment()
|
|
|
|
|
|
|
|
def hasCBI(self):
|
|
|
|
if self.has_cbi is None:
|
|
|
|
|
|
|
|
# if ( not ( self.isZip() or self.isRar()) or not
|
|
|
|
# self.seemsToBeAComicArchive() ):
|
|
|
|
if not self.seemsToBeAComicArchive():
|
|
|
|
self.has_cbi = False
|
|
|
|
else:
|
|
|
|
comment = self.archiver.getArchiveComment()
|
|
|
|
self.has_cbi = ComicBookInfo().validateString(comment)
|
|
|
|
|
|
|
|
return self.has_cbi
|
|
|
|
|
|
|
|
def writeCBI(self, metadata):
|
|
|
|
if metadata is not None:
|
|
|
|
self.applyArchiveInfoToMetadata(metadata)
|
|
|
|
cbi_string = ComicBookInfo().stringFromMetadata(metadata)
|
|
|
|
write_success = self.archiver.setArchiveComment(cbi_string)
|
|
|
|
if write_success:
|
|
|
|
self.has_cbi = True
|
|
|
|
self.cbi_md = metadata
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeCBI(self):
|
|
|
|
if self.hasCBI():
|
|
|
|
write_success = self.archiver.setArchiveComment("")
|
|
|
|
if write_success:
|
|
|
|
self.has_cbi = False
|
|
|
|
self.cbi_md = None
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
return True
|
|
|
|
|
|
|
|
def readCIX(self):
|
|
|
|
if self.cix_md is None:
|
|
|
|
raw_cix = self.readRawCIX()
|
|
|
|
if raw_cix is None or raw_cix == "":
|
|
|
|
self.cix_md = GenericMetadata()
|
|
|
|
else:
|
|
|
|
self.cix_md = ComicInfoXml().metadataFromString(raw_cix)
|
|
|
|
|
|
|
|
# validate the existing page list (make sure count is correct)
|
|
|
|
if len(self.cix_md.pages) != 0:
|
|
|
|
if len(self.cix_md.pages) != self.getNumberOfPages():
|
|
|
|
# pages array doesn't match the actual number of images we're seeing
|
|
|
|
# in the archive, so discard the data
|
|
|
|
self.cix_md.pages = []
|
|
|
|
|
|
|
|
if len(self.cix_md.pages) == 0:
|
|
|
|
self.cix_md.setDefaultPageList(self.getNumberOfPages())
|
|
|
|
|
|
|
|
return self.cix_md
|
|
|
|
|
|
|
|
def readRawCIX(self):
|
|
|
|
if not self.hasCIX():
|
|
|
|
return None
|
|
|
|
try:
|
|
|
|
raw_cix = self.archiver.readArchiveFile(self.ci_xml_filename)
|
|
|
|
except IOError:
|
|
|
|
print "Error reading in raw CIX!"
|
|
|
|
raw_cix = ""
|
|
|
|
return raw_cix
|
|
|
|
|
|
|
|
def writeCIX(self, metadata):
|
|
|
|
|
|
|
|
if metadata is not None:
|
|
|
|
self.applyArchiveInfoToMetadata(metadata, calc_page_sizes=True)
|
|
|
|
cix_string = ComicInfoXml().stringFromMetadata(metadata)
|
|
|
|
write_success = self.archiver.writeArchiveFile(
|
|
|
|
self.ci_xml_filename,
|
|
|
|
cix_string)
|
|
|
|
if write_success:
|
|
|
|
self.has_cix = True
|
|
|
|
self.cix_md = metadata
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeCIX(self):
|
|
|
|
if self.hasCIX():
|
|
|
|
write_success = self.archiver.removeArchiveFile(
|
|
|
|
self.ci_xml_filename)
|
|
|
|
if write_success:
|
|
|
|
self.has_cix = False
|
|
|
|
self.cix_md = None
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
return True
|
|
|
|
|
|
|
|
def hasCIX(self):
|
|
|
|
if self.has_cix is None:
|
|
|
|
|
|
|
|
if not self.seemsToBeAComicArchive():
|
|
|
|
self.has_cix = False
|
|
|
|
elif self.ci_xml_filename in self.archiver.getArchiveFilenameList():
|
|
|
|
self.has_cix = True
|
|
|
|
else:
|
|
|
|
self.has_cix = False
|
|
|
|
return self.has_cix
|
|
|
|
|
|
|
|
def readCoMet(self):
|
|
|
|
if self.comet_md is None:
|
|
|
|
raw_comet = self.readRawCoMet()
|
|
|
|
if raw_comet is None or raw_comet == "":
|
|
|
|
self.comet_md = GenericMetadata()
|
|
|
|
else:
|
|
|
|
self.comet_md = CoMet().metadataFromString(raw_comet)
|
|
|
|
|
|
|
|
self.comet_md.setDefaultPageList(self.getNumberOfPages())
|
|
|
|
# use the coverImage value from the comet_data to mark the cover in this struct
|
|
|
|
# walk through list of images in file, and find the matching one for md.coverImage
|
|
|
|
# need to remove the existing one in the default
|
|
|
|
if self.comet_md.coverImage is not None:
|
|
|
|
cover_idx = 0
|
|
|
|
for idx, f in enumerate(self.getPageNameList()):
|
|
|
|
if self.comet_md.coverImage == f:
|
|
|
|
cover_idx = idx
|
|
|
|
break
|
|
|
|
if cover_idx != 0:
|
|
|
|
del (self.comet_md.pages[0]['Type'])
|
|
|
|
self.comet_md.pages[cover_idx][
|
|
|
|
'Type'] = PageType.FrontCover
|
|
|
|
|
|
|
|
return self.comet_md
|
|
|
|
|
|
|
|
def readRawCoMet(self):
|
|
|
|
if not self.hasCoMet():
|
|
|
|
print >> sys.stderr, self.path, "doesn't have CoMet data!"
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
raw_comet = self.archiver.readArchiveFile(self.comet_filename)
|
|
|
|
except IOError:
|
|
|
|
print >> sys.stderr, u"Error reading in raw CoMet!"
|
|
|
|
raw_comet = ""
|
|
|
|
return raw_comet
|
|
|
|
|
|
|
|
def writeCoMet(self, metadata):
|
|
|
|
|
|
|
|
if metadata is not None:
|
|
|
|
if not self.hasCoMet():
|
|
|
|
self.comet_filename = self.comet_default_filename
|
|
|
|
|
|
|
|
self.applyArchiveInfoToMetadata(metadata)
|
|
|
|
# Set the coverImage value, if it's not the first page
|
|
|
|
cover_idx = int(metadata.getCoverPageIndexList()[0])
|
|
|
|
if cover_idx != 0:
|
|
|
|
metadata.coverImage = self.getPageName(cover_idx)
|
|
|
|
|
|
|
|
comet_string = CoMet().stringFromMetadata(metadata)
|
|
|
|
write_success = self.archiver.writeArchiveFile(
|
|
|
|
self.comet_filename,
|
|
|
|
comet_string)
|
|
|
|
if write_success:
|
|
|
|
self.has_comet = True
|
|
|
|
self.comet_md = metadata
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def removeCoMet(self):
|
|
|
|
if self.hasCoMet():
|
|
|
|
write_success = self.archiver.removeArchiveFile(
|
|
|
|
self.comet_filename)
|
|
|
|
if write_success:
|
|
|
|
self.has_comet = False
|
|
|
|
self.comet_md = None
|
|
|
|
self.resetCache()
|
|
|
|
return write_success
|
|
|
|
return True
|
|
|
|
|
|
|
|
def hasCoMet(self):
|
|
|
|
if self.has_comet is None:
|
|
|
|
self.has_comet = False
|
|
|
|
if not self.seemsToBeAComicArchive():
|
|
|
|
return self.has_comet
|
|
|
|
|
|
|
|
# look at all xml files in root, and search for CoMet data, get
|
|
|
|
# first
|
|
|
|
for n in self.archiver.getArchiveFilenameList():
|
|
|
|
if (os.path.dirname(n) == "" and
|
|
|
|
os.path.splitext(n)[1].lower() == '.xml'):
|
|
|
|
# read in XML file, and validate it
|
|
|
|
try:
|
|
|
|
data = self.archiver.readArchiveFile(n)
|
|
|
|
except:
|
|
|
|
data = ""
|
|
|
|
print >> sys.stderr, u"Error reading in Comet XML for validation!"
|
|
|
|
if CoMet().validateString(data):
|
|
|
|
# since we found it, save it!
|
|
|
|
self.comet_filename = n
|
|
|
|
self.has_comet = True
|
|
|
|
break
|
|
|
|
|
|
|
|
return self.has_comet
|
|
|
|
|
|
|
|
def applyArchiveInfoToMetadata(self, md, calc_page_sizes=False):
|
|
|
|
md.pageCount = self.getNumberOfPages()
|
|
|
|
|
|
|
|
if calc_page_sizes:
|
|
|
|
for p in md.pages:
|
|
|
|
idx = int(p['Image'])
|
|
|
|
if pil_available:
|
|
|
|
if 'ImageSize' not in p or 'ImageHeight' not in p or 'ImageWidth' not in p:
|
|
|
|
data = self.getPage(idx)
|
|
|
|
if data is not None:
|
|
|
|
try:
|
|
|
|
im = Image.open(StringIO.StringIO(data))
|
|
|
|
w, h = im.size
|
|
|
|
|
|
|
|
p['ImageSize'] = str(len(data))
|
|
|
|
p['ImageHeight'] = str(h)
|
|
|
|
p['ImageWidth'] = str(w)
|
|
|
|
except IOError:
|
|
|
|
p['ImageSize'] = str(len(data))
|
|
|
|
|
|
|
|
else:
|
|
|
|
if 'ImageSize' not in p:
|
|
|
|
data = self.getPage(idx)
|
|
|
|
p['ImageSize'] = str(len(data))
|
|
|
|
|
|
|
|
def metadataFromFilename(self, parse_scan_info=True):
|
|
|
|
|
|
|
|
metadata = GenericMetadata()
|
|
|
|
|
|
|
|
fnp = FileNameParser()
|
|
|
|
fnp.parseFilename(self.path)
|
|
|
|
|
|
|
|
if fnp.issue != "":
|
|
|
|
metadata.issue = fnp.issue
|
|
|
|
if fnp.series != "":
|
|
|
|
metadata.series = fnp.series
|
|
|
|
if fnp.volume != "":
|
|
|
|
metadata.volume = fnp.volume
|
|
|
|
if fnp.year != "":
|
|
|
|
metadata.year = fnp.year
|
|
|
|
if fnp.issue_count != "":
|
|
|
|
metadata.issueCount = fnp.issue_count
|
|
|
|
if parse_scan_info:
|
|
|
|
if fnp.remainder != "":
|
|
|
|
metadata.scanInfo = fnp.remainder
|
|
|
|
|
|
|
|
metadata.isEmpty = False
|
|
|
|
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
def exportAsZip(self, zipfilename):
|
|
|
|
if self.archive_type == self.ArchiveType.Zip:
|
|
|
|
# nothing to do, we're already a zip
|
|
|
|
return True
|
|
|
|
|
|
|
|
zip_archiver = ZipArchiver(zipfilename)
|
|
|
|
return zip_archiver.copyFromArchive(self.archiver)
|