Merge from trunk

This commit is contained in:
Charles Haley 2010-10-06 21:06:15 +01:00
commit 6623db3063
2 changed files with 190 additions and 89 deletions

View File

@ -17,7 +17,7 @@ from calibre.ebooks.metadata import authors_to_string, MetaInformation
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime
from calibre.utils.config import config_dir
from calibre.utils.config import config_dir, prefs
from calibre.utils.date import isoformat, now, parse_date
from calibre.utils.localization import get_lang
from calibre.utils.logging import Log
@ -68,6 +68,8 @@ class ITUNES(DriverBase):
Delete:
delete_books()
remove_books_from_metadata()
use_plugboard_ext()
set_plugboard()
sync_booklists()
card_prefix()
free_space()
@ -76,6 +78,8 @@ class ITUNES(DriverBase):
set_progress_reporter()
upload_books()
add_books_to_metadata()
use_plugboard_ext()
set_plugboard()
set_progress_reporter()
sync_booklists()
card_prefix()
@ -106,6 +110,9 @@ class ITUNES(DriverBase):
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a]
BCD = [0x01]
# Plugboard ID
DEVICE_PLUGBOARD_NAME = 'APPLE'
# iTunes enumerations
Audiobooks = [
'Audible file',
@ -164,6 +171,7 @@ class ITUNES(DriverBase):
# Properties
cached_books = {}
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
calibre_library_path = prefs['library_path']
archive_path = os.path.join(cache_dir, "thumbs.zip")
description_prefix = "added by calibre"
ejected = False
@ -173,6 +181,8 @@ class ITUNES(DriverBase):
log = Log()
manual_sync_mode = False
path_template = 'iTunes/%s - %s.%s'
plugboards = None
plugboard_func = None
problem_titles = []
problem_msg = None
report_progress = None
@ -814,6 +824,15 @@ class ITUNES(DriverBase):
'''
self.report_progress = report_progress
def set_plugboards(self, plugboards, pb_func):
# This method is called with the plugboard that matches the format
# declared in use_plugboard_ext and a device name of ITUNES
if DEBUG:
self.log.info("ITUNES.set_plugboard()")
#self.log.info(' using plugboard %s' % plugboards)
self.plugboards = plugboards
self.plugboard_func = pb_func
def sync_booklists(self, booklists, end_session=True):
'''
Update metadata on device.
@ -977,7 +996,6 @@ class ITUNES(DriverBase):
self._dump_cached_books(header="after upload_books()",indent=2)
return (new_booklist, [], [])
# Private methods
def _add_device_book(self,fpath, metadata):
'''
@ -1256,7 +1274,10 @@ class ITUNES(DriverBase):
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title))
finally:
zfw.close()
try:
zfw.close()
except:
pass
else:
if DEBUG:
self.log.info(" no cover defined in metadata for '%s'" % metadata.title)
@ -1273,10 +1294,10 @@ class ITUNES(DriverBase):
this_book.db_id = None
this_book.device_collections = []
this_book.format = format
this_book.library_id = lb_added
this_book.library_id = lb_added # ??? GR
this_book.path = path
this_book.thumbnail = thumb
this_book.iTunes_id = lb_added
this_book.iTunes_id = lb_added # ??? GR
this_book.uuid = metadata.uuid
if isosx:
@ -1322,8 +1343,8 @@ class ITUNES(DriverBase):
plist = None
if plist:
if DEBUG:
self.log.info(" _delete_iTunesMetadata_plist():")
self.log.info(" deleting '%s'\n from '%s'" % (pl_name,fpath))
self.log.info(" _delete_iTunesMetadata_plist():")
self.log.info(" deleting '%s'\n from '%s'" % (pl_name,fpath))
zf.delete(pl_name)
zf.close()
@ -2213,6 +2234,7 @@ class ITUNES(DriverBase):
(self.iTunes.name(), self.iTunes.version(), self.initial_status,
self.version[0],self.version[1],self.version[2]))
self.log.info(" iTunes_media: %s" % self.iTunes_media)
self.log.info(" calibre_library_path: %s" % self.calibre_library_path)
if iswindows:
'''
@ -2266,6 +2288,7 @@ class ITUNES(DriverBase):
(self.iTunes.Windows[0].name, self.iTunes.Version, self.initial_status,
self.version[0],self.version[1],self.version[2]))
self.log.info(" iTunes_media: %s" % self.iTunes_media)
self.log.info(" calibre_library_path: %s" % self.calibre_library_path)
def _purge_orphans(self,library_books, cached_books):
'''
@ -2368,7 +2391,8 @@ class ITUNES(DriverBase):
'''
iTunes does not delete books from storage when removing from database
We only want to delete stored copies if the file is stored in iTunes
We don't want to delete files stored outside of iTunes
We don't want to delete files stored outside of iTunes.
Also confirm that storage_path does not point into calibre's storage.
'''
if DEBUG:
self.log.info(" ITUNES._remove_from_iTunes():")
@ -2376,7 +2400,8 @@ class ITUNES(DriverBase):
if isosx:
try:
storage_path = os.path.split(cached_book['lib_book'].location().path)
if cached_book['lib_book'].location().path.startswith(self.iTunes_media):
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \
not storage_path[0].startswith(self.calibre_library_path):
title_storage_path = storage_path[0]
if DEBUG:
self.log.info(" removing title_storage_path: %s" % title_storage_path)
@ -2427,7 +2452,8 @@ class ITUNES(DriverBase):
path = book.Location
if book:
if self.iTunes_media and path.startswith(self.iTunes_media):
if self.iTunes_media and path.startswith(self.iTunes_media) and \
not path.startswith(self.calibre_library_path):
storage_path = os.path.split(path)
if DEBUG:
self.log.info(" removing '%s' at %s" %
@ -2454,11 +2480,17 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" unable to remove '%s' from iTunes" % cached_book['title'])
def title_sorter(self, title):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip()
def _update_epub_metadata(self, fpath, metadata):
'''
'''
self.log.info(" ITUNES._update_epub_metadata()")
# Fetch plugboard updates
metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub')
# Refresh epub metadata
with open(fpath,'r+b') as zfo:
# Touch the OPF timestamp
@ -2490,9 +2522,14 @@ class ITUNES(DriverBase):
self.log.info(" add timestamp: %s" % metadata.timestamp)
# Force the language declaration for iBooks 1.1
metadata.language = get_lang().replace('_', '-')
#metadata.language = get_lang().replace('_', '-')
# Updates from metadata plugboard (ignoring publisher)
metadata.language = metadata_x.language
if DEBUG:
self.log.info(" rewriting language: <dc:language>%s</dc:language>" % metadata.language)
if metadata.language != metadata_x.language:
self.log.info(" rewriting language: <dc:language>%s</dc:language>" % metadata.language)
zf_opf.close()
@ -2570,75 +2607,97 @@ class ITUNES(DriverBase):
if DEBUG:
self.log.info(" ITUNES._update_iTunes_metadata()")
strip_tags = re.compile(r'<[^<]*?/?>')
STRIP_TAGS = re.compile(r'<[^<]*?/?>')
# Update metadata from plugboard
# If self.plugboard is None (no transforms), original metadata is returned intact
metadata_x = self._xform_metadata_via_plugboard(metadata, this_book.format)
if isosx:
if lb_added:
lb_added.album.set(metadata.title)
lb_added.artist.set(authors_to_string(metadata.authors))
lb_added.composer.set(metadata.uuid)
lb_added.name.set(metadata_x.title)
lb_added.album.set(metadata_x.title)
lb_added.artist.set(authors_to_string(metadata_x.authors))
lb_added.composer.set(metadata_x.uuid)
lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.enabled.set(True)
lb_added.sort_artist.set(metadata.author_sort.title())
lb_added.sort_name.set(this_book.title_sorter)
if this_book.format == 'pdf':
lb_added.name.set(metadata.title)
lb_added.sort_artist.set(metadata_x.author_sort.title())
lb_added.sort_name.set(metadata.title_sort)
if db_added:
db_added.album.set(metadata.title)
db_added.artist.set(authors_to_string(metadata.authors))
db_added.composer.set(metadata.uuid)
db_added.name.set(metadata_x.title)
db_added.album.set(metadata_x.title)
db_added.artist.set(authors_to_string(metadata_x.authors))
db_added.composer.set(metadata_x.uuid)
db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.enabled.set(True)
db_added.sort_artist.set(metadata.author_sort.title())
db_added.sort_name.set(this_book.title_sorter)
if this_book.format == 'pdf':
db_added.name.set(metadata.title)
db_added.sort_artist.set(metadata_x.author_sort.title())
db_added.sort_name.set(metadata.title_sort)
if metadata.comments:
if metadata_x.comments:
if lb_added:
lb_added.comment.set(strip_tags.sub('',metadata.comments))
lb_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments))
if db_added:
db_added.comment.set(strip_tags.sub('',metadata.comments))
db_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments))
if metadata.rating:
if metadata_x.rating:
if lb_added:
lb_added.rating.set(metadata.rating*10)
lb_added.rating.set(metadata_x.rating*10)
# iBooks currently doesn't allow setting rating ... ?
try:
if db_added:
db_added.rating.set(metadata.rating*10)
db_added.rating.set(metadata_x.rating*10)
except:
pass
# Set genre from series if available, else first alpha tag
# Otherwise iTunes grabs the first dc:subject from the opf metadata
if metadata.series and self.settings().read_metadata:
# self.settings().read_metadata is used as a surrogate for "Use Series name as Genre"
if metadata_x.series and self.settings().read_metadata:
if DEBUG:
self.log.info(" ITUNES._update_iTunes_metadata()")
self.log.info(" using Series name as Genre")
# Format the index as a sort key
index = metadata.series_index
index = metadata_x.series_index
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
if lb_added:
lb_added.sort_name.set("%s %s" % (metadata.series, series_index))
lb_added.genre.set(metadata.series)
lb_added.episode_ID.set(metadata.series)
lb_added.episode_number.set(metadata.series_index)
lb_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
lb_added.episode_ID.set(metadata_x.series)
lb_added.episode_number.set(metadata_x.series_index)
# If no plugboard transform applied to tags, change the Genre/Category to Series
if metadata.tags == metadata_x.tags:
lb_added.genre.set(self.title_sorter(metadata_x.series))
else:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
lb_added.genre.set(tag)
break
if db_added:
db_added.sort_name.set("%s %s" % (metadata.series, series_index))
db_added.genre.set(metadata.series)
db_added.episode_ID.set(metadata.series)
db_added.episode_number.set(metadata.series_index)
db_added.sort_name.set("%s %s" % (self.title_sorter(metadata_x.series), series_index))
db_added.episode_ID.set(metadata_x.series)
db_added.episode_number.set(metadata_x.series_index)
elif metadata.tags:
# If no plugboard transform applied to tags, change the Genre/Category to Series
if metadata.tags == metadata_x.tags:
db_added.genre.set(self.title_sorter(metadata_x.series))
else:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
db_added.genre.set(tag)
break
elif metadata_x.tags is not None:
if DEBUG:
self.log.info(" %susing Tag as Genre" %
"no Series name available, " if self.settings().read_metadata else '')
for tag in metadata.tags:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
if lb_added:
lb_added.genre.set(tag)
@ -2648,40 +2707,38 @@ class ITUNES(DriverBase):
elif iswindows:
if lb_added:
lb_added.Album = metadata.title
lb_added.Artist = authors_to_string(metadata.authors)
lb_added.Composer = metadata.uuid
lb_added.Name = metadata_x.title
lb_added.Album = metadata_x.title
lb_added.Artist = authors_to_string(metadata_x.authors)
lb_added.Composer = metadata_x.uuid
lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.Enabled = True
lb_added.SortArtist = (metadata.author_sort.title())
lb_added.SortName = (this_book.title_sorter)
if this_book.format == 'pdf':
lb_added.Name = metadata.title
lb_added.SortArtist = metadata_x.author_sort.title()
lb_added.SortName = metadata.title_sort
if db_added:
db_added.Album = metadata.title
db_added.Artist = authors_to_string(metadata.authors)
db_added.Composer = metadata.uuid
db_added.Name = metadata_x.title
db_added.Album = metadata_x.title
db_added.Artist = authors_to_string(metadata_x.authors)
db_added.Composer = metadata_x.uuid
db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.Enabled = True
db_added.SortArtist = (metadata.author_sort.title())
db_added.SortName = (this_book.title_sorter)
if this_book.format == 'pdf':
db_added.Name = metadata.title
db_added.SortArtist = metadata_x.author_sort.title()
db_added.SortName = metadata.title_sort
if metadata.comments:
if metadata_x.comments:
if lb_added:
lb_added.Comment = (strip_tags.sub('',metadata.comments))
lb_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments))
if db_added:
db_added.Comment = (strip_tags.sub('',metadata.comments))
db_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments))
if metadata.rating:
if metadata_x.rating:
if lb_added:
lb_added.AlbumRating = (metadata.rating*10)
lb_added.AlbumRating = (metadata_x.rating*10)
# iBooks currently doesn't allow setting rating ... ?
try:
if db_added:
db_added.AlbumRating = (metadata.rating*10)
db_added.AlbumRating = (metadata_x.rating*10)
except:
if DEBUG:
self.log.warning(" iTunes automation interface reported an error"
@ -2691,36 +2748,54 @@ class ITUNES(DriverBase):
# Otherwise iBooks uses first <dc:subject> from opf
# iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12)
if metadata.series and self.settings().read_metadata:
if metadata_x.series and self.settings().read_metadata:
if DEBUG:
self.log.info(" using Series name as Genre")
# Format the index as a sort key
index = metadata.series_index
index = metadata_x.series_index
integer = int(index)
fraction = index-integer
series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
if lb_added:
lb_added.SortName = "%s %s" % (metadata.series, series_index)
lb_added.Genre = metadata.series
lb_added.EpisodeID = metadata.series
lb_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
lb_added.EpisodeID = metadata_x.series
try:
lb_added.EpisodeNumber = metadata.series_index
lb_added.EpisodeNumber = metadata_x.series_index
except:
pass
# If no plugboard transform applied to tags, change the Genre/Category to Series
if metadata.tags == metadata_x.tags:
lb_added.Genre = self.title_sorter(metadata_x.series)
else:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
lb_added.Genre = tag
break
if db_added:
db_added.SortName = "%s %s" % (metadata.series, series_index)
db_added.Genre = metadata.series
db_added.EpisodeID = metadata.series
db_added.SortName = "%s %s" % (self.title_sorter(metadata_x.series), series_index)
db_added.EpisodeID = metadata_x.series
try:
db_added.EpisodeNumber = metadata.series_index
db_added.EpisodeNumber = metadata_x.series_index
except:
if DEBUG:
self.log.warning(" iTunes automation interface reported an error"
" setting EpisodeNumber on iDevice")
elif metadata.tags:
# If no plugboard transform applied to tags, change the Genre/Category to Series
if metadata.tags == metadata_x.tags:
db_added.Genre = self.title_sorter(metadata_x.series)
else:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
db_added.Genre = tag
break
elif metadata_x.tags is not None:
if DEBUG:
self.log.info(" using Tag as Genre")
for tag in metadata.tags:
for tag in metadata_x.tags:
if self._is_alpha(tag[0]):
if lb_added:
lb_added.Genre = tag
@ -2728,6 +2803,36 @@ class ITUNES(DriverBase):
db_added.Genre = tag
break
def _xform_metadata_via_plugboard(self, book, format):
''' Transform book metadata from plugboard templates '''
if DEBUG:
self.log.info(" ITUNES._update_metadata_from_plugboard()")
if self.plugboard_func:
pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
newmi = book.deepcopy_metadata()
newmi.template_to_attribute(book, pb)
if DEBUG:
self.log.info(" transforming %s using %s:" % (format, pb))
self.log.info(" title: %s %s" % (book.title, ">>> %s" %
newmi.title if book.title != newmi.title else ''))
self.log.info(" title_sort: %s %s" % (book.title_sort, ">>> %s" %
newmi.title_sort if book.title_sort != newmi.title_sort else ''))
self.log.info(" authors: %s %s" % (book.authors, ">>> %s" %
newmi.authors if book.authors != newmi.authors else ''))
self.log.info(" author_sort: %s %s" % (book.author_sort, ">>> %s" %
newmi.author_sort if book.author_sort != newmi.author_sort else ''))
self.log.info(" language: %s %s" % (book.language, ">>> %s" %
newmi.language if book.language != newmi.language else ''))
self.log.info(" publisher: %s %s" % (book.publisher, ">>> %s" %
newmi.publisher if book.publisher != newmi.publisher else ''))
self.log.info(" tags: %s %s" % (book.tags, ">>> %s" %
newmi.tags if book.tags != newmi.tags else ''))
else:
newmi = book
return newmi
class ITUNES_ASYNC(ITUNES):
'''
This subclass allows the user to interact directly with iTunes via a menu option
@ -2738,6 +2843,9 @@ class ITUNES_ASYNC(ITUNES):
icon = I('devices/itunes.png')
description = _('Communicate with iTunes.')
# Plugboard ID
DEVICE_PLUGBOARD_NAME = 'APPLE'
connected = False
def __init__(self,path):
@ -3012,15 +3120,9 @@ class BookList(list):
class Book(Metadata):
'''
A simple class describing a book in the iTunes Books Library.
- See ebooks.metadata.__init__ for all fields
See ebooks.metadata.book.base
'''
def __init__(self,title,author):
Metadata.__init__(self, title, authors=[author])
@dynamic_property
def title_sorter(self):
doc = '''String to sort the title. If absent, title is returned'''
def fget(self):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip()
return property(doc=doc, fget=fget)

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>707</width>
<height>340</height>
<width>931</width>
<height>389</height>
</rect>
</property>
<property name="windowTitle">
@ -23,7 +23,7 @@ Use this dialog to define a 'plugboard' for a format (or all formats) and a devi
Often templates will contain simple references to composite columns, but this is not necessary. You can use any template in a source box that you can use elsewhere in calibre.
One possible use for a plugboard is to alter the title to contain series informaton. Another would be to change the author sort, something that mobi users might do to force it to use the ';' that the kindle requires. A third would be to specify the language.</string>
One possible use for a plugboard is to alter the title to contain series information. Another would be to change the author sort, something that mobi users might do to force it to use the ';' that the kindle requires. A third would be to specify the language.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
@ -41,8 +41,7 @@ One possible use for a plugboard is to alter the title to contain series informa
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="device_label">
</widget>
<widget class="QLabel" name="device_label"/>
</item>
<item row="3" column="0" colspan="2">
<widget class="Line" name="line">