mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR revisions iTunes/iPad 0.4.0
This commit is contained in:
commit
2e8a102018
@ -4,6 +4,13 @@
|
|||||||
# for important features/bug fixes.
|
# for important features/bug fixes.
|
||||||
# Also, each release can have new and improved recipes.
|
# Also, each release can have new and improved recipes.
|
||||||
|
|
||||||
|
- version: 0.7.0
|
||||||
|
date: 2010-06-04
|
||||||
|
|
||||||
|
new features:
|
||||||
|
- title: "Go to http://calibre-ebook.com/new-in/seven to see what's new in 0.7.0"
|
||||||
|
type: major
|
||||||
|
|
||||||
- version: 0.6.55
|
- version: 0.6.55
|
||||||
date: 2010-05-28
|
date: 2010-05-28
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
__appname__ = 'calibre'
|
__appname__ = 'calibre'
|
||||||
__version__ = '0.6.55'
|
__version__ = '0.7.0'
|
||||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
@ -22,10 +22,46 @@ from calibre.devices.errors import UserFeedback
|
|||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
|
try:
|
||||||
import appscript
|
import appscript
|
||||||
|
appscript
|
||||||
|
except:
|
||||||
|
# appscript fails to load on 10.4
|
||||||
|
appscript = None
|
||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import pythoncom, win32com.client
|
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 = [
|
Sources = [
|
||||||
'Unknown',
|
'Unknown',
|
||||||
'Library',
|
'Library',
|
||||||
@ -43,34 +79,27 @@ if iswindows:
|
|||||||
'BMP'
|
'BMP'
|
||||||
]
|
]
|
||||||
|
|
||||||
class ITUNES(DevicePlugin):
|
PlaylistKind = [
|
||||||
'''
|
'Unknown',
|
||||||
try:
|
'Library',
|
||||||
pythoncom.CoInitialize()
|
'User',
|
||||||
finally:
|
'CD',
|
||||||
pythoncom.CoUninitialize()
|
'Device',
|
||||||
'''
|
'Radio Tuner'
|
||||||
|
]
|
||||||
|
|
||||||
name = 'Apple device interface'
|
PlaylistSpecialKind = [
|
||||||
gui_name = 'Apple device'
|
'Unknown',
|
||||||
icon = I('devices/ipad.png')
|
'Purchased Music',
|
||||||
description = _('Communicate with iBooks through iTunes.')
|
'Party Shuffle',
|
||||||
supported_platforms = ['osx','windows']
|
'Podcasts',
|
||||||
author = 'GRiker'
|
'Folder',
|
||||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
'Video',
|
||||||
version = (1, 0, 0)
|
'Music',
|
||||||
|
'Movies',
|
||||||
OPEN_FEEDBACK_MESSAGE = _(
|
'TV Shows',
|
||||||
'Apple device detected, launching iTunes, please wait ...')
|
'Books',
|
||||||
|
]
|
||||||
FORMATS = ['epub']
|
|
||||||
|
|
||||||
# Product IDs:
|
|
||||||
# 0x1292:iPhone 3G
|
|
||||||
# 0x129a:iPad
|
|
||||||
VENDOR_ID = [0x05ac]
|
|
||||||
PRODUCT_ID = [0x129a]
|
|
||||||
BCD = [0x01]
|
|
||||||
|
|
||||||
# Properties
|
# Properties
|
||||||
cached_books = {}
|
cached_books = {}
|
||||||
@ -268,6 +297,8 @@ class ITUNES(DevicePlugin):
|
|||||||
instantiate iTunes if necessary
|
instantiate iTunes if necessary
|
||||||
This gets called ~1x/second while device fingerprint is sensed
|
This gets called ~1x/second while device fingerprint is sensed
|
||||||
'''
|
'''
|
||||||
|
if appscript is None:
|
||||||
|
return False
|
||||||
|
|
||||||
if self.iTunes:
|
if self.iTunes:
|
||||||
# Check for connected book-capable device
|
# Check for connected book-capable device
|
||||||
@ -452,10 +483,12 @@ class ITUNES(DevicePlugin):
|
|||||||
if isosx:
|
if isosx:
|
||||||
self.iTunes.eject(self.sources['iPod'])
|
self.iTunes.eject(self.sources['iPod'])
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
|
if 'iPod' in self.sources:
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod()
|
self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
@ -628,7 +661,7 @@ class ITUNES(DevicePlugin):
|
|||||||
if self.update_needed:
|
if self.update_needed:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(' calling _update_device')
|
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
|
self.update_needed = False
|
||||||
|
|
||||||
# Get actual size of updated books on device
|
# Get actual size of updated books on device
|
||||||
@ -729,12 +762,13 @@ class ITUNES(DevicePlugin):
|
|||||||
self.problem_msg = _("Some cover art could not be converted.\n"
|
self.problem_msg = _("Some cover art could not be converted.\n"
|
||||||
"Click 'Show Details' for a list.")
|
"Click 'Show Details' for a list.")
|
||||||
|
|
||||||
if isosx:
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES.upload_books():")
|
self.log.info("ITUNES.upload_books():")
|
||||||
self._dump_files(files, header='upload_books()')
|
self._dump_files(files, header='upload_books()')
|
||||||
self._dump_cached_books('upload_books()')
|
self._dump_cached_books('upload_books()')
|
||||||
self._dump_update_list('upload_books()')
|
self._dump_update_list('upload_books()')
|
||||||
|
|
||||||
|
if isosx:
|
||||||
for (i,file) in enumerate(files):
|
for (i,file) in enumerate(files):
|
||||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||||
# Delete existing from Library|Books, add to self.update_list
|
# Delete existing from Library|Books, add to self.update_list
|
||||||
@ -829,11 +863,36 @@ class ITUNES(DevicePlugin):
|
|||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
lib = self.iTunes.sources.ItemByName('Library')
|
|
||||||
lib_playlists = [pl.Name for pl in lib.Playlists]
|
for source in self.iTunes.sources:
|
||||||
if not 'Books' in lib_playlists:
|
if source.Kind == self.Sources.index('Library'):
|
||||||
self.log.error(" no 'Books' playlist in Library")
|
lib = source
|
||||||
library_books = lib.Playlists.ItemByName('Books')
|
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):
|
for (i,file) in enumerate(files):
|
||||||
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
path = self.path_template % (metadata[i].title, metadata[i].author[0])
|
||||||
@ -852,10 +911,10 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
# Add to iTunes Library|Books
|
# Add to iTunes Library|Books
|
||||||
if isinstance(file,PersistentTemporaryFile):
|
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)
|
self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" % file._name)
|
||||||
else:
|
else:
|
||||||
op_status = library_books.AddFile(file)
|
op_status = lib_books.AddFile(file)
|
||||||
self.log.info(" iTunes adding '%s'" % file)
|
self.log.info(" iTunes adding '%s'" % file)
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1053,20 +1112,6 @@ class ITUNES(DevicePlugin):
|
|||||||
ub['author']))
|
ub['author']))
|
||||||
self.log.info()
|
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):
|
def _find_library_book(self, cached_book):
|
||||||
'''
|
'''
|
||||||
Windows-only method to get a handle to a library book in the current pythoncom session
|
Windows-only method to get a handle to a library book in the current pythoncom session
|
||||||
@ -1076,7 +1121,28 @@ class ITUNES(DevicePlugin):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES._find_library_book()")
|
self.log.info("ITUNES._find_library_book()")
|
||||||
self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author']))
|
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
|
attempts = 9
|
||||||
while attempts:
|
while attempts:
|
||||||
@ -1114,12 +1180,6 @@ class ITUNES(DevicePlugin):
|
|||||||
except:
|
except:
|
||||||
zfw = zipfile.ZipFile(archive_path, mode='a')
|
zfw = zipfile.ZipFile(archive_path, mode='a')
|
||||||
else:
|
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
|
return thumb_data
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -1153,7 +1213,7 @@ class ITUNES(DevicePlugin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Save the cover from iTunes
|
# 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)
|
book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb)
|
||||||
try:
|
try:
|
||||||
# Resize the cover
|
# Resize the cover
|
||||||
@ -1177,8 +1237,6 @@ class ITUNES(DevicePlugin):
|
|||||||
def _get_device_book_size(self, title, author):
|
def _get_device_book_size(self, title, author):
|
||||||
'''
|
'''
|
||||||
Fetch the size of a book stored on the device
|
Fetch the size of a book stored on the device
|
||||||
|
|
||||||
Windows: If sync-in-progress, this call blocked until sync completes
|
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: '%s'" %
|
self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: '%s'" %
|
||||||
@ -1207,53 +1265,134 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
def _get_device_books(self):
|
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 isosx:
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
connected_device = self.sources['iPod']
|
connected_device = self.sources['iPod']
|
||||||
if 'Books' in self.iTunes.sources[connected_device].playlists.name():
|
device = self.iTunes.sources[connected_device]
|
||||||
return self.iTunes.sources[connected_device].playlists['Books'].file_tracks()
|
for pl in device.playlists():
|
||||||
return []
|
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:
|
elif iswindows:
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
|
try:
|
||||||
|
pythoncom.CoInitialize()
|
||||||
connected_device = self.sources['iPod']
|
connected_device = self.sources['iPod']
|
||||||
dev = self.iTunes.sources.ItemByName(connected_device)
|
device = self.iTunes.sources.ItemByName(connected_device)
|
||||||
dev_playlists = [pl.Name for pl in dev.Playlists]
|
|
||||||
if 'Books' in dev_playlists:
|
dev_books = None
|
||||||
return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Books').Tracks
|
for pl in device.Playlists:
|
||||||
else:
|
if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books':
|
||||||
return []
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.warning('ITUNES._get_device_book(): No iPod device connected')
|
self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind]))
|
||||||
return []
|
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):
|
def _get_library_books(self):
|
||||||
'''
|
'''
|
||||||
Populate a dict of paths from iTunes Library|Books
|
Populate a dict of paths from iTunes Library|Books
|
||||||
'''
|
'''
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info("\nITUNES._get_library_books()")
|
||||||
|
|
||||||
library_books = {}
|
library_books = {}
|
||||||
|
lib = None
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
lib = self.iTunes.sources['library']
|
for source in self.iTunes.sources():
|
||||||
if 'Books' in lib.playlists.name():
|
if source.kind() == appscript.k.library:
|
||||||
lib_books = lib.playlists['Books'].file_tracks()
|
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:
|
for book in lib_books:
|
||||||
|
if book.kind() in ['Book','Protected book']:
|
||||||
path = self.path_template % (book.name(), book.artist())
|
path = self.path_template % (book.name(), book.artist())
|
||||||
library_books[path] = book
|
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:
|
elif iswindows:
|
||||||
|
lib = None
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
lib = self.iTunes.sources.ItemByName('Library')
|
for source in self.iTunes.sources:
|
||||||
lib_playlists = [pl.Name for pl in lib.Playlists]
|
if source.Kind == self.Sources.index('Library'):
|
||||||
if 'Books' in lib_playlists:
|
lib = source
|
||||||
lib_books = lib.Playlists.ItemByName('Books').Tracks
|
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:
|
for book in lib_books:
|
||||||
|
if book.KindAsString in ['Book','Protected book']:
|
||||||
path = self.path_template % (book.Name, book.Artist)
|
path = self.path_template % (book.Name, book.Artist)
|
||||||
library_books[path] = book
|
library_books[path] = book
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString))
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
@ -1429,7 +1568,7 @@ class ITUNES(DevicePlugin):
|
|||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
result = self.iTunes.UpdateIPod()
|
self.iTunes.UpdateIPod()
|
||||||
if wait:
|
if wait:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
sys.stdout.write(" waiting for iPad sync to complete ...")
|
sys.stdout.write(" waiting for iPad sync to complete ...")
|
||||||
@ -1448,11 +1587,9 @@ class ITUNES(DevicePlugin):
|
|||||||
sys.stdout.write('\n')
|
sys.stdout.write('\n')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
break
|
break
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
|
|
||||||
class BookList(list):
|
class BookList(list):
|
||||||
'''
|
'''
|
||||||
A list of books. Each Book object must have the fields:
|
A list of books. Each Book object must have the fields:
|
||||||
|
@ -44,6 +44,7 @@ def get_metadata_(src, encoding=None):
|
|||||||
author = match.group(2).replace(',', ';')
|
author = match.group(2).replace(',', ';')
|
||||||
|
|
||||||
ent_pat = re.compile(r'&(\S+)?;')
|
ent_pat = re.compile(r'&(\S+)?;')
|
||||||
|
if title:
|
||||||
title = ent_pat.sub(entity_to_unicode, title)
|
title = ent_pat.sub(entity_to_unicode, title)
|
||||||
if author:
|
if author:
|
||||||
author = ent_pat.sub(entity_to_unicode, author)
|
author = ent_pat.sub(entity_to_unicode, author)
|
||||||
|
@ -201,7 +201,11 @@ class CSSFlattener(object):
|
|||||||
tag = barename(node.tag)
|
tag = barename(node.tag)
|
||||||
style = stylizer.style(node)
|
style = stylizer.style(node)
|
||||||
cssdict = style.cssdict()
|
cssdict = style.cssdict()
|
||||||
|
try:
|
||||||
font_size = style['font-size']
|
font_size = style['font-size']
|
||||||
|
except:
|
||||||
|
font_size = self.sbase if self.sbase is not None else \
|
||||||
|
self.context.source.fbase
|
||||||
if 'align' in node.attrib:
|
if 'align' in node.attrib:
|
||||||
cssdict['text-align'] = node.attrib['align']
|
cssdict['text-align'] = node.attrib['align']
|
||||||
del node.attrib['align']
|
del node.attrib['align']
|
||||||
|
@ -243,7 +243,7 @@ Now, you can access your saved search in the Tag Browser under "Searches". A sin
|
|||||||
|
|
||||||
Preferences
|
Preferences
|
||||||
---------------
|
---------------
|
||||||
The Preferences dialog allows you to set some global defaults used by all of |app|. To access it, click the |cbi|.
|
The Preferences dialog allows you to change the way various aspects of |app| work. To access it, click the |cbi|.
|
||||||
|
|
||||||
.. |cbi| image:: images/configuration.png
|
.. |cbi| image:: images/configuration.png
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ The Preferences dialog allows you to set some global defaults used by all of |ap
|
|||||||
|
|
||||||
Guessing metadata from file names
|
Guessing metadata from file names
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
In the :guilabel:`Advanced` section of the configuration dialog, you can specify a regularexpression that |app| will use to try and guess metadata from the names of ebook files
|
In the :guilabel:`Add/Save` section of the configuration dialog, you can specify a regular expression that |app| will use to try and guess metadata from the names of ebook files
|
||||||
that you add to the library. The default regular expression is::
|
that you add to the library. The default regular expression is::
|
||||||
|
|
||||||
title - author
|
title - author
|
||||||
@ -265,18 +265,13 @@ will be interpreted to have the title: Foundation and Earth and author: Isaac As
|
|||||||
.. tip::
|
.. tip::
|
||||||
If the filename does not contain the hyphen, the regular expression will fail.
|
If the filename does not contain the hyphen, the regular expression will fail.
|
||||||
|
|
||||||
.. tip::
|
|
||||||
If you want to only use metadata guessed from filenames and not metadata read from the file itself, you can tell |app| to do this, via the configuration dialog, accessed by the button to the right
|
|
||||||
of the search box.
|
|
||||||
|
|
||||||
.. _book_details:
|
.. _book_details:
|
||||||
|
|
||||||
Book Details
|
Book Details
|
||||||
-------------
|
-------------
|
||||||
.. image:: images/book_details.png
|
.. image:: images/book_details.png
|
||||||
|
|
||||||
The Book Details display shows you extra information and the cover for the currently selected book. THe comments section is truncated if the comments are too long. To see the full comments as well as
|
The Book Details display shows you extra information and the cover for the currently selected book.
|
||||||
a larger image of the cover, click anywhere in the Book Details area.
|
|
||||||
|
|
||||||
.. _jobs:
|
.. _jobs:
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -161,6 +161,19 @@ def create_text_arc(text, font_size, font=None, bgcolor='white'):
|
|||||||
p.MagickTrimImage(canvas, 0)
|
p.MagickTrimImage(canvas, 0)
|
||||||
return canvas
|
return canvas
|
||||||
|
|
||||||
|
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
|
||||||
|
border_color='white'):
|
||||||
|
with p.ImageMagick():
|
||||||
|
img = load_image(path_to_image)
|
||||||
|
lwidth = p.MagickGetImageWidth(img)
|
||||||
|
lheight = p.MagickGetImageHeight(img)
|
||||||
|
canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
|
||||||
|
border_color)
|
||||||
|
compose_image(canvas, img, left, top)
|
||||||
|
p.DestroyMagickWand(img)
|
||||||
|
with open(path_to_image, 'wb') as f:
|
||||||
|
p.MagickWriteImage(canvas, f)
|
||||||
|
p.DestroyMagickWand(canvas)
|
||||||
|
|
||||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||||
bgcolor='white', output_format='png'):
|
bgcolor='white', output_format='png'):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user