diff --git a/src/calibre/ebooks/metadata/archive.py b/src/calibre/ebooks/metadata/archive.py index b73754a7ae..e608d6f486 100644 --- a/src/calibre/ebooks/metadata/archive.py +++ b/src/calibre/ebooks/metadata/archive.py @@ -60,29 +60,61 @@ class KPFExtract(FileTypePlugin): of.write(zf.read(candidates[0])) return of.name +class RAR: + + def __init__(self, archive): + self.archive = archive + + def close(self): + pass + + def namelist(self): + from calibre.utils.unrar import names + return list(names(self.archive)) + + def read(self, fname): + from calibre.utils.unrar import extract_member + return extract_member(self.archive, match=None, name=fname)[1] + + +class SevenZip: + + def __init__(self, archive): + from py7zr import SevenZipFile + self.zf = SevenZipFile(archive, 'r') + + def namelist(self): + return list(self.zf.getnames()) + + def close(self): + self.zf.close() + + def read(self, fname): + return self.zf.read((fname,))[fname].read() + class ArchiveExtract(FileTypePlugin): name = 'Archive Extract' author = 'Kovid Goyal' description = _('Extract common e-book formats from archive files ' - '(ZIP/RAR). Also try to autodetect if they are actually ' - 'CBZ/CBR files.') - file_types = {'zip', 'rar'} + '(ZIP/RAR/7z). Also try to autodetect if they are actually ' + 'CBZ/CBR/CB7 files.') + file_types = {'zip', 'rar', '7z'} supported_platforms = ['windows', 'osx', 'linux'] on_import = True def run(self, archive): - from calibre.utils.zipfile import ZipFile - is_rar = archive.lower().endswith('.rar') - if is_rar: - from calibre.utils.unrar import extract_member, names + q = archive.lower() + if q.endswith('.rar'): + comic_ext = 'cbr' + zf = RAR(archive) + elif q.endswith('.7z'): + comic_ext = 'cb7' + zf = SevenZip(archive) else: + from calibre.utils.zipfile import ZipFile zf = ZipFile(archive, 'r') - - if is_rar: - fnames = list(names(archive)) - else: - fnames = zf.namelist() + comic_ext = 'cbz' def fname_ok(fname): bn = os.path.basename(fname).lower() @@ -96,31 +128,28 @@ class ArchiveExtract(FileTypePlugin): return False return True - fnames = list(filter(fname_ok, fnames)) - if is_comic(fnames): - ext = '.cbr' if is_rar else '.cbz' - of = self.temporary_file('_archive_extract'+ext) - with open(archive, 'rb') as f: - of.write(f.read()) - of.close() - return of.name - if len(fnames) > 1 or not fnames: - return archive - fname = fnames[0] - ext = os.path.splitext(fname)[1][1:] - if ext.lower() not in { - 'lit', 'epub', 'mobi', 'prc', 'rtf', 'pdf', 'mp3', 'pdb', - 'azw', 'azw1', 'azw3', 'fb2', 'docx', 'doc', 'odt'}: - return archive + with closing(zf): + fnames = zf.namelist() + fnames = list(filter(fname_ok, fnames)) + if is_comic(fnames): + ext = comic_ext + of = self.temporary_file('_archive_extract'+ext) + with closing(of), open(archive, 'rb') as f: + of.write(f.read()) + return of.name + if len(fnames) > 1 or not fnames: + return archive + fname = fnames[0] + ext = os.path.splitext(fname)[1][1:] + if ext.lower() not in { + 'lit', 'epub', 'mobi', 'prc', 'rtf', 'pdf', 'mp3', 'pdb', + 'azw', 'azw1', 'azw3', 'fb2', 'docx', 'doc', 'odt'}: + return archive - of = self.temporary_file('_archive_extract.'+ext) - with closing(of): - if is_rar: - data = extract_member(archive, match=None, name=fname)[1] - of.write(data) - else: + of = self.temporary_file('_archive_extract.'+ext) + with closing(of): of.write(zf.read(fname)) - return of.name + return of.name def get_comic_book_info(d, mi, series_index='volume'): diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index 31aa002e90..ecbd07168d 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -31,7 +31,7 @@ from polyglot.builtins import iteritems, string_or_bytes def get_filters(): - archives = ['zip', 'rar'] + archives = ['zip', 'rar', '7z'] return [ (_('Books'), [x for x in BOOK_EXTENSIONS if x not in archives]), (_('EPUB books'), ['epub', 'kepub']),