mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Implement set_cover() and set_metada(), tests still to come
This commit is contained in:
parent
881b30ad62
commit
04090c8e6f
@ -25,6 +25,7 @@ from calibre.utils.config import to_json, from_json, prefs, tweaks
|
|||||||
from calibre.utils.date import utcfromtimestamp, parse_date
|
from calibre.utils.date import utcfromtimestamp, parse_date
|
||||||
from calibre.utils.filenames import (is_case_sensitive, samefile, hardlink_file, ascii_filename,
|
from calibre.utils.filenames import (is_case_sensitive, samefile, hardlink_file, ascii_filename,
|
||||||
WindowsAtomicFolderMove)
|
WindowsAtomicFolderMove)
|
||||||
|
from calibre.utils.magick.draw import save_cover_data_to
|
||||||
from calibre.utils.recycle_bin import delete_tree
|
from calibre.utils.recycle_bin import delete_tree
|
||||||
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
|
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
|
||||||
SizeTable, FormatsTable, AuthorsTable, IdentifiersTable, PathTable,
|
SizeTable, FormatsTable, AuthorsTable, IdentifiersTable, PathTable,
|
||||||
@ -973,6 +974,23 @@ class DB(object):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def set_cover(self, book_id, path, data):
|
||||||
|
path = os.path.abspath(os.path.join(self.library_path, path))
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
path = os.path.join(path, 'cover.jpg')
|
||||||
|
if callable(getattr(data, 'save', None)):
|
||||||
|
from calibre.gui2 import pixmap_to_data
|
||||||
|
data = pixmap_to_data(data)
|
||||||
|
else:
|
||||||
|
if callable(getattr(data, 'read', None)):
|
||||||
|
data = data.read()
|
||||||
|
try:
|
||||||
|
save_cover_data_to(data, path)
|
||||||
|
except (IOError, OSError):
|
||||||
|
time.sleep(0.2)
|
||||||
|
save_cover_data_to(data, path)
|
||||||
|
|
||||||
def copy_format_to(self, book_id, fmt, fname, path, dest,
|
def copy_format_to(self, book_id, fmt, fname, path, dest,
|
||||||
windows_atomic_move=None, use_hardlink=False):
|
windows_atomic_move=None, use_hardlink=False):
|
||||||
path = self.format_abspath(book_id, fmt, fname, path)
|
path = self.format_abspath(book_id, fmt, fname, path)
|
||||||
|
@ -22,6 +22,7 @@ from calibre.db.search import Search
|
|||||||
from calibre.db.tables import VirtualTable
|
from calibre.db.tables import VirtualTable
|
||||||
from calibre.db.write import get_series_values
|
from calibre.db.write import get_series_values
|
||||||
from calibre.db.lazy import FormatMetadata, FormatsList
|
from calibre.db.lazy import FormatMetadata, FormatsList
|
||||||
|
from calibre.ebooks.metadata import string_to_authors
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.ptempfile import (base_dir, PersistentTemporaryFile,
|
from calibre.ptempfile import (base_dir, PersistentTemporaryFile,
|
||||||
@ -669,7 +670,7 @@ class Cache(object):
|
|||||||
self.dirtied_cache.update(new_dirtied)
|
self.dirtied_cache.update(new_dirtied)
|
||||||
|
|
||||||
@write_api
|
@write_api
|
||||||
def set_field(self, name, book_id_to_val_map, allow_case_change=True):
|
def set_field(self, name, book_id_to_val_map, allow_case_change=True, do_path_update=True):
|
||||||
f = self.fields[name]
|
f = self.fields[name]
|
||||||
is_series = f.metadata['datatype'] == 'series'
|
is_series = f.metadata['datatype'] == 'series'
|
||||||
update_path = name in {'title', 'authors'}
|
update_path = name in {'title', 'authors'}
|
||||||
@ -702,7 +703,7 @@ class Cache(object):
|
|||||||
for name in self.composites:
|
for name in self.composites:
|
||||||
self.fields[name].pop_cache(dirtied)
|
self.fields[name].pop_cache(dirtied)
|
||||||
|
|
||||||
if dirtied and update_path:
|
if dirtied and update_path and do_path_update:
|
||||||
self._update_path(dirtied, mark_as_dirtied=False)
|
self._update_path(dirtied, mark_as_dirtied=False)
|
||||||
|
|
||||||
self._mark_as_dirty(dirtied)
|
self._mark_as_dirty(dirtied)
|
||||||
@ -822,6 +823,102 @@ class Cache(object):
|
|||||||
if callback is not None:
|
if callback is not None:
|
||||||
callback(book_id, mi, True)
|
callback(book_id, mi, True)
|
||||||
|
|
||||||
|
@write_api
|
||||||
|
def set_cover(self, book_id_data_map):
|
||||||
|
''' Set the cover for this book. data can be either a QImage,
|
||||||
|
QPixmap, file object or bytestring '''
|
||||||
|
|
||||||
|
for book_id, data in book_id_data_map.iteritems():
|
||||||
|
try:
|
||||||
|
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||||
|
except AttributeError:
|
||||||
|
self._update_path((book_id,))
|
||||||
|
path = self._field_for('path', book_id).replace('/', os.sep)
|
||||||
|
|
||||||
|
self.backend.set_cover(book_id, path, data)
|
||||||
|
self._set_field('cover', {book_id:1 for book_id in book_id_data_map})
|
||||||
|
|
||||||
|
@write_api
|
||||||
|
def set_metadata(self, book_id, mi, ignore_errors=False, force_changes=False,
|
||||||
|
set_title=True, set_authors=True):
|
||||||
|
if callable(getattr(mi, 'to_book_metadata', None)):
|
||||||
|
# Handle code passing in an OPF object instead of a Metadata object
|
||||||
|
mi = mi.to_book_metadata()
|
||||||
|
|
||||||
|
def set_field(name, val, **kwargs):
|
||||||
|
self._set_field(name, {book_id:val}, **kwargs)
|
||||||
|
|
||||||
|
path_changed = False
|
||||||
|
if set_title and mi.title:
|
||||||
|
path_changed = True
|
||||||
|
set_field('title', mi.title, do_path_update=False)
|
||||||
|
if set_authors:
|
||||||
|
path_changed = True
|
||||||
|
if not mi.authors:
|
||||||
|
mi.authors = [_('Unknown')]
|
||||||
|
authors = []
|
||||||
|
for a in mi.authors:
|
||||||
|
authors += string_to_authors(a)
|
||||||
|
set_field('authors', authors, do_path_update=False)
|
||||||
|
|
||||||
|
if path_changed:
|
||||||
|
self._update_path((book_id,))
|
||||||
|
|
||||||
|
def protected_set_field(name, val, **kwargs):
|
||||||
|
try:
|
||||||
|
set_field(name, val, **kwargs)
|
||||||
|
except:
|
||||||
|
if ignore_errors:
|
||||||
|
traceback.print_exc()
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
for field in ('rating', 'series_index', 'timestamp'):
|
||||||
|
val = getattr(mi, field)
|
||||||
|
if val is not None:
|
||||||
|
protected_set_field(field, val)
|
||||||
|
|
||||||
|
# force_changes has no effect on cover manipulation
|
||||||
|
cdata = mi.cover_data[1]
|
||||||
|
if cdata is None and isinstance(mi.cover, basestring) and mi.cover and os.access(mi.cover, os.R_OK):
|
||||||
|
with lopen(mi.cover, 'rb') as f:
|
||||||
|
raw = f.read()
|
||||||
|
if raw:
|
||||||
|
cdata = raw
|
||||||
|
if cdata is not None:
|
||||||
|
self._set_cover({book_id: cdata})
|
||||||
|
|
||||||
|
for field in ('title_sort', 'author_sort', 'publisher', 'series',
|
||||||
|
'tags', 'comments', 'languages', 'pubdate'):
|
||||||
|
val = mi.get(field, None)
|
||||||
|
if (force_changes and val is not None) or not mi.is_null(field):
|
||||||
|
protected_set_field(field, val)
|
||||||
|
|
||||||
|
# identifiers will always be replaced if force_changes is True
|
||||||
|
mi_idents = mi.get_identifiers()
|
||||||
|
if force_changes:
|
||||||
|
protected_set_field('identifiers', mi_idents)
|
||||||
|
elif mi_idents:
|
||||||
|
identifiers = self._field_for('identifiers', book_id, default_value={})
|
||||||
|
for key, val in mi_idents.iteritems():
|
||||||
|
if val and val.strip(): # Don't delete an existing identifier
|
||||||
|
identifiers[icu_lower(key)] = val
|
||||||
|
protected_set_field('identifiers', identifiers)
|
||||||
|
|
||||||
|
user_mi = mi.get_all_user_metadata(make_copy=False)
|
||||||
|
fm = self.field_metadata
|
||||||
|
for key in user_mi.iterkeys():
|
||||||
|
if (key in fm and
|
||||||
|
user_mi[key]['datatype'] == fm[key]['datatype'] and
|
||||||
|
(user_mi[key]['datatype'] != 'text' or
|
||||||
|
user_mi[key]['is_multiple'] == fm[key]['is_multiple'])):
|
||||||
|
val = mi.get(key, None)
|
||||||
|
if force_changes or val is not None:
|
||||||
|
protected_set_field(key, val)
|
||||||
|
extra = mi.get_extra(key)
|
||||||
|
if extra is not None:
|
||||||
|
protected_set_field(key+'_index', extra)
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class SortKey(object): # {{{
|
class SortKey(object): # {{{
|
||||||
|
@ -355,4 +355,7 @@ class WritingTest(BaseTest):
|
|||||||
ae(opf.authors, ['author1', 'author2'])
|
ae(opf.authors, ['author1', 'author2'])
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
def test_set_cover(self):
|
||||||
|
' Test setting of cover '
|
||||||
|
# TODO: test set_cover() and set_metadata()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user