mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
2c3ea53b0e
@ -31,6 +31,37 @@ if isosx:
|
||||
|
||||
if iswindows:
|
||||
import pythoncom, win32com.client
|
||||
|
||||
class ITUNES(DevicePlugin):
|
||||
'''
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
'''
|
||||
|
||||
name = 'Apple device interface'
|
||||
gui_name = 'Apple device'
|
||||
icon = I('devices/ipad.png')
|
||||
description = _('Communicate with iBooks through iTunes.')
|
||||
supported_platforms = ['osx','windows']
|
||||
author = 'GRiker'
|
||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||
version = (0, 4, 0)
|
||||
|
||||
OPEN_FEEDBACK_MESSAGE = _(
|
||||
'Apple device detected, launching iTunes, please wait ...')
|
||||
|
||||
FORMATS = ['epub']
|
||||
|
||||
# Product IDs:
|
||||
# 0x1292:iPhone 3G
|
||||
# 0x129a:iPad
|
||||
VENDOR_ID = [0x05ac]
|
||||
PRODUCT_ID = [0x129a]
|
||||
BCD = [0x01]
|
||||
|
||||
# iTunes enumerations
|
||||
Sources = [
|
||||
'Unknown',
|
||||
'Library',
|
||||
@ -48,34 +79,27 @@ if iswindows:
|
||||
'BMP'
|
||||
]
|
||||
|
||||
class ITUNES(DevicePlugin):
|
||||
'''
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
'''
|
||||
PlaylistKind = [
|
||||
'Unknown',
|
||||
'Library',
|
||||
'User',
|
||||
'CD',
|
||||
'Device',
|
||||
'Radio Tuner'
|
||||
]
|
||||
|
||||
name = 'Apple device interface'
|
||||
gui_name = 'Apple device'
|
||||
icon = I('devices/ipad.png')
|
||||
description = _('Communicate with iBooks through iTunes.')
|
||||
supported_platforms = ['osx','windows']
|
||||
author = 'GRiker'
|
||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||
version = (1, 0, 0)
|
||||
|
||||
OPEN_FEEDBACK_MESSAGE = _(
|
||||
'Apple device detected, launching iTunes, please wait ...')
|
||||
|
||||
FORMATS = ['epub']
|
||||
|
||||
# Product IDs:
|
||||
# 0x1292:iPhone 3G
|
||||
# 0x129a:iPad
|
||||
VENDOR_ID = [0x05ac]
|
||||
PRODUCT_ID = [0x129a]
|
||||
BCD = [0x01]
|
||||
PlaylistSpecialKind = [
|
||||
'Unknown',
|
||||
'Purchased Music',
|
||||
'Party Shuffle',
|
||||
'Podcasts',
|
||||
'Folder',
|
||||
'Video',
|
||||
'Music',
|
||||
'Movies',
|
||||
'TV Shows',
|
||||
'Books',
|
||||
]
|
||||
|
||||
# Properties
|
||||
cached_books = {}
|
||||
@ -459,12 +483,14 @@ class ITUNES(DevicePlugin):
|
||||
if isosx:
|
||||
self.iTunes.eject(self.sources['iPod'])
|
||||
elif iswindows:
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||
self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod()
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
if 'iPod' in self.sources:
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||
self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod()
|
||||
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
self.iTunes = None
|
||||
self.sources = None
|
||||
@ -635,7 +661,7 @@ class ITUNES(DevicePlugin):
|
||||
if self.update_needed:
|
||||
if DEBUG:
|
||||
self.log.info(' calling _update_device')
|
||||
self._update_device(msg=self.update_msg)
|
||||
self._update_device(msg=self.update_msg, wait=False)
|
||||
self.update_needed = False
|
||||
|
||||
# Get actual size of updated books on device
|
||||
@ -736,12 +762,13 @@ class ITUNES(DevicePlugin):
|
||||
self.problem_msg = _("Some cover art could not be converted.\n"
|
||||
"Click 'Show Details' for a list.")
|
||||
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES.upload_books():")
|
||||
self._dump_files(files, header='upload_books()')
|
||||
self._dump_cached_books('upload_books()')
|
||||
self._dump_update_list('upload_books()')
|
||||
|
||||
if isosx:
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES.upload_books():")
|
||||
self._dump_files(files, header='upload_books()')
|
||||
self._dump_cached_books('upload_books()')
|
||||
self._dump_update_list('upload_books()')
|
||||
for (i,file) in enumerate(files):
|
||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||
# Delete existing from Library|Books, add to self.update_list
|
||||
@ -836,11 +863,36 @@ class ITUNES(DevicePlugin):
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||
lib = self.iTunes.sources.ItemByName('Library')
|
||||
lib_playlists = [pl.Name for pl in lib.Playlists]
|
||||
if not 'Books' in lib_playlists:
|
||||
self.log.error(" no 'Books' playlist in Library")
|
||||
library_books = lib.Playlists.ItemByName('Books')
|
||||
|
||||
for source in self.iTunes.sources:
|
||||
if source.Kind == self.Sources.index('Library'):
|
||||
lib = source
|
||||
if DEBUG:
|
||||
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" Library source not found")
|
||||
|
||||
if lib is not None:
|
||||
lib_books = None
|
||||
for pl in lib.Playlists:
|
||||
if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books':
|
||||
if DEBUG:
|
||||
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind]))
|
||||
lib_books = pl
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.error(" no Books playlist found")
|
||||
|
||||
#
|
||||
# lib = self.iTunes.sources.ItemByName('Library')
|
||||
# lib_playlists = [pl.Name for pl in lib.Playlists]
|
||||
# if not 'Books' in lib_playlists:
|
||||
# self.log.error(" no 'Books' playlist in Library")
|
||||
# library_books = lib.Playlists.ItemByName('Books')
|
||||
#
|
||||
|
||||
for (i,file) in enumerate(files):
|
||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||
@ -859,10 +911,10 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
# Add to iTunes Library|Books
|
||||
if isinstance(file,PersistentTemporaryFile):
|
||||
op_status = library_books.AddFile(file._name)
|
||||
op_status = lib_books.AddFile(file._name)
|
||||
self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" % file._name)
|
||||
else:
|
||||
op_status = library_books.AddFile(file)
|
||||
op_status = lib_books.AddFile(file)
|
||||
self.log.info(" iTunes adding '%s'" % file)
|
||||
|
||||
if DEBUG:
|
||||
@ -1060,20 +1112,6 @@ class ITUNES(DevicePlugin):
|
||||
ub['author']))
|
||||
self.log.info()
|
||||
|
||||
def _find_device_book(self, cached_book):
|
||||
'''
|
||||
Windows-only method to get a handle to a device book in the current pythoncom session
|
||||
'''
|
||||
SearchField = ['All','Visible','Artists','Titles','Composers','SongNames']
|
||||
if iswindows:
|
||||
dev_books = self.iTunes.sources.ItemByName(self.sources['iPod']).Playlists.ItemByName('Books')
|
||||
hits = dev_books.Search(cached_book['title'],SearchField.index('Titles'))
|
||||
if hits:
|
||||
for hit in hits:
|
||||
if hit.Artist == cached_book['author']:
|
||||
return hit
|
||||
return None
|
||||
|
||||
def _find_library_book(self, cached_book):
|
||||
'''
|
||||
Windows-only method to get a handle to a library book in the current pythoncom session
|
||||
@ -1083,7 +1121,28 @@ class ITUNES(DevicePlugin):
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES._find_library_book()")
|
||||
self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author']))
|
||||
lib_books = self.iTunes.sources.ItemByName('Library').Playlists.ItemByName('Books')
|
||||
|
||||
for source in self.iTunes.sources:
|
||||
if source.Kind == self.Sources.index('Library'):
|
||||
lib = source
|
||||
if DEBUG:
|
||||
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" Library source not found")
|
||||
|
||||
if lib is not None:
|
||||
lib_books = None
|
||||
for pl in lib.Playlists:
|
||||
if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books':
|
||||
if DEBUG:
|
||||
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind]))
|
||||
lib_books = pl
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.error(" no Books playlist found")
|
||||
|
||||
attempts = 9
|
||||
while attempts:
|
||||
@ -1121,12 +1180,6 @@ class ITUNES(DevicePlugin):
|
||||
except:
|
||||
zfw = zipfile.ZipFile(archive_path, mode='a')
|
||||
else:
|
||||
# if DEBUG:
|
||||
# if isosx:
|
||||
# self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.name())
|
||||
# elif iswindows:
|
||||
# self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.Name)
|
||||
|
||||
return thumb_data
|
||||
|
||||
if isosx:
|
||||
@ -1160,7 +1213,7 @@ class ITUNES(DevicePlugin):
|
||||
return None
|
||||
|
||||
# Save the cover from iTunes
|
||||
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % ArtworkFormat[book.Artwork.Item(1).Format])
|
||||
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format])
|
||||
book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb)
|
||||
try:
|
||||
# Resize the cover
|
||||
@ -1184,8 +1237,6 @@ class ITUNES(DevicePlugin):
|
||||
def _get_device_book_size(self, title, author):
|
||||
'''
|
||||
Fetch the size of a book stored on the device
|
||||
|
||||
Windows: If sync-in-progress, this call blocked until sync completes
|
||||
'''
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: '%s'" %
|
||||
@ -1214,53 +1265,134 @@ class ITUNES(DevicePlugin):
|
||||
|
||||
def _get_device_books(self):
|
||||
'''
|
||||
Assumes pythoncom wrapper
|
||||
Assumes pythoncom wrapper for Windows
|
||||
'''
|
||||
if DEBUG:
|
||||
self.log.info("\nITUNES._get_device_books()")
|
||||
|
||||
device_books = []
|
||||
if isosx:
|
||||
if 'iPod' in self.sources:
|
||||
connected_device = self.sources['iPod']
|
||||
if 'Books' in self.iTunes.sources[connected_device].playlists.name():
|
||||
return self.iTunes.sources[connected_device].playlists['Books'].file_tracks()
|
||||
return []
|
||||
device = self.iTunes.sources[connected_device]
|
||||
for pl in device.playlists():
|
||||
if pl.special_kind() == appscript.k.Books:
|
||||
if DEBUG:
|
||||
self.log.info(" Book playlist: '%s' special_kind: '%s'" % (pl.name(), pl.special_kind()))
|
||||
books = pl.file_tracks()
|
||||
break
|
||||
else:
|
||||
self.log.error(" book_playlist not found")
|
||||
|
||||
for book in books:
|
||||
if book.kind() in ['Book','Protected book']:
|
||||
device_books.append(book)
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
||||
|
||||
elif iswindows:
|
||||
if 'iPod' in self.sources:
|
||||
connected_device = self.sources['iPod']
|
||||
dev = self.iTunes.sources.ItemByName(connected_device)
|
||||
dev_playlists = [pl.Name for pl in dev.Playlists]
|
||||
if 'Books' in dev_playlists:
|
||||
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Books').Tracks
|
||||
else:
|
||||
return []
|
||||
if DEBUG:
|
||||
self.log.warning('ITUNES._get_device_book(): No iPod device connected')
|
||||
return []
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
connected_device = self.sources['iPod']
|
||||
device = self.iTunes.sources.ItemByName(connected_device)
|
||||
|
||||
dev_books = None
|
||||
for pl in device.Playlists:
|
||||
if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books':
|
||||
if DEBUG:
|
||||
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind]))
|
||||
dev_books = pl.Tracks
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" no Books playlist found")
|
||||
|
||||
for book in dev_books:
|
||||
if book.KindAsString in ['Book','Protected book']:
|
||||
device_books.append(book)
|
||||
else:
|
||||
self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString))
|
||||
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
return device_books
|
||||
|
||||
def _get_library_books(self):
|
||||
'''
|
||||
Populate a dict of paths from iTunes Library|Books
|
||||
'''
|
||||
if DEBUG:
|
||||
self.log.info("\nITUNES._get_library_books()")
|
||||
|
||||
library_books = {}
|
||||
lib = None
|
||||
|
||||
if isosx:
|
||||
lib = self.iTunes.sources['library']
|
||||
if 'Books' in lib.playlists.name():
|
||||
lib_books = lib.playlists['Books'].file_tracks()
|
||||
for source in self.iTunes.sources():
|
||||
if source.kind() == appscript.k.library:
|
||||
lib = source
|
||||
if DEBUG:
|
||||
self.log.info(" Library source: '%s' kind: %s" % (lib.name(), lib.kind()))
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.error(' Library source not found')
|
||||
|
||||
if lib is not None:
|
||||
lib_books = None
|
||||
for pl in lib.playlists():
|
||||
if pl.special_kind() == appscript.k.Books:
|
||||
if DEBUG:
|
||||
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.name(), pl.special_kind()))
|
||||
break
|
||||
lib_books = pl.file_tracks()
|
||||
for book in lib_books:
|
||||
path = self.path_template % (book.name(), book.artist())
|
||||
library_books[path] = book
|
||||
if book.kind() in ['Book','Protected book']:
|
||||
path = self.path_template % (book.name(), book.artist())
|
||||
library_books[path] = book
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" ignoring library book of type '%s'" % book.kind())
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info('ITUNES._get_library_books():\n No Books playlist')
|
||||
|
||||
|
||||
elif iswindows:
|
||||
lib = None
|
||||
try:
|
||||
pythoncom.CoInitialize()
|
||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||
lib = self.iTunes.sources.ItemByName('Library')
|
||||
lib_playlists = [pl.Name for pl in lib.Playlists]
|
||||
if 'Books' in lib_playlists:
|
||||
lib_books = lib.Playlists.ItemByName('Books').Tracks
|
||||
for source in self.iTunes.sources:
|
||||
if source.Kind == self.Sources.index('Library'):
|
||||
lib = source
|
||||
self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind]))
|
||||
break
|
||||
else:
|
||||
self.log.error(" Library source not found")
|
||||
|
||||
if lib is not None:
|
||||
lib_books = None
|
||||
for pl in lib.Playlists:
|
||||
if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books':
|
||||
if DEBUG:
|
||||
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind]))
|
||||
lib_books = pl.Tracks
|
||||
break
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.error(" no Books playlist found")
|
||||
|
||||
for book in lib_books:
|
||||
path = self.path_template % (book.Name, book.Artist)
|
||||
library_books[path] = book
|
||||
if book.KindAsString in ['Book','Protected book']:
|
||||
path = self.path_template % (book.Name, book.Artist)
|
||||
library_books[path] = book
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString))
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
@ -1455,11 +1587,9 @@ class ITUNES(DevicePlugin):
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
break
|
||||
|
||||
finally:
|
||||
pythoncom.CoUninitialize()
|
||||
|
||||
|
||||
class BookList(list):
|
||||
'''
|
||||
A list of books. Each Book object must have the fields:
|
||||
|
Loading…
x
Reference in New Issue
Block a user