mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
d675e36b19
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 160 KiB |
Binary file not shown.
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 396 KiB |
Binary file not shown.
@ -12,15 +12,14 @@ from calibre import fit_image
|
|||||||
from calibre.constants import isosx, iswindows
|
from calibre.constants import isosx, iswindows
|
||||||
from calibre.devices.errors import UserFeedback
|
from calibre.devices.errors import UserFeedback
|
||||||
from calibre.devices.interface import DevicePlugin
|
from calibre.devices.interface import DevicePlugin
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.metadata.epub import set_metadata
|
from calibre.ebooks.metadata.epub import set_metadata
|
||||||
from calibre.library.server.utils import strftime
|
from calibre.library.server.utils import strftime
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
|
||||||
from calibre.utils.config import Config, config_dir
|
from calibre.utils.config import Config, config_dir
|
||||||
from calibre.utils.date import fromtimestamp, isoformat, now, parse_date, strptime
|
from calibre.utils.date import isoformat, now, parse_date
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
from calibre.utils.zipfile import safe_replace, ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
|
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
@ -34,7 +33,6 @@ if isosx:
|
|||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import pythoncom, win32com.client
|
import pythoncom, win32com.client
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
|
|
||||||
class ITUNES(DevicePlugin):
|
class ITUNES(DevicePlugin):
|
||||||
@ -1665,8 +1663,6 @@ class ITUNES(DevicePlugin):
|
|||||||
'''
|
'''
|
||||||
assumes pythoncom wrapper
|
assumes pythoncom wrapper
|
||||||
'''
|
'''
|
||||||
# if DEBUG:
|
|
||||||
# self.log.info(" ITUNES._get_device_books_playlist()")
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
pl = None
|
pl = None
|
||||||
@ -1709,11 +1705,6 @@ class ITUNES(DevicePlugin):
|
|||||||
if update_md:
|
if update_md:
|
||||||
self._update_epub_metadata(fpath, metadata)
|
self._update_epub_metadata(fpath, metadata)
|
||||||
|
|
||||||
# if DEBUG:
|
|
||||||
# self.log.info(" metadata before rewrite: '{0[0]}' '{0[1]}' '{0[2]}'".format(self._dump_epub_metadata(fpath)))
|
|
||||||
# self._update_epub_metadata(fpath, metadata)
|
|
||||||
# if DEBUG:
|
|
||||||
# self.log.info(" metadata after rewrite: '{0[0]}' '{0[1]}' '{0[2]}'".format(self._dump_epub_metadata(fpath)))
|
|
||||||
return fpath
|
return fpath
|
||||||
|
|
||||||
def _get_library_books(self):
|
def _get_library_books(self):
|
||||||
@ -2132,21 +2123,6 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
# Refresh epub metadata
|
# Refresh epub metadata
|
||||||
with open(fpath,'r+b') as zfo:
|
with open(fpath,'r+b') as zfo:
|
||||||
'''
|
|
||||||
# Touch the timestamp to force a recache
|
|
||||||
if metadata.timestamp:
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" old timestamp: %s" % metadata.timestamp)
|
|
||||||
old_ts = metadata.timestamp
|
|
||||||
metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
|
|
||||||
old_ts.minute, old_ts.second, old_ts.microsecond+1, old_ts.tzinfo)
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" new timestamp: %s" % metadata.timestamp)
|
|
||||||
else:
|
|
||||||
metadata.timestamp = isoformat(now())
|
|
||||||
if DEBUG:
|
|
||||||
self.log.info(" add timestamp: %s" % metadata.timestamp)
|
|
||||||
'''
|
|
||||||
# Touch the OPF timestamp
|
# Touch the OPF timestamp
|
||||||
zf_opf = ZipFile(fpath,'r')
|
zf_opf = ZipFile(fpath,'r')
|
||||||
fnames = zf_opf.namelist()
|
fnames = zf_opf.namelist()
|
||||||
@ -2186,7 +2162,7 @@ class ITUNES(DevicePlugin):
|
|||||||
if iswindows and metadata.series:
|
if iswindows and metadata.series:
|
||||||
metadata.tags = None
|
metadata.tags = None
|
||||||
|
|
||||||
set_metadata(zfo,metadata)
|
set_metadata(zfo, metadata, update_timestamp=True)
|
||||||
|
|
||||||
def _update_device(self, msg='', wait=True):
|
def _update_device(self, msg='', wait=True):
|
||||||
'''
|
'''
|
||||||
|
@ -55,6 +55,7 @@ class PRS505(USBMS):
|
|||||||
|
|
||||||
SUPPORTS_SUB_DIRS = True
|
SUPPORTS_SUB_DIRS = True
|
||||||
MUST_READ_METADATA = True
|
MUST_READ_METADATA = True
|
||||||
|
SUPPORTS_USE_AUTHOR_SORT = True
|
||||||
EBOOK_DIR_MAIN = 'database/media/books'
|
EBOOK_DIR_MAIN = 'database/media/books'
|
||||||
|
|
||||||
EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of metadata fields '
|
EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of metadata fields '
|
||||||
@ -125,7 +126,7 @@ class PRS505(USBMS):
|
|||||||
d = os.path.dirname(paths[source_id])
|
d = os.path.dirname(paths[source_id])
|
||||||
if not os.path.exists(d):
|
if not os.path.exists(d):
|
||||||
os.makedirs(d)
|
os.makedirs(d)
|
||||||
return XMLCache(paths, prefixes)
|
return XMLCache(paths, prefixes, self.settings().use_author_sort)
|
||||||
|
|
||||||
def books(self, oncard=None, end_session=True):
|
def books(self, oncard=None, end_session=True):
|
||||||
debug_print('PRS505: starting fetching books for card', oncard)
|
debug_print('PRS505: starting fetching books for card', oncard)
|
||||||
|
@ -60,12 +60,13 @@ def uuid():
|
|||||||
|
|
||||||
class XMLCache(object):
|
class XMLCache(object):
|
||||||
|
|
||||||
def __init__(self, paths, prefixes):
|
def __init__(self, paths, prefixes, use_author_sort):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
debug_print('Building XMLCache...')
|
debug_print('Building XMLCache...')
|
||||||
pprint(paths)
|
pprint(paths)
|
||||||
self.paths = paths
|
self.paths = paths
|
||||||
self.prefixes = prefixes
|
self.prefixes = prefixes
|
||||||
|
self.use_author_sort = use_author_sort
|
||||||
|
|
||||||
# Parse XML files {{{
|
# Parse XML files {{{
|
||||||
parser = etree.XMLParser(recover=True)
|
parser = etree.XMLParser(recover=True)
|
||||||
@ -434,7 +435,10 @@ class XMLCache(object):
|
|||||||
if not ts:
|
if not ts:
|
||||||
ts = title_sort(title)
|
ts = title_sort(title)
|
||||||
record.set('titleSorter', ts)
|
record.set('titleSorter', ts)
|
||||||
record.set('author', authors_to_string(book.authors))
|
if self.use_author_sort and book.author_sort is not None:
|
||||||
|
record.set('author', book.author_sort)
|
||||||
|
else:
|
||||||
|
record.set('author', authors_to_string(book.authors))
|
||||||
ext = os.path.splitext(path)[1]
|
ext = os.path.splitext(path)[1]
|
||||||
if ext:
|
if ext:
|
||||||
ext = ext[1:].lower()
|
ext = ext[1:].lower()
|
||||||
|
@ -80,6 +80,7 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
|
|
||||||
SUPPORTS_SUB_DIRS = False
|
SUPPORTS_SUB_DIRS = False
|
||||||
MUST_READ_METADATA = False
|
MUST_READ_METADATA = False
|
||||||
|
SUPPORTS_USE_AUTHOR_SORT = False
|
||||||
|
|
||||||
EBOOK_DIR_MAIN = ''
|
EBOOK_DIR_MAIN = ''
|
||||||
EBOOK_DIR_CARD_A = ''
|
EBOOK_DIR_CARD_A = ''
|
||||||
|
@ -32,6 +32,8 @@ class DeviceConfig(object):
|
|||||||
help=_('Place files in sub directories if the device supports them'))
|
help=_('Place files in sub directories if the device supports them'))
|
||||||
c.add_opt('read_metadata', default=True,
|
c.add_opt('read_metadata', default=True,
|
||||||
help=_('Read metadata from files on device'))
|
help=_('Read metadata from files on device'))
|
||||||
|
c.add_opt('use_author_sort', default=False,
|
||||||
|
help=_('Use author sort instead of author'))
|
||||||
c.add_opt('save_template', default=cls._default_save_template(),
|
c.add_opt('save_template', default=cls._default_save_template(),
|
||||||
help=_('Template to control how books are saved'))
|
help=_('Template to control how books are saved'))
|
||||||
c.add_opt('extra_customization',
|
c.add_opt('extra_customization',
|
||||||
@ -47,7 +49,8 @@ class DeviceConfig(object):
|
|||||||
def config_widget(cls):
|
def config_widget(cls):
|
||||||
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
from calibre.gui2.device_drivers.configwidget import ConfigWidget
|
||||||
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
cw = ConfigWidget(cls.settings(), cls.FORMATS, cls.SUPPORTS_SUB_DIRS,
|
||||||
cls.MUST_READ_METADATA, cls.EXTRA_CUSTOMIZATION_MESSAGE)
|
cls.MUST_READ_METADATA, cls.SUPPORTS_USE_AUTHOR_SORT,
|
||||||
|
cls.EXTRA_CUSTOMIZATION_MESSAGE)
|
||||||
return cw
|
return cw
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -58,6 +61,8 @@ class DeviceConfig(object):
|
|||||||
proxy['use_subdirs'] = config_widget.use_subdirs()
|
proxy['use_subdirs'] = config_widget.use_subdirs()
|
||||||
if not cls.MUST_READ_METADATA:
|
if not cls.MUST_READ_METADATA:
|
||||||
proxy['read_metadata'] = config_widget.read_metadata()
|
proxy['read_metadata'] = config_widget.read_metadata()
|
||||||
|
if cls.SUPPORTS_USE_AUTHOR_SORT:
|
||||||
|
proxy['use_author_sort'] = config_widget.use_author_sort()
|
||||||
if cls.EXTRA_CUSTOMIZATION_MESSAGE:
|
if cls.EXTRA_CUSTOMIZATION_MESSAGE:
|
||||||
ec = unicode(config_widget.opt_extra_customization.text()).strip()
|
ec = unicode(config_widget.opt_extra_customization.text()).strip()
|
||||||
if not ec:
|
if not ec:
|
||||||
|
@ -299,7 +299,7 @@ class USBMS(CLI, Device):
|
|||||||
def replfunc(match):
|
def replfunc(match):
|
||||||
if match.group(1) in ['title', 'series', 'series_index', 'isbn']:
|
if match.group(1) in ['title', 'series', 'series_index', 'isbn']:
|
||||||
return '(?P<' + match.group(1) + '>.+?)'
|
return '(?P<' + match.group(1) + '>.+?)'
|
||||||
elif match.group(1) == 'authors':
|
elif match.group(1) in ['authors', 'author_sort']:
|
||||||
return '(?P<author>.+?)'
|
return '(?P<author>.+?)'
|
||||||
else:
|
else:
|
||||||
return '(.+?)'
|
return '(.+?)'
|
||||||
|
@ -182,7 +182,7 @@ def get_metadata(stream, extract_cover=True):
|
|||||||
def get_quick_metadata(stream):
|
def get_quick_metadata(stream):
|
||||||
return get_metadata(stream, False)
|
return get_metadata(stream, False)
|
||||||
|
|
||||||
def set_metadata(stream, mi, apply_null=False):
|
def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
|
||||||
stream.seek(0)
|
stream.seek(0)
|
||||||
reader = OCFZipReader(stream, root=os.getcwdu())
|
reader = OCFZipReader(stream, root=os.getcwdu())
|
||||||
mi = MetaInformation(mi)
|
mi = MetaInformation(mi)
|
||||||
@ -196,6 +196,8 @@ def set_metadata(stream, mi, apply_null=False):
|
|||||||
reader.opf.tags = []
|
reader.opf.tags = []
|
||||||
if not getattr(mi, 'isbn', None):
|
if not getattr(mi, 'isbn', None):
|
||||||
reader.opf.isbn = None
|
reader.opf.isbn = None
|
||||||
|
if update_timestamp and mi.timestamp is not None:
|
||||||
|
reader.opf.timestamp = mi.timestamp
|
||||||
|
|
||||||
newopf = StringIO(reader.opf.render())
|
newopf = StringIO(reader.opf.render())
|
||||||
safe_replace(stream, reader.container[OPF.MIMETYPE], newopf)
|
safe_replace(stream, reader.container[OPF.MIMETYPE], newopf)
|
||||||
|
@ -920,7 +920,7 @@ class OPF(object):
|
|||||||
for attr in ('title', 'authors', 'author_sort', 'title_sort',
|
for attr in ('title', 'authors', 'author_sort', 'title_sort',
|
||||||
'publisher', 'series', 'series_index', 'rating',
|
'publisher', 'series', 'series_index', 'rating',
|
||||||
'isbn', 'language', 'tags', 'category', 'comments',
|
'isbn', 'language', 'tags', 'category', 'comments',
|
||||||
'pubdate','timestamp'):
|
'pubdate'):
|
||||||
val = getattr(mi, attr, None)
|
val = getattr(mi, attr, None)
|
||||||
if val is not None and val != [] and val != (None, None):
|
if val is not None and val != [] and val != (None, None):
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
|
@ -11,7 +11,8 @@ from calibre.gui2.device_drivers.configwidget_ui import Ui_ConfigWidget
|
|||||||
class ConfigWidget(QWidget, Ui_ConfigWidget):
|
class ConfigWidget(QWidget, Ui_ConfigWidget):
|
||||||
|
|
||||||
def __init__(self, settings, all_formats, supports_subdirs,
|
def __init__(self, settings, all_formats, supports_subdirs,
|
||||||
must_read_metadata, extra_customization_message):
|
must_read_metadata, supports_use_author_sort,
|
||||||
|
extra_customization_message):
|
||||||
|
|
||||||
QWidget.__init__(self)
|
QWidget.__init__(self)
|
||||||
Ui_ConfigWidget.__init__(self)
|
Ui_ConfigWidget.__init__(self)
|
||||||
@ -38,6 +39,10 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
self.opt_read_metadata.setChecked(self.settings.read_metadata)
|
self.opt_read_metadata.setChecked(self.settings.read_metadata)
|
||||||
else:
|
else:
|
||||||
self.opt_read_metadata.hide()
|
self.opt_read_metadata.hide()
|
||||||
|
if supports_use_author_sort:
|
||||||
|
self.opt_use_author_sort.setChecked(self.settings.use_author_sort)
|
||||||
|
else:
|
||||||
|
self.opt_use_author_sort.hide()
|
||||||
if extra_customization_message:
|
if extra_customization_message:
|
||||||
self.extra_customization_label.setText(extra_customization_message)
|
self.extra_customization_label.setText(extra_customization_message)
|
||||||
if settings.extra_customization:
|
if settings.extra_customization:
|
||||||
@ -69,3 +74,6 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
|
|
||||||
def read_metadata(self):
|
def read_metadata(self):
|
||||||
return self.opt_read_metadata.isChecked()
|
return self.opt_read_metadata.isChecked()
|
||||||
|
|
||||||
|
def use_author_sort(self):
|
||||||
|
return self.opt_use_author_sort.isChecked()
|
||||||
|
@ -90,7 +90,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="3" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_use_author_sort">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use author sort for author</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
<widget class="QLabel" name="extra_customization_label">
|
<widget class="QLabel" name="extra_customization_label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Extra customization</string>
|
<string>Extra customization</string>
|
||||||
@ -103,10 +110,10 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="6" column="0">
|
<item row="7" column="0">
|
||||||
<widget class="QLineEdit" name="opt_extra_customization"/>
|
<widget class="QLineEdit" name="opt_extra_customization"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="4" column="0">
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save &template:</string>
|
<string>Save &template:</string>
|
||||||
@ -116,7 +123,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="5" column="0">
|
||||||
<widget class="QLineEdit" name="opt_save_template"/>
|
<widget class="QLineEdit" name="opt_save_template"/>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -163,8 +163,7 @@ class ToolbarMixin(object): # {{{
|
|||||||
self.convert_menu = cm
|
self.convert_menu = cm
|
||||||
|
|
||||||
pm = QMenu()
|
pm = QMenu()
|
||||||
ap = self.action_preferences
|
pm.addAction(QIcon(I('config.svg')), _('Preferences'), self.do_config)
|
||||||
pm.addAction(ap)
|
|
||||||
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
||||||
self.run_wizard)
|
self.run_wizard)
|
||||||
self.action_preferences.setMenu(pm)
|
self.action_preferences.setMenu(pm)
|
||||||
|
@ -84,12 +84,12 @@ class DownloadMetadata(Thread):
|
|||||||
if mi.isbn:
|
if mi.isbn:
|
||||||
args['isbn'] = mi.isbn
|
args['isbn'] = mi.isbn
|
||||||
else:
|
else:
|
||||||
if not mi.title:
|
if not mi.title or mi.title == _('Unknown'):
|
||||||
self.failures[id] = \
|
self.failures[id] = \
|
||||||
(str(id), _('Book has neither title nor ISBN'))
|
(str(id), _('Book has neither title nor ISBN'))
|
||||||
continue
|
continue
|
||||||
args['title'] = mi.title
|
args['title'] = mi.title
|
||||||
if mi.authors:
|
if mi.authors and mi.authors[0] != _('Unknown'):
|
||||||
args['author'] = mi.authors[0]
|
args['author'] = mi.authors[0]
|
||||||
if self.key:
|
if self.key:
|
||||||
args['isbndb_key'] = self.key
|
args['isbndb_key'] = self.key
|
||||||
|
Loading…
x
Reference in New Issue
Block a user