mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Set cover in MOBI files when saving to disk. Also update metadata in LRF, EPUB and MOBI files when sending to device.
This commit is contained in:
parent
2a474382d0
commit
f8aa3bf8de
@ -18,7 +18,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
|
||||
from calibre.ebooks.metadata import get_parser, MetaInformation
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre import CurrentDir, fit_image
|
||||
from calibre import CurrentDir
|
||||
|
||||
class EPubException(Exception):
|
||||
pass
|
||||
|
@ -151,18 +151,22 @@ class MetadataUpdater(object):
|
||||
self.exth[:] = ''.join([exth, title, '\0' * trail])
|
||||
self.record0[84:92] = pack('>II', title_off, title_len)
|
||||
self.record0[92:96] = iana2mobi(mi.language)
|
||||
if mi.cover_data[1]:
|
||||
data = mi.cover_data[1]
|
||||
if self.cover_record is not None:
|
||||
size = len(self.cover_record)
|
||||
cover = rescale_image(data, size)
|
||||
cover += '\0' * (size - len(cover))
|
||||
self.cover_record[:] = cover
|
||||
if self.thumbnail_record is not None:
|
||||
size = len(self.thumbnail_record)
|
||||
thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN)
|
||||
thumbnail += '\0' * (size - len(thumbnail))
|
||||
self.thumbnail_record[:] = thumbnail
|
||||
if mi.cover_data[1] or mi.cover:
|
||||
try:
|
||||
data = mi.cover_data[1] if mi.cover_data[1] else open(mi.cover, 'rb').read()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if self.cover_record is not None:
|
||||
size = len(self.cover_record)
|
||||
cover = rescale_image(data, size)
|
||||
cover += '\0' * (size - len(cover))
|
||||
self.cover_record[:] = cover
|
||||
if self.thumbnail_record is not None:
|
||||
size = len(self.thumbnail_record)
|
||||
thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN)
|
||||
thumbnail += '\0' * (size - len(thumbnail))
|
||||
self.thumbnail_record[:] = thumbnail
|
||||
return
|
||||
|
||||
def set_metadata(stream, mi):
|
||||
|
@ -306,13 +306,15 @@ IANA_MOBI = \
|
||||
'zu': {None: (53, 0)}}
|
||||
|
||||
def iana2mobi(icode):
|
||||
subtags = list(icode.split('-'))
|
||||
langdict = IANA_MOBI[None]
|
||||
while len(subtags) > 0:
|
||||
lang = subtags.pop(0).lower()
|
||||
if lang in IANA_MOBI:
|
||||
langdict = IANA_MOBI[lang]
|
||||
break
|
||||
langdict, subtags = IANA_MOBI[None], []
|
||||
if icode:
|
||||
subtags = list(icode.split('-'))
|
||||
while len(subtags) > 0:
|
||||
lang = subtags.pop(0).lower()
|
||||
if lang in IANA_MOBI:
|
||||
langdict = IANA_MOBI[lang]
|
||||
break
|
||||
|
||||
mcode = langdict[None]
|
||||
while len(subtags) > 0:
|
||||
subtag = subtags.pop(0)
|
||||
|
@ -18,6 +18,7 @@ from calibre.library.database2 import FIELD_MAP
|
||||
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
|
||||
error_dialog
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||
|
||||
class LibraryDelegate(QItemDelegate):
|
||||
COLOR = QColor("blue")
|
||||
@ -423,7 +424,7 @@ class BooksModel(QAbstractTableModel):
|
||||
|
||||
|
||||
|
||||
def get_preferred_formats(self, rows, formats, paths=False):
|
||||
def get_preferred_formats(self, rows, formats, paths=False, set_metadata=False):
|
||||
ans = []
|
||||
for row in (row.row() for row in rows):
|
||||
format = None
|
||||
@ -441,6 +442,9 @@ class BooksModel(QAbstractTableModel):
|
||||
pt = PersistentTemporaryFile(suffix='.'+format)
|
||||
pt.write(self.db.format(row, format))
|
||||
pt.flush()
|
||||
if set_metadata:
|
||||
_set_metadata(pt, self.db.get_metadata(row, get_cover=True),
|
||||
format)
|
||||
pt.close() if paths else pt.seek(0)
|
||||
ans.append(pt)
|
||||
else:
|
||||
|
@ -932,7 +932,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
mi['cover'] = self.cover_to_thumbnail(cdata)
|
||||
metadata = iter(metadata)
|
||||
_files = self.library_view.model().get_preferred_formats(rows,
|
||||
self.device_manager.device_class.FORMATS, paths=True)
|
||||
self.device_manager.device_class.FORMATS,
|
||||
paths=True, set_metadata=True)
|
||||
files = [getattr(f, 'name', None) for f in _files]
|
||||
bad, good, gf, names, remove_ids = [], [], [], [], []
|
||||
for f in files:
|
||||
|
@ -4,12 +4,11 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
Backend that implements storage of ebooks in an sqlite database.
|
||||
'''
|
||||
import sqlite3 as sqlite
|
||||
import datetime, re, os, cPickle, traceback, sre_constants
|
||||
import datetime, re, os, cPickle, sre_constants
|
||||
from zlib import compress, decompress
|
||||
|
||||
from calibre import sanitize_file_name
|
||||
from calibre.ebooks.metadata.meta import set_metadata, metadata_from_formats
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe
|
||||
@ -1389,76 +1388,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
def all_ids(self):
|
||||
return [i[0] for i in self.conn.get('SELECT id FROM books')]
|
||||
|
||||
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
||||
index_is_id=False, callback=None):
|
||||
if not os.path.exists(dir):
|
||||
raise IOError('Target directory does not exist: '+dir)
|
||||
by_author = {}
|
||||
count = 0
|
||||
for index in indices:
|
||||
id = index if index_is_id else self.id(index)
|
||||
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
||||
(id,), all=False)
|
||||
if not au:
|
||||
au = self.authors(index, index_is_id=index_is_id)
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
au = au.split(',')[0]
|
||||
if not by_author.has_key(au):
|
||||
by_author[au] = []
|
||||
by_author[au].append(index)
|
||||
for au in by_author.keys():
|
||||
apath = os.path.join(dir, sanitize_file_name(au))
|
||||
if not single_dir and not os.path.exists(apath):
|
||||
os.mkdir(apath)
|
||||
for idx in by_author[au]:
|
||||
title = re.sub(r'\s', ' ', self.title(idx, index_is_id=index_is_id))
|
||||
tpath = os.path.join(apath, sanitize_file_name(title))
|
||||
id = idx if index_is_id else self.id(idx)
|
||||
id = str(id)
|
||||
if not single_dir and not os.path.exists(tpath):
|
||||
os.mkdir(tpath)
|
||||
|
||||
name = au + ' - ' + title if byauthor else title + ' - ' + au
|
||||
name += '_'+id
|
||||
base = dir if single_dir else tpath
|
||||
mi = self.get_metadata(idx, index_is_id=index_is_id)
|
||||
cover = self.cover(idx, index_is_id=index_is_id)
|
||||
if cover is not None:
|
||||
cname = sanitize_file_name(name) + '.jpg'
|
||||
cpath = os.path.join(base, cname)
|
||||
open(cpath, 'wb').write(cover)
|
||||
mi.cover = cname
|
||||
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
|
||||
if not mi.authors:
|
||||
mi.authors = [_('Unknown')]
|
||||
opf = OPFCreator(base, mi)
|
||||
opf.render(f)
|
||||
f.close()
|
||||
|
||||
fmts = self.formats(idx, index_is_id=index_is_id)
|
||||
if not fmts:
|
||||
fmts = ''
|
||||
for fmt in fmts.split(','):
|
||||
data = self.format(idx, fmt, index_is_id=index_is_id)
|
||||
if not data:
|
||||
continue
|
||||
fname = name +'.'+fmt.lower()
|
||||
fname = sanitize_file_name(fname)
|
||||
f = open(os.path.join(base, fname), 'w+b')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
f.seek(0)
|
||||
try:
|
||||
set_metadata(f, mi, fmt.lower())
|
||||
except:
|
||||
print 'Error setting metadata for book:', mi.title
|
||||
traceback.print_exc()
|
||||
f.close()
|
||||
count += 1
|
||||
if callable(callback):
|
||||
if not callback(count, mi.title):
|
||||
return
|
||||
|
||||
|
||||
|
||||
@ -1573,42 +1502,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
|
||||
return duplicates
|
||||
|
||||
def export_single_format_to_dir(self, dir, indices, format,
|
||||
index_is_id=False, callback=None):
|
||||
dir = os.path.abspath(dir)
|
||||
if not index_is_id:
|
||||
indices = map(self.id, indices)
|
||||
failures = []
|
||||
for count, id in enumerate(indices):
|
||||
try:
|
||||
data = self.format(id, format, index_is_id=True)
|
||||
if not data:
|
||||
failures.append((id, self.title(id, index_is_id=True)))
|
||||
continue
|
||||
except:
|
||||
failures.append((id, self.title(id, index_is_id=True)))
|
||||
continue
|
||||
title = self.title(id, index_is_id=True)
|
||||
au = self.authors(id, index_is_id=True)
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
fname = '%s - %s.%s'%(title, au, format.lower())
|
||||
fname = sanitize_file_name(fname)
|
||||
if not os.path.exists(dir):
|
||||
os.makedirs(dir)
|
||||
f = open(os.path.join(dir, fname), 'w+b')
|
||||
f.write(data)
|
||||
f.seek(0)
|
||||
try:
|
||||
set_metadata(f, self.get_metadata(id, index_is_id=True), stream_type=format.lower())
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
if callable(callback):
|
||||
if not callback(count, title):
|
||||
break
|
||||
return failures
|
||||
|
||||
|
||||
|
||||
class SearchToken(object):
|
||||
|
@ -19,8 +19,9 @@ from calibre.library import title_sort
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.library.sqlite import connect, IntegrityError
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation
|
||||
from calibre.ebooks.metadata.meta import get_metadata, set_metadata
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.customize.ui import run_plugins_on_import
|
||||
@ -556,7 +557,8 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
def cover(self, index, index_is_id=False, as_file=False, as_image=False):
|
||||
def cover(self, index, index_is_id=False, as_file=False, as_image=False,
|
||||
as_path=False):
|
||||
'''
|
||||
Return the cover image as a bytestring (in JPEG format) or None.
|
||||
|
||||
@ -566,6 +568,8 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
id = index if index_is_id else self.id(index)
|
||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
|
||||
if os.access(path, os.R_OK):
|
||||
if as_path:
|
||||
return path
|
||||
f = open(path, 'rb')
|
||||
if as_image:
|
||||
img = QImage()
|
||||
@ -573,6 +577,30 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
return img
|
||||
return f if as_file else f.read()
|
||||
|
||||
def get_metadata(self, idx, index_is_id=False, get_cover=False):
|
||||
'''
|
||||
Convenience method to return metadata as a L{MetaInformation} object.
|
||||
'''
|
||||
aum = self.authors(idx, index_is_id=index_is_id)
|
||||
if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
|
||||
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
|
||||
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
|
||||
mi.comments = self.comments(idx, index_is_id=index_is_id)
|
||||
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
|
||||
tags = self.tags(idx, index_is_id=index_is_id)
|
||||
if tags:
|
||||
mi.tags = [i.strip() for i in tags.split(',')]
|
||||
mi.series = self.series(idx, index_is_id=index_is_id)
|
||||
if mi.series:
|
||||
mi.series_index = self.series_index(idx, index_is_id=index_is_id)
|
||||
mi.rating = self.rating(idx, index_is_id=index_is_id)
|
||||
mi.isbn = self.isbn(idx, index_is_id=index_is_id)
|
||||
id = idx if index_is_id else self.id(idx)
|
||||
mi.application_id = id
|
||||
if get_cover:
|
||||
mi.cover = self.cover(id, index_is_id=True, as_path=True)
|
||||
return mi
|
||||
|
||||
def has_book(self, mi):
|
||||
title = mi.title
|
||||
if title:
|
||||
@ -1323,4 +1351,106 @@ books_series_link feeds
|
||||
progress.reset()
|
||||
return len(books)
|
||||
|
||||
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
||||
index_is_id=False, callback=None):
|
||||
if not os.path.exists(dir):
|
||||
raise IOError('Target directory does not exist: '+dir)
|
||||
by_author = {}
|
||||
count = 0
|
||||
for index in indices:
|
||||
id = index if index_is_id else self.id(index)
|
||||
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
||||
(id,), all=False)
|
||||
if not au:
|
||||
au = self.authors(index, index_is_id=index_is_id)
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
au = au.split(',')[0]
|
||||
if not by_author.has_key(au):
|
||||
by_author[au] = []
|
||||
by_author[au].append(index)
|
||||
for au in by_author.keys():
|
||||
apath = os.path.join(dir, sanitize_file_name(au))
|
||||
if not single_dir and not os.path.exists(apath):
|
||||
os.mkdir(apath)
|
||||
for idx in by_author[au]:
|
||||
title = re.sub(r'\s', ' ', self.title(idx, index_is_id=index_is_id))
|
||||
tpath = os.path.join(apath, sanitize_file_name(title))
|
||||
id = idx if index_is_id else self.id(idx)
|
||||
id = str(id)
|
||||
if not single_dir and not os.path.exists(tpath):
|
||||
os.mkdir(tpath)
|
||||
|
||||
name = au + ' - ' + title if byauthor else title + ' - ' + au
|
||||
name += '_'+id
|
||||
base = dir if single_dir else tpath
|
||||
mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True)
|
||||
f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb')
|
||||
if not mi.authors:
|
||||
mi.authors = [_('Unknown')]
|
||||
opf = OPFCreator(base, mi)
|
||||
opf.render(f)
|
||||
f.close()
|
||||
|
||||
fmts = self.formats(idx, index_is_id=index_is_id)
|
||||
if not fmts:
|
||||
fmts = ''
|
||||
for fmt in fmts.split(','):
|
||||
data = self.format(idx, fmt, index_is_id=index_is_id)
|
||||
if not data:
|
||||
continue
|
||||
fname = name +'.'+fmt.lower()
|
||||
fname = sanitize_file_name(fname)
|
||||
f = open(os.path.join(base, fname), 'w+b')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
f.seek(0)
|
||||
try:
|
||||
set_metadata(f, mi, fmt.lower())
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
count += 1
|
||||
if callable(callback):
|
||||
if not callback(count, mi.title):
|
||||
return
|
||||
|
||||
def export_single_format_to_dir(self, dir, indices, format,
|
||||
index_is_id=False, callback=None):
|
||||
dir = os.path.abspath(dir)
|
||||
if not index_is_id:
|
||||
indices = map(self.id, indices)
|
||||
failures = []
|
||||
for count, id in enumerate(indices):
|
||||
try:
|
||||
data = self.format(id, format, index_is_id=True)
|
||||
if not data:
|
||||
failures.append((id, self.title(id, index_is_id=True)))
|
||||
continue
|
||||
except:
|
||||
failures.append((id, self.title(id, index_is_id=True)))
|
||||
continue
|
||||
title = self.title(id, index_is_id=True)
|
||||
au = self.authors(id, index_is_id=True)
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
fname = '%s - %s.%s'%(title, au, format.lower())
|
||||
fname = sanitize_file_name(fname)
|
||||
if not os.path.exists(dir):
|
||||
os.makedirs(dir)
|
||||
f = open(os.path.join(dir, fname), 'w+b')
|
||||
f.write(data)
|
||||
f.seek(0)
|
||||
try:
|
||||
set_metadata(f, self.get_metadata(id, index_is_id=True, get_cover=True),
|
||||
stream_type=format.lower())
|
||||
except:
|
||||
pass
|
||||
f.close()
|
||||
if callable(callback):
|
||||
if not callback(count, title):
|
||||
break
|
||||
return failures
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user