diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index d8c6d03f55..14c2863d6b 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2010, Gregory Riker' __docformat__ = 'restructuredtext en' @@ -20,6 +20,7 @@ from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.zipfile import ZipFile + def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): if not hasattr(dt, 'timetuple'): @@ -38,6 +39,7 @@ def logger(): _log = ThreadSafeLog() return _log + class AppleOpenFeedback(OpenFeedback): def __init__(self, plugin): @@ -102,6 +104,7 @@ class AppleOpenFeedback(OpenFeedback): return Dialog(parent, self) + class DriverBase(DeviceConfig, DevicePlugin): # Needed for config_widget to work FORMATS = ['epub', 'pdf'] @@ -116,12 +119,12 @@ class DriverBase(DeviceConfig, DevicePlugin): EXTRA_CUSTOMIZATION_MESSAGE = [ _('Use Series as Category in iTunes/iBooks') + - ':::'+_('Enable to use the series name as the iTunes Genre, ' + ':::' + _('Enable to use the series name as the iTunes Genre, ' 'iBooks Category'), _('Cache covers from iTunes/iBooks') + ':::' + _('Enable to cache and display covers from iTunes/iBooks'), - _(u'"Copy files to iTunes Media folder %s" is enabled in iTunes Preferences|Advanced')%u'\u2026' + + _(u'"Copy files to iTunes Media folder %s" is enabled in iTunes Preferences|Advanced') % u'\u2026' + ':::' + _("
This setting should match your iTunes Preferences|Advanced setting.
" "Disabling will store copies of books transferred to iTunes in your calibre configuration directory.
" @@ -133,11 +136,11 @@ class DriverBase(DeviceConfig, DevicePlugin): False, ] - @classmethod def _config_base_name(cls): return 'iTunes' + class ITUNES(DriverBase): ''' Calling sequences: @@ -158,6 +161,7 @@ class ITUNES(DriverBase): can_handle() set_progress_reporter() books() (once for each storage point) + (create self.cached_books) settings() settings() can_handle() (~1x per second OSX while idle) @@ -191,11 +195,11 @@ class ITUNES(DriverBase): name = 'Apple iTunes interface' gui_name = _('Apple device') icon = I('devices/ipad.png') - description = _('Communicate with iTunes/iBooks.') - supported_platforms = ['osx','windows'] + description = _('Communicate with iTunes/iBooks.') + supported_platforms = ['osx', 'windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (1,0,0) + version = (1, 1, 1) DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog" @@ -290,7 +294,7 @@ class ITUNES(DriverBase): archive_path = os.path.join(cache_dir, "thumbs.zip") description_prefix = "added by calibre" ejected = False - iTunes= None + iTunes = None iTunes_local_storage = None library_orphans = None manual_sync_mode = False @@ -325,21 +329,21 @@ class ITUNES(DriverBase): # Delete any obsolete copies of the book from the booklist if self.update_list: if False: - self._dump_booklist(booklists[0], header='before',indent=2) - self._dump_update_list(header='before',indent=2) - self._dump_cached_books(header='before',indent=2) + self._dump_booklist(booklists[0], header='before', indent=2) + self._dump_update_list(header='before', indent=2) + self._dump_cached_books(header='before', indent=2) - for (j,p_book) in enumerate(self.update_list): + for (j, p_book) in enumerate(self.update_list): if False: if isosx: logger().info(" looking for '%s' by %s uuid:%s" % - (p_book['title'],p_book['author'], p_book['uuid'])) + (p_book['title'], p_book['author'], p_book['uuid'])) elif iswindows: logger().info(" looking for '%s' by %s (%s)" % - (p_book['title'],p_book['author'], p_book['uuid'])) + (p_book['title'], p_book['author'], p_book['uuid'])) # Purge the booklist, self.cached_books - for i,bl_book in enumerate(booklists[0]): + for i, bl_book in enumerate(booklists[0]): if bl_book.uuid == p_book['uuid']: # Remove from booklists[0] booklists[0].pop(i) @@ -363,12 +367,12 @@ class ITUNES(DriverBase): if self.cached_books[cb]['title'] == p_book['title'] and \ self.cached_books[cb]['author'] == p_book['author']: if DEBUG: - self._dump_cached_book(self.cached_books[cb],header="removing from self.cached_books:", indent=2) + self._dump_cached_book(self.cached_books[cb], header="removing from self.cached_books:", indent=2) self.cached_books.pop(cb) break break if self.report_progress is not None: - self.report_progress((j+1)/task_count, _('Updating device metadata listing...')) + self.report_progress((j + 1) / task_count, _('Updating device metadata listing...')) if self.report_progress is not None: self.report_progress(1.0, _('Updating device metadata listing...')) @@ -383,8 +387,8 @@ class ITUNES(DriverBase): booklists[0].append(new_book) if False: - self._dump_booklist(booklists[0],header='after',indent=2) - self._dump_cached_books(header='after',indent=2) + self._dump_booklist(booklists[0], header='after', indent=2) + self._dump_cached_books(header='after', indent=2) def books(self, oncard=None, end_session=True): """ @@ -409,7 +413,7 @@ class ITUNES(DriverBase): else: logger().info(" Cover fetching/caching disabled") - # Fetch a list of books from iPod device connected to iTunes + # Fetch a list of books from iDevice connected to iTunes if 'iPod' in self.sources: booklist = BookList(logger()) cached_books = {} @@ -418,10 +422,10 @@ class ITUNES(DriverBase): library_books = self._get_library_books() device_books = self._get_device_books() book_count = float(len(device_books)) - for (i,book) in enumerate(device_books): + for (i, book) in enumerate(device_books): this_book = Book(book.name(), book.artist()) format = 'pdf' if book.kind().startswith('PDF') else 'epub' - this_book.path = self.path_template % (book.name(), book.artist(),format) + this_book.path = self.path_template % (book.name(), book.artist(), format) try: this_book.datetime = parse_date(str(book.date_added())).timetuple() except: @@ -439,16 +443,17 @@ class ITUNES(DriverBase): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':book.name(), - 'author':book.artist().split(' & '), - 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, - 'dev_book':book, + 'title': book.name(), + 'author': book.artist(), + 'authors': book.artist().split(' & '), + 'lib_book': library_books[this_book.path] if this_book.path in library_books else None, + 'dev_book': book, 'uuid': book.composer() } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=book_count)) + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) self._purge_orphans(library_books, cached_books) elif iswindows: @@ -459,10 +464,10 @@ class ITUNES(DriverBase): library_books = self._get_library_books() device_books = self._get_device_books() book_count = float(len(device_books)) - for (i,book) in enumerate(device_books): + for (i, book) in enumerate(device_books): this_book = Book(book.Name, book.Artist) format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' - this_book.path = self.path_template % (book.Name, book.Artist,format) + this_book.path = self.path_template % (book.Name, book.Artist, format) try: this_book.datetime = parse_date(str(book.DateAdded)).timetuple() except: @@ -479,16 +484,17 @@ class ITUNES(DriverBase): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':book.Name, - 'author':book.Artist.split(' & '), - 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, + 'title': book.Name, + 'author': book.Artist, + 'authors': book.Artist.split(' & '), + 'lib_book': library_books[this_book.path] if this_book.path in library_books else None, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) self._purge_orphans(library_books, cached_books) @@ -500,7 +506,7 @@ class ITUNES(DriverBase): self.cached_books = cached_books if DEBUG: self._dump_booklist(booklist, 'returning from books()', indent=2) - self._dump_cached_books('returning from books()',indent=2) + self._dump_cached_books('returning from books()', indent=2) return booklist else: return BookList(logger()) @@ -556,7 +562,7 @@ class ITUNES(DriverBase): self.sources = self._get_sources() if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): attempts -= 1 - time.sleep(0.5) + time.sleep(1.0) if DEBUG: logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) else: @@ -570,7 +576,7 @@ class ITUNES(DriverBase): self.ejected = True return False - self._discover_manual_sync_mode(wait = 2 if self.initial_status == 'launched' else 0) + self._discover_manual_sync_mode(wait=2 if self.initial_status == 'launched' else 0) return True def can_handle_windows(self, device_id, debug=False): @@ -634,9 +640,9 @@ class ITUNES(DriverBase): self.sources = self._get_sources() if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): attempts -= 1 - time.sleep(0.5) + time.sleep(1.0) if DEBUG: - logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts)) + logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) else: if DEBUG: logger().info(' found connected iPad in iTunes') @@ -666,7 +672,7 @@ class ITUNES(DriverBase): ('place', None) (None, None) ''' - return (None,None) + return (None, None) @classmethod def config_widget(cls): @@ -720,22 +726,25 @@ class ITUNES(DriverBase): else: if self.manual_sync_mode: metadata = MetaInformation(self.cached_books[path]['title'], - [self.cached_books[path]['author']]) + self.cached_books[path]['authors']) + metadata.author = self.cached_books[path]['author'] metadata.uuid = self.cached_books[path]['uuid'] + if not metadata.uuid: + metadata.uuid = "unknown" if isosx: - self._remove_existing_copy(self.cached_books[path],metadata) + self._remove_existing_copy(self.cached_books[path], metadata) elif iswindows: try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - self._remove_existing_copy(self.cached_books[path],metadata) + self._remove_existing_copy(self.cached_books[path], metadata) finally: pythoncom.CoUninitialize() else: self.problem_titles.append("'%s' by %s" % - (self.cached_books[path]['title'],self.cached_books[path]['author'])) + (self.cached_books[path]['title'], self.cached_books[path]['author'])) def eject(self): ''' @@ -799,7 +808,7 @@ class ITUNES(DriverBase): except: logger().error(' waiting for free_space() call to go through') - return (free_space,-1,-1) + return (free_space, -1, -1) def get_device_information(self, end_session=True): """ @@ -809,7 +818,7 @@ class ITUNES(DriverBase): if DEBUG: logger().info("%s.get_device_information()" % self.__class__.__name__) - return (self.sources['iPod'],'hw v1.0','sw v1.0', 'mime type normally goes here') + return (self.sources['iPod'], 'hw v1.0', 'sw v1.0', 'mime type normally goes here') def get_file(self, path, outfile, end_session=True): ''' @@ -848,28 +857,32 @@ class ITUNES(DriverBase): raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) if DEBUG: - VENDOR_ID = "0x%x" % connected_device[0] - PRODUCT_ID = "0x%x" % connected_device[1] - BCD = "0x%x" % connected_device[2] - MFG = connected_device[3] - MODEL = connected_device[4] + vendor_id = "0x%x" % connected_device[0] + product_id = "0x%x" % connected_device[1] + bcd = "0x%x" % connected_device[2] + mfg = connected_device[3] + model = connected_device[4] logger().info("%s.open(MFG: %s, VENDOR_ID: %s, MODEL: %s, BCD: %s, PRODUCT_ID: %s)" % (self.__class__.__name__, - MFG, - VENDOR_ID, - MODEL, - BCD, - PRODUCT_ID + mfg, + vendor_id, + model, + bcd, + product_id )) # Display a dialog recommending using 'Connect to iTunes' if user hasn't # previously disabled the dialog - if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG),True): + if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG), True): raise AppleOpenFeedback(self) else: if DEBUG: logger().error(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE) + # Log supported DEVICE_IDs and BCDs + logger().info(" BCD: %s" % ['0x%x' % x for x in sorted(self.BCD)]) + logger().info(" PRODUCT_ID: %s" % ['0x%x' % x for x in sorted(self.PRODUCT_ID)]) + # Confirm/create thumbs archive if not os.path.exists(self.cache_dir): if DEBUG: @@ -879,7 +892,7 @@ class ITUNES(DriverBase): if not os.path.exists(self.archive_path): logger().info(" creating zip archive") zfw = ZipFile(self.archive_path, mode='w') - zfw.writestr("iTunes Thumbs Archive",'') + zfw.writestr("iTunes Thumbs Archive", '') zfw.close() else: if DEBUG: @@ -887,7 +900,7 @@ class ITUNES(DriverBase): # If enabled in config options, create/confirm an iTunes storage folder if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: - self.iTunes_local_storage = os.path.join(config_dir,'iTunes storage') + self.iTunes_local_storage = os.path.join(config_dir, 'iTunes storage') if not os.path.exists(self.iTunes_local_storage): if DEBUG: logger()(" creating iTunes_local_storage at '%s'" % self.iTunes_local_storage) @@ -916,37 +929,41 @@ class ITUNES(DriverBase): logger().info(" looking for '%s' by '%s' uuid:%s" % (self.cached_books[path]['title'], self.cached_books[path]['author'], - self.cached_books[path]['uuid'])) + repr(self.cached_books[path]['uuid']))) # Purge the booklist, self.cached_books, thumb cache - for i,bl_book in enumerate(booklists[0]): + for i, bl_book in enumerate(booklists[0]): if False: - logger().info(" evaluating '%s' by '%s' uuid:%s" % - (bl_book.title, bl_book.author,bl_book.uuid)) + logger().info(" evaluating '%s' by '%s' uuid:%s" % + (bl_book.title, bl_book.author, bl_book.uuid)) found = False - if bl_book.uuid == self.cached_books[path]['uuid']: - if False: - logger().info(" matched with uuid") + if bl_book.uuid and bl_book.uuid == self.cached_books[path]['uuid']: + if True: + logger().info(" --matched uuid") booklists[0].pop(i) found = True elif bl_book.title == self.cached_books[path]['title'] and \ - bl_book.author[0] == self.cached_books[path]['author']: - if False: - logger().info(" matched with title + author") + bl_book.author == self.cached_books[path]['author']: + if True: + logger().info(" --matched title + author") booklists[0].pop(i) found = True if found: # Remove from self.cached_books for cb in self.cached_books: - if self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid']: + if (self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid'] and + self.cached_books[cb]['author'] == self.cached_books[path]['author'] and + self.cached_books[cb]['title'] == self.cached_books[path]['title']): self.cached_books.pop(cb) break + else: + logger().error(" '%s' not found in self.cached_books" % self.cached_books[path]['title']) # Remove from thumb from thumb cache thumb_path = path.rpartition('.')[0] + '.jpg' - zf = ZipFile(self.archive_path,'a') + zf = ZipFile(self.archive_path, 'a') fnames = zf.namelist() try: thumb = [x for x in fnames if thumb_path in x][0] @@ -965,14 +982,16 @@ class ITUNES(DriverBase): else: if DEBUG: logger().error(" unable to find '%s' by '%s' (%s)" % - (bl_book.title, bl_book.author,bl_book.uuid)) + (self.cached_books[path]['title'], + self.cached_books[path]['author'], + self.cached_books[path]['uuid'])) if False: - self._dump_booklist(booklists[0], indent = 2) + self._dump_booklist(booklists[0], indent=2) self._dump_cached_books(indent=2) def reset(self, key='-1', log_packets=False, report_progress=None, - detected_device=None) : + detected_device=None): """ :key: The key to unlock the device :log_packets: If true the packet stream to/from the device is logged @@ -1051,7 +1070,7 @@ class ITUNES(DriverBase): connected_device = self.sources['iPod'] capacity = self.iTunes.sources[connected_device].capacity() - return (capacity,-1,-1) + return (capacity, -1, -1) def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): @@ -1085,7 +1104,7 @@ class ITUNES(DriverBase): logger().info("%s.upload_books()" % self.__class__.__name__) if isosx: - for (i,fpath) in enumerate(files): + for (i, fpath) in enumerate(files): format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1110,12 +1129,12 @@ class ITUNES(DriverBase): 'format': format, 'lib_book': lb_added, 'title': metadata[i].title, - 'uuid': metadata[i].uuid } + 'uuid': metadata[i].uuid} # Report progress if self.report_progress is not None: - self.report_progress((i+1)/file_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=file_count)) + self.report_progress((i + 1) / file_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=file_count)) elif iswindows: import pythoncom, win32com.client @@ -1124,7 +1143,7 @@ class ITUNES(DriverBase): pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - for (i,fpath) in enumerate(files): + for (i, fpath) in enumerate(files): format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1160,8 +1179,8 @@ class ITUNES(DriverBase): # Report progress if self.report_progress is not None: - self.report_progress((i+1)/file_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=file_count)) + self.report_progress((i + 1) / file_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=file_count)) finally: pythoncom.CoUninitialize() @@ -1174,12 +1193,12 @@ class ITUNES(DriverBase): self.update_msg = "Added books to device" if False: - self._dump_booklist(new_booklist,header="after upload_books()",indent=2) - self._dump_cached_books(header="after upload_books()",indent=2) + self._dump_booklist(new_booklist, header="after upload_books()", indent=2) + self._dump_cached_books(header="after upload_books()", indent=2) return (new_booklist, [], []) # Private methods - def _add_device_book(self,fpath, metadata): + def _add_device_book(self, fpath, metadata): ''' assumes pythoncom wrapper for windows ''' @@ -1201,7 +1220,7 @@ class ITUNES(DriverBase): delay = 1.0 while attempts: try: - added = pl.add(appscript.mactypes.File(fpath),to=pl) + added = pl.add(appscript.mactypes.File(fpath), to=pl) if False: logger().info(" '%s' added to Device|Books" % metadata.title) break @@ -1282,13 +1301,13 @@ class ITUNES(DriverBase): base_fn = fpath.rpartition(os.sep)[2] base_fn = base_fn.rpartition('.')[0] db_added = self._find_device_book( - { 'title': base_fn if format == 'pdf' else metadata.title, + {'title': base_fn if format == 'pdf' else metadata.title, 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return db_added - def _add_library_book(self,file, metadata): + def _add_library_book(self, file, metadata): ''' windows assumes pythoncom wrapper ''' @@ -1349,7 +1368,7 @@ class ITUNES(DriverBase): base_fn = file.rpartition(os.sep)[2] base_fn = base_fn.rpartition('.')[0] added = self._find_library_book( - { 'title': base_fn if format == 'pdf' else metadata.title, + {'title': base_fn if format == 'pdf' else metadata.title, 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) @@ -1372,7 +1391,7 @@ class ITUNES(DriverBase): # If using iTunes_local_storage, copy the file, redirect iTunes to use local copy if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: local_copy = os.path.join(self.iTunes_local_storage, str(metadata.uuid) + os.path.splitext(fpath)[1]) - shutil.copyfile(fpath,local_copy) + shutil.copyfile(fpath, local_copy) fpath = local_copy if self.manual_sync_mode: @@ -1418,18 +1437,18 @@ class ITUNES(DriverBase): if scaled: if DEBUG: logger().info(" cover scaled from %sx%s to %sx%s" % - (width,height,nwidth,nheight)) + (width, height, nwidth, nheight)) img = img.resize((nwidth, nheight), PILImage.ANTIALIAS) cd = cStringIO.StringIO() img.convert('RGB').save(cd, 'JPEG') cover_data = cd.getvalue() cd.close() else: - with open(metadata.cover,'r+b') as cd: + with open(metadata.cover, 'r+b') as cd: cover_data = cd.read() except: self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) - logger().error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) + logger().error(" error scaling '%s' for '%s'" % (metadata.cover, metadata.title)) import traceback traceback.print_exc() @@ -1468,7 +1487,7 @@ class ITUNES(DriverBase): elif iswindows: ''' Write the data to a real file for Windows iTunes ''' tc = os.path.join(tempfile.gettempdir(), "cover.jpg") - with open(tc,'wb') as tmp_cover: + with open(tc, 'wb') as tmp_cover: tmp_cover.write(cover_data) if lb_added: @@ -1506,13 +1525,13 @@ class ITUNES(DriverBase): # Refresh the thumbnail cache if DEBUG: - logger().info( " refreshing cached thumb for '%s'" % metadata.title) + logger().info(" refreshing cached thumb for '%s'" % metadata.title) zfw = ZipFile(self.archive_path, mode='a') thumb_path = path.rpartition('.')[0] + '.jpg' zfw.writestr(thumb_path, thumb) except: self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) - logger().error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) + logger().error(" error converting '%s' to thumb for '%s'" % (metadata.cover, metadata.title)) finally: try: zfw.close() @@ -1523,7 +1542,7 @@ class ITUNES(DriverBase): logger().info(" no cover defined in metadata for '%s'" % metadata.title) return thumb - def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb, format): + def _create_new_book(self, fpath, metadata, path, db_added, lb_added, thumb, format): ''' ''' if DEBUG: @@ -1604,7 +1623,7 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" adding tracer to empty Books|Playlist") try: - added = pl.add(appscript.mactypes.File(P('tracer.epub')),to=pl) + added = pl.add(appscript.mactypes.File(P('tracer.epub')), to=pl) time.sleep(0.5) added.delete() self.manual_sync_mode = True @@ -1635,9 +1654,9 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" sending tracer to empty Books|Playlist") fpath = P('tracer.epub') - mi = MetaInformation('Tracer',['calibre']) + mi = MetaInformation('Tracer', ['calibre']) try: - added = self._add_device_book(fpath,mi) + added = self._add_device_book(fpath, mi) time.sleep(0.5) added.Delete() self.manual_sync_mode = True @@ -1646,40 +1665,40 @@ class ITUNES(DriverBase): logger().info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode) - def _dump_booklist(self, booklist, header=None,indent=0): + def _dump_booklist(self, booklist, header=None, indent=0): ''' ''' if header: - msg = '\n%sbooklist %s:' % (' '*indent,header) + msg = '\n%sbooklist %s:' % (' ' * indent, header) logger().info(msg) - logger().info('%s%s' % (' '*indent,'-' * len(msg))) + logger().info('%s%s' % (' ' * indent, '-' * len(msg))) for book in booklist: if isosx: - logger().info("%s%-40.40s %-30.30s %-10.10s %s" % - (' '*indent,book.title, book.author, str(book.library_id)[-9:], book.uuid)) + logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s" % + (' ' * indent, book.title, book.author, book.uuid, str(book.library_id)[-9:])) elif iswindows: logger().info("%s%-40.40s %-30.30s" % - (' '*indent,book.title, book.author)) + (' ' * indent, book.title, book.author)) logger().info() - def _dump_cached_book(self, cached_book, header=None,indent=0): + def _dump_cached_book(self, cached_book, header=None, indent=0): ''' ''' if isosx: if header: - msg = '%s%s' % (' '*indent,header) + msg = '%s%s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent, '-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + (' ' * indent, 'title', 'author', 'lib_book', 'dev_book', 'uuid')) logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + (' ' * indent, cached_book['title'], cached_book['author'], str(cached_book['lib_book'])[-9:], @@ -1687,12 +1706,12 @@ class ITUNES(DriverBase): cached_book['uuid'])) elif iswindows: if header: - msg = '%s%s' % (' '*indent,header) + msg = '%s%s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent, '-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) logger().info("%s%-40.40s %-30.30s %s" % - (' '*indent, + (' ' * indent, cached_book['title'], cached_book['author'], cached_book['uuid'])) @@ -1701,22 +1720,23 @@ class ITUNES(DriverBase): ''' ''' if header: - msg = '\n%sself.cached_books %s:' % (' '*indent,header) + msg = '\n%sself.cached_books %s:' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent,'-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) if isosx: for cb in self.cached_books.keys(): - logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s %-10.10s" % + (' ' * indent, self.cached_books[cb]['title'], self.cached_books[cb]['author'], + self.cached_books[cb]['uuid'], str(self.cached_books[cb]['lib_book'])[-9:], str(self.cached_books[cb]['dev_book'])[-9:], - self.cached_books[cb]['uuid'])) + )) elif iswindows: for cb in self.cached_books.keys(): logger().info("%s%-40.40s %-30.30s %-4.4s %s" % - (' '*indent, + (' ' * indent, self.cached_books[cb]['title'], self.cached_books[cb]['author'], self.cached_books[cb]['format'], @@ -1733,7 +1753,7 @@ class ITUNES(DriverBase): title = None author = None timestamp = None - zf = ZipFile(fpath,'r') + zf = ZipFile(fpath, 'r') fnames = zf.namelist() opf = [x for x in fnames if '.opf' in x][0] if opf: @@ -1742,14 +1762,14 @@ class ITUNES(DriverBase): opf_raw.close() title = soup.find('dc:title').renderContents() author = soup.find('dc:creator').renderContents() - ts = soup.find('meta',attrs={'name':'calibre:timestamp'}) + ts = soup.find('meta', attrs={'name': 'calibre:timestamp'}) if ts: # Touch existing calibre timestamp timestamp = ts['content'] if not title or not author: if DEBUG: - logger().error(" couldn't extract title/author from %s in %s" % (opf,fpath)) + logger().error(" couldn't extract title/author from %s in %s" % (opf, fpath)) logger().error(" title: %s author: %s timestamp: %s" % (title, author, timestamp)) else: if DEBUG: @@ -1760,14 +1780,15 @@ class ITUNES(DriverBase): def _dump_hex(self, src, length=16): ''' ''' - FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) - N=0; result='' + FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) + N = 0 + result = '' while src: - s,src = src[:length],src[length:] - hexa = ' '.join(["%02X"%ord(x) for x in s]) - s = s.translate(FILTER) - result += "%04X %-*s %s\n" % (N, length*3, hexa, s) - N+=length + s, src = src[:length], src[length:] + hexa = ' '.join(["%02X" % ord(x) for x in s]) + s = s.translate(FILTER) + result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) + N += length print result def _dump_library_books(self, library_books): @@ -1779,16 +1800,16 @@ class ITUNES(DriverBase): logger().info(" %s" % book) logger().info() - def _dump_update_list(self,header=None,indent=0): + def _dump_update_list(self, header=None, indent=0): if header and self.update_list: - msg = '\n%sself.update_list %s' % (' '*indent,header) + msg = '\n%sself.update_list %s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent,'-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) if isosx: for ub in self.update_list: logger().info("%s%-40.40s %-30.30s %-10.10s %s" % - (' '*indent, + (' ' * indent, ub['title'], ub['author'], str(ub['lib_book'])[-9:], @@ -1796,7 +1817,7 @@ class ITUNES(DriverBase): elif iswindows: for ub in self.update_list: logger().info("%s%-40.40s %-30.30s" % - (' '*indent, + (' ' * indent, ub['title'], ub['author'])) @@ -1809,14 +1830,14 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" %s._find_device_book()" % self.__class__.__name__) logger().info(" searching for '%s' by '%s' (%s)" % - (search['title'], search['author'],search['uuid'])) + (search['title'], search['author'], search['uuid'])) attempts = 9 while attempts: # Try by uuid - only one hit if 'uuid' in search and search['uuid']: if DEBUG: logger().info(" searching by uuid '%s' ..." % search['uuid']) - hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) + hits = dev_books.Search(search['uuid'], self.SearchField.index('All')) if hits: hit = hits[0] logger().info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) @@ -1826,7 +1847,7 @@ class ITUNES(DriverBase): if search['author']: if DEBUG: logger().info(" searching by author '%s' ..." % search['author']) - hits = dev_books.Search(search['author'],self.SearchField.index('Artists')) + hits = dev_books.Search(search['author'], self.SearchField.index('Artists')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1837,7 +1858,7 @@ class ITUNES(DriverBase): # Search by title if no author available if DEBUG: logger().info(" searching by title '%s' ..." % search['title']) - hits = dev_books.Search(search['title'],self.SearchField.index('All')) + hits = dev_books.Search(search['title'], self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1851,8 +1872,8 @@ class ITUNES(DriverBase): title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) if DEBUG: - logger().info(" searching by name: '%s - %s'" % (title,author)) - hits = dev_books.Search('%s - %s' % (title,author), + logger().info(" searching by name: '%s - %s'" % (title, author)) + hits = dev_books.Search('%s - %s' % (title, author), self.SearchField.index('All')) if hits: hit = hits[0] @@ -1910,14 +1931,13 @@ class ITUNES(DriverBase): if DEBUG: logger().error(" no Books playlist found") - attempts = 9 while attempts: # Find book whose Album field = search['uuid'] if 'uuid' in search and search['uuid']: if DEBUG: logger().info(" searching by uuid '%s' ..." % search['uuid']) - hits = lib_books.Search(search['uuid'],self.SearchField.index('All')) + hits = lib_books.Search(search['uuid'], self.SearchField.index('All')) if hits: hit = hits[0] if DEBUG: @@ -1928,7 +1948,7 @@ class ITUNES(DriverBase): if search['author']: if DEBUG: logger().info(" searching by author '%s' ..." % search['author']) - hits = lib_books.Search(search['author'],self.SearchField.index('Artists')) + hits = lib_books.Search(search['author'], self.SearchField.index('Artists')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1939,7 +1959,7 @@ class ITUNES(DriverBase): # Search by title if no author available if DEBUG: logger().info(" searching by title '%s' ..." % search['title']) - hits = lib_books.Search(search['title'],self.SearchField.index('All')) + hits = lib_books.Search(search['title'], self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1953,8 +1973,8 @@ class ITUNES(DriverBase): title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) if DEBUG: - logger().info(" searching by name: %s - %s" % (title,author)) - hits = lib_books.Search('%s - %s' % (title,author), + logger().info(" searching by name: %s - %s" % (title, author)) + hits = lib_books.Search('%s - %s' % (title, author), self.SearchField.index('All')) if hits: hit = hits[0] @@ -2027,11 +2047,11 @@ class ITUNES(DriverBase): try: img_data = cStringIO.StringIO(data) im = PILImage.open(img_data) - scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) - im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80) + im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') + im.convert('RGB').save(thumb, 'JPEG') thumb_data = thumb.getvalue() thumb.close() if False: @@ -2051,7 +2071,6 @@ class ITUNES(DriverBase): return thumb_data - elif iswindows: if not book.Artwork.Count: if DEBUG: @@ -2067,10 +2086,10 @@ class ITUNES(DriverBase): book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb) # Resize the cover im = PILImage.open(tmp_thumb) - scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) - im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80) + im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') + im.convert('RGB').save(thumb, 'JPEG') thumb_data = thumb.getvalue() os.remove(tmp_thumb) thumb.close() @@ -2083,7 +2102,7 @@ class ITUNES(DriverBase): logger().error(" error generating thumb for '%s', caching empty marker" % book.Name) thumb_data = None # Cache the empty cover - zfw.writestr(thumb_path,'None') + zfw.writestr(thumb_path, 'None') finally: zfw.close() @@ -2097,7 +2116,7 @@ class ITUNES(DriverBase): exploded_file_size = compressed_size format = file.rpartition('.')[2].lower() if format == 'epub': - myZip = ZipFile(file,'r') + myZip = ZipFile(file, 'r') myZipList = myZip.infolist() exploded_file_size = 0 for file in myZipList: @@ -2133,14 +2152,13 @@ class ITUNES(DriverBase): logger().error(" book_playlist not found") for book in dev_books: - # This may need additional entries for international iTunes users if book.kind() in self.Audiobooks: if DEBUG: logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) else: if DEBUG: - logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % - (book.name(), book.artist(), book.album(), book.kind())) + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % + (book.name(), book.artist(), book.composer(), book.kind())) device_books.append(book) if DEBUG: logger().info() @@ -2167,13 +2185,12 @@ class ITUNES(DriverBase): logger().info(" no Books playlist found") for book in dev_books: - # This may need additional entries for international iTunes users if book.KindAsString in self.Audiobooks: if DEBUG: logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) else: if DEBUG: - logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Album, book.KindAsString)) + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Composer, book.KindAsString)) device_books.append(book) if DEBUG: logger().info() @@ -2247,7 +2264,7 @@ class ITUNES(DriverBase): else: # Collect calibre orphans - remnants of recipe uploads format = 'pdf' if book.kind().startswith('PDF') else 'epub' - path = self.path_template % (book.name(), book.artist(),format) + path = self.path_template % (book.name(), book.artist(), format) if str(book.description()).startswith(self.description_prefix): try: if book.location() == appscript.k.missing_value: @@ -2304,7 +2321,7 @@ class ITUNES(DriverBase): logger().info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) else: format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' - path = self.path_template % (book.Name, book.Artist,format) + path = self.path_template % (book.Name, book.Artist, format) # Collect calibre orphans if book.Description.startswith(self.description_prefix): @@ -2356,7 +2373,7 @@ class ITUNES(DriverBase): return {} elif iswindows: # Assumes a pythoncom wrapper - it_sources = ['Unknown','Library','iPod','AudioCD','MP3CD','Device','RadioTuner','SharedLibrary'] + it_sources = ['Unknown', 'Library', 'iPod', 'AudioCD', 'MP3CD', 'Device', 'RadioTuner', 'SharedLibrary'] names = [s.name for s in self.iTunes.sources] kinds = [it_sources[s.kind] for s in self.iTunes.sources] @@ -2369,12 +2386,12 @@ class ITUNES(DriverBase): kinds.pop(index) names.pop(index) - return dict(zip(kinds,names)) + return dict(zip(kinds, names)) - def _is_alpha(self,char): + def _is_alpha(self, char): ''' ''' - if not re.search('[a-zA-Z]',char): + if not re.search('[a-zA-Z]', char): return False else: return True @@ -2396,7 +2413,7 @@ class ITUNES(DriverBase): running_apps = appscript.app('System Events') if not 'iTunes' in running_apps.processes.name(): if DEBUG: - logger().info( "%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__) + logger().info("%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__) try: self.iTunes = iTunes = appscript.app('iTunes', hide=True) except: @@ -2422,7 +2439,7 @@ class ITUNES(DriverBase): except: # Try static binding import itunes - self.iTunes = appscript.app('iTunes',terms=itunes) + self.iTunes = appscript.app('iTunes', terms=itunes) try: foo = self.iTunes.name() as_binding = "static" @@ -2450,7 +2467,7 @@ class ITUNES(DriverBase): logger().info(" [OSX %s, %s %s (%s), %s driver version %d.%d.%d]" % (platform.mac_ver()[0], self.iTunes.name(), self.iTunes.version(), self.initial_status, - self.__class__.__name__, self.version[0],self.version[1],self.version[2])) + self.__class__.__name__, self.version[0], self.version[1], self.version[2])) logger().info(" communicating with iTunes via %s %s using %s binding" % (as_name, as_version, as_binding)) logger().info(" calibre_library_path: %s" % self.calibre_library_path) @@ -2519,41 +2536,47 @@ class ITUNES(DriverBase): logger().info(" %s %s" % (__appname__, __version__)) logger().info(" [Windows %s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.Windows[0].name, self.iTunes.Version, self.initial_status, - self.version[0],self.version[1],self.version[2])) + self.version[0], self.version[1], self.version[2])) logger().info(" calibre_library_path: %s" % self.calibre_library_path) - def _purge_orphans(self,library_books, cached_books): + def _purge_orphans(self, library_books, cached_books): ''' Scan library_books for any paths not on device Remove any iTunes orphans originally added by calibre This occurs when the user deletes a book in iBooks while disconnected ''' - if DEBUG: - logger().info(" %s._purge_orphans()" % self.__class__.__name__) - #self._dump_library_books(library_books) - #logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) + PURGE_ORPHANS = False - for book in library_books: - if isosx: - if book not in cached_books and \ - str(library_books[book].description()).startswith(self.description_prefix): - if DEBUG: - logger().info(" '%s' not found on iDevice, removing from iTunes" % book) - btr = { 'title':library_books[book].name(), - 'author':library_books[book].artist(), - 'lib_book':library_books[book]} - self._remove_from_iTunes(btr) - elif iswindows: - if book not in cached_books and \ - library_books[book].Description.startswith(self.description_prefix): - if DEBUG: - logger().info(" '%s' not found on iDevice, removing from iTunes" % book) - btr = { 'title':library_books[book].Name, - 'author':library_books[book].Artist, - 'lib_book':library_books[book]} - self._remove_from_iTunes(btr) - if DEBUG: - logger().info() + if PURGE_ORPHANS: + if DEBUG: + logger().info(" %s._purge_orphans()" % self.__class__.__name__) + #self._dump_library_books(library_books) + #logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) + + for book in library_books: + if isosx: + if book not in cached_books and \ + str(library_books[book].description()).startswith(self.description_prefix): + if DEBUG: + logger().info(" '%s' not found on iDevice, removing from iTunes" % book) + btr = { + 'title': library_books[book].name(), + 'author': library_books[book].artist(), + 'lib_book': library_books[book]} + self._remove_from_iTunes(btr) + elif iswindows: + if book not in cached_books and \ + library_books[book].Description.startswith(self.description_prefix): + if DEBUG: + logger().info(" '%s' not found on iDevice, removing from iTunes" % book) + btr = { + 'title': library_books[book].Name, + 'author': library_books[book].Artist, + 'lib_book': library_books[book]} + self._remove_from_iTunes(btr) + else: + if DEBUG: + logger().info(" %s._purge_orphans(disabled)" % self.__class__.__name__) def _remove_existing_copy(self, path, metadata): ''' @@ -2565,17 +2588,11 @@ class ITUNES(DriverBase): # Delete existing from Device|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid or \ - (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == authors_to_string(metadata.authors)): + if (self.cached_books[book]['uuid'] == metadata.uuid or + (self.cached_books[book]['title'] == metadata.title and + self.cached_books[book]['author'] == metadata.author)): self.update_list.append(self.cached_books[book]) - - if DEBUG: - logger().info( " deleting device book '%s'" % (metadata.title)) self._remove_from_device(self.cached_books[book]) - - if DEBUG: - logger().info(" deleting library book '%s'" % metadata.title) self._remove_from_iTunes(self.cached_books[book]) break else: @@ -2585,12 +2602,12 @@ class ITUNES(DriverBase): # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid or \ - (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == authors_to_string(metadata.authors)): + if (self.cached_books[book]['uuid'] == metadata.uuid or + (self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.author)): self.update_list.append(self.cached_books[book]) if DEBUG: - logger().info( " deleting library book '%s'" % metadata.title) + logger().info(" deleting library book '%s'" % metadata.title) self._remove_from_iTunes(self.cached_books[book]) break else: @@ -2619,7 +2636,7 @@ class ITUNES(DriverBase): else: if DEBUG: logger().warning(" unable to remove '%s' by '%s' (%s) from device" % - (cached_book['title'],cached_book['author'],cached_book['uuid'])) + (cached_book['title'], cached_book['author'], cached_book['uuid'])) def _remove_from_iTunes(self, cached_book): ''' @@ -2668,7 +2685,8 @@ class ITUNES(DriverBase): except: # We get here if there was an error with .location().path if DEBUG: - logger().info(" '%s' not found in iTunes storage" % cached_book['title']) + logger().info(" '%s' by %s not found in iTunes storage" % + (cached_book['title'], cached_book['author'])) # Delete the book from the iTunes database try: @@ -2749,10 +2767,10 @@ class ITUNES(DriverBase): metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub') # Refresh epub metadata - with open(fpath,'r+b') as zfo: + with open(fpath, 'r+b') as zfo: if False: try: - zf_opf = ZipFile(fpath,'r') + zf_opf = ZipFile(fpath, 'r') fnames = zf_opf.namelist() opf = [x for x in fnames if '.opf' in x][0] except: @@ -2769,7 +2787,7 @@ class ITUNES(DriverBase): timestamp = ts.get('content') old_ts = parse_date(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) + old_ts.minute, old_ts.second, old_ts.microsecond + 1, old_ts.tzinfo) if DEBUG: logger().info(" existing timestamp: %s" % metadata.timestamp) else: @@ -2789,10 +2807,10 @@ class ITUNES(DriverBase): if _('News') in metadata_x.tags or \ _('Catalog') in metadata_x.tags: if metadata_x.title.find('[') > 0: - metadata_x.title = metadata_x.title[:metadata_x.title.find('[')-1] + metadata_x.title = metadata_x.title[:metadata_x.title.find('[') - 1] date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) metadata_x.author = metadata_x.authors = [date_as_author] - sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', metadata_x.title).rstrip() + sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', metadata_x.title).rstrip() metadata_x.author_sort = '%s %s' % (sort_author, strftime('%Y-%m-%d')) # Remove any non-alpha category tags @@ -2873,7 +2891,7 @@ class ITUNES(DriverBase): 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.description.set("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) lb_added.enabled.set(True) lb_added.sort_artist.set(icu_title(metadata_x.author_sort)) lb_added.sort_name.set(metadata_x.title_sort) @@ -2884,7 +2902,7 @@ class ITUNES(DriverBase): 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.description.set("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) db_added.enabled.set(True) db_added.sort_artist.set(icu_title(metadata_x.author_sort)) db_added.sort_name.set(metadata_x.title_sort) @@ -2892,17 +2910,17 @@ class ITUNES(DriverBase): if metadata_x.comments: if lb_added: - lb_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments)) + lb_added.comment.set(STRIP_TAGS.sub('', metadata_x.comments)) if db_added: - db_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments)) + db_added.comment.set(STRIP_TAGS.sub('', metadata_x.comments)) if metadata_x.rating: if lb_added: - lb_added.rating.set(metadata_x.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_x.rating*10) + db_added.rating.set(metadata_x.rating * 10) except: pass @@ -2917,7 +2935,7 @@ class ITUNES(DriverBase): # Format the index as a sort key index = metadata_x.series_index integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: # If no title_sort plugboard tweak, create sort_name from series/index @@ -2953,7 +2971,6 @@ class ITUNES(DriverBase): db_added.genre.set(tag) break - elif metadata_x.tags is not None: if DEBUG: logger().info(" %susing Tag as Genre" % @@ -2972,7 +2989,7 @@ class ITUNES(DriverBase): 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.Description = ("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Enabled = True lb_added.SortArtist = icu_title(metadata_x.author_sort) lb_added.SortName = metadata_x.title_sort @@ -2985,7 +3002,7 @@ class ITUNES(DriverBase): 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.Description = ("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) db_added.Enabled = True db_added.SortArtist = icu_title(metadata_x.author_sort) db_added.SortName = metadata_x.title_sort @@ -2993,17 +3010,17 @@ class ITUNES(DriverBase): if metadata_x.comments: if lb_added: - lb_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments)) + lb_added.Comment = (STRIP_TAGS.sub('', metadata_x.comments)) if db_added: - db_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments)) + db_added.Comment = (STRIP_TAGS.sub('', metadata_x.comments)) if metadata_x.rating: if lb_added: - lb_added.AlbumRating = (metadata_x.rating*10) + lb_added.AlbumRating = (metadata_x.rating * 10) # iBooks currently doesn't allow setting rating ... ? try: if db_added: - db_added.AlbumRating = (metadata_x.rating*10) + db_added.AlbumRating = (metadata_x.rating * 10) except: if DEBUG: logger().warning(" iTunes automation interface reported an error" @@ -3019,7 +3036,7 @@ class ITUNES(DriverBase): # Format the index as a sort key index = metadata_x.series_index integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: # If no title_sort plugboard tweak, create sort_name from series/index @@ -3147,6 +3164,7 @@ class ITUNES(DriverBase): newmi = book return newmi + class ITUNES_ASYNC(ITUNES): ''' This subclass allows the user to interact directly with iTunes via a menu option @@ -3155,14 +3173,14 @@ class ITUNES_ASYNC(ITUNES): name = 'iTunes interface' gui_name = 'Apple iTunes' icon = I('devices/itunes.png') - description = _('Communicate with iTunes.') + description = _('Communicate with iTunes.') # Plugboard ID DEVICE_PLUGBOARD_NAME = 'APPLE' connected = False - def __init__(self,path): + def __init__(self, path): if DEBUG: logger().info("%s.__init__()" % self.__class__.__name__) @@ -3228,7 +3246,7 @@ class ITUNES_ASYNC(ITUNES): if isosx: library_books = self._get_library_books() book_count = float(len(library_books)) - for (i,book) in enumerate(library_books): + for (i, book) in enumerate(library_books): format = 'pdf' if library_books[book].kind().startswith('PDF') else 'epub' this_book = Book(library_books[book].name(), library_books[book].artist()) #this_book.path = library_books[book].location().path @@ -3253,17 +3271,17 @@ class ITUNES_ASYNC(ITUNES): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':library_books[book].name(), - 'author':library_books[book].artist().split(' & '), - 'lib_book':library_books[book], - 'dev_book':None, + 'title': library_books[book].name(), + 'author': library_books[book].artist().split(' & '), + 'lib_book': library_books[book], + 'dev_book': None, 'uuid': library_books[book].composer(), 'format': format } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=book_count)) + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) elif iswindows: import pythoncom, win32com.client @@ -3273,7 +3291,7 @@ class ITUNES_ASYNC(ITUNES): self.iTunes = win32com.client.Dispatch("iTunes.Application") library_books = self._get_library_books() book_count = float(len(library_books)) - for (i,book) in enumerate(library_books): + for (i, book) in enumerate(library_books): this_book = Book(library_books[book].Name, library_books[book].Artist) format = 'pdf' if library_books[book].KindAsString.startswith('PDF') else 'epub' this_book.path = self.path_template % (library_books[book].Name, @@ -3296,16 +3314,16 @@ class ITUNES_ASYNC(ITUNES): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':library_books[book].Name, - 'author':library_books[book].Artist.split(' & '), - 'lib_book':library_books[book], + 'title': library_books[book].Name, + 'author': library_books[book].Artist.split(' & '), + 'lib_book': library_books[book], 'uuid': library_books[book].Composer, 'format': format } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) finally: @@ -3316,7 +3334,7 @@ class ITUNES_ASYNC(ITUNES): self.cached_books = cached_books if DEBUG: self._dump_booklist(booklist, 'returning from books()', indent=2) - self._dump_cached_books('returning from books()',indent=2) + self._dump_cached_books('returning from books()', indent=2) return booklist else: @@ -3352,7 +3370,7 @@ class ITUNES_ASYNC(ITUNES): free_bytes = ctypes.c_ulonglong(0) ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.sep), None, None, ctypes.pointer(free_bytes)) free_space = free_bytes.value - return (free_space,-1,-1) + return (free_space, -1, -1) def get_device_information(self, end_session=True): """ @@ -3362,7 +3380,7 @@ class ITUNES_ASYNC(ITUNES): if DEBUG: logger().info("%s.get_device_information()" % self.__class__.__name__) - return ('iTunes','hw v1.0','sw v1.0', 'mime type normally goes here') + return ('iTunes', 'hw v1.0', 'sw v1.0', 'mime type normally goes here') def is_usb_connected(self, devices_on_system, debug=False, only_presence=False): @@ -3398,7 +3416,7 @@ class ITUNES_ASYNC(ITUNES): if not os.path.exists(self.archive_path): logger().info(" creating zip archive") zfw = ZipFile(self.archive_path, mode='w') - zfw.writestr("iTunes Thumbs Archive",'') + zfw.writestr("iTunes Thumbs Archive", '') zfw.close() else: if DEBUG: @@ -3406,7 +3424,7 @@ class ITUNES_ASYNC(ITUNES): # If enabled in config options, create/confirm an iTunes storage folder if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: - self.iTunes_local_storage = os.path.join(config_dir,'iTunes storage') + self.iTunes_local_storage = os.path.join(config_dir, 'iTunes storage') if not os.path.exists(self.iTunes_local_storage): if DEBUG: logger()(" creating iTunes_local_storage at '%s'" % self.iTunes_local_storage) @@ -3441,6 +3459,7 @@ class ITUNES_ASYNC(ITUNES): logger().info("%s.unmount_device()" % self.__class__.__name__) self.connected = False + class BookList(list): ''' A list of books. Each Book object must have the fields: @@ -3493,16 +3512,17 @@ class BookList(list): ''' return {} + class Book(Metadata): ''' A simple class describing a book in the iTunes Books Library. See ebooks.metadata.book.base ''' - def __init__(self,title,author): + def __init__(self, title, author): Metadata.__init__(self, title, authors=author.split(' & ')) + self.author = author self.author_sort = author_to_author_sort(author) @property def title_sorter(self): return title_sort(self.title) - diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py index 49df903320..fd2bb5113b 100644 --- a/src/calibre/library/catalogs/csv_xml.py +++ b/src/calibre/library/catalogs/csv_xml.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2012, Kovid GoyalInconsistent Author Sort values for Author
" +
"'{!s}':
Unable to build MOBI catalog.
" +
"Select all books by '{!s}', apply correct Author Sort value in Edit Metadata dialog, then rebuild the catalog.\n
".format(author[0]))
@@ -553,7 +557,7 @@ class CatalogBuilder(object):
self.error.append('Author Sort mismatch')
error_msg = _("Warning: Inconsistent Author Sort values for Author '{!s}':\n".format(author[0]) +
- " {!s} != {!s}\n".format(author[1],current_author[1]))
+ " {!s} != {!s}\n".format(author[1], current_author[1]))
self.opts.log.warn('\n*** Metadata warning ***')
self.opts.log.warn(error_msg)
self.error.append(error_msg)
@@ -576,7 +580,7 @@ class CatalogBuilder(object):
"""
def _log_prefix_rule_match_info(rule, record, matched):
self.opts.log.info(" %s '%s' by %s (%s: '%s' contains '%s')" %
- (rule['prefix'],record['title'],
+ (rule['prefix'], record['title'],
record['authors'][0], rule['name'],
self.db.metadata_for_field(rule['field'])['name'],
matched))
@@ -585,10 +589,10 @@ class CatalogBuilder(object):
for rule in self.prefix_rules:
# Literal comparison for Tags field
if rule['field'].lower() == 'tags':
- if rule['pattern'].lower() in map(unicode.lower,record['tags']):
- if self.opts.verbose:
+ if rule['pattern'].lower() in map(unicode.lower, record['tags']):
+ if self.DEBUG and self.opts.verbose:
self.opts.log.info(" %s '%s' by %s (%s: Tags includes '%s')" %
- (rule['prefix'],record['title'],
+ (rule['prefix'], record['title'],
record['authors'][0], rule['name'],
rule['pattern']))
return rule['prefix']
@@ -602,7 +606,7 @@ class CatalogBuilder(object):
if field_contents == '':
field_contents = None
- if (self.db.metadata_for_field(rule['field'])['datatype'] == 'bool' and
+ if (self.db.metadata_for_field(rule['field'])['datatype'] == 'bool' and
field_contents is None):
# Handle condition where field is a bool and contents is None,
# which is displayed as No
@@ -616,7 +620,7 @@ class CatalogBuilder(object):
try:
if re.search(rule['pattern'], unicode(field_contents),
re.IGNORECASE) is not None:
- if self.opts.verbose:
+ if self.DEBUG:
_log_prefix_rule_match_info(rule, record, field_contents)
return rule['prefix']
except:
@@ -624,12 +628,24 @@ class CatalogBuilder(object):
self.opts.log.error("pattern failed to compile: %s" % rule['pattern'])
pass
elif field_contents is None and rule['pattern'] == 'None':
- if self.opts.verbose:
+ if self.DEBUG:
_log_prefix_rule_match_info(rule, record, field_contents)
return rule['prefix']
return None
+ def dump_custom_fields(self):
+ """
+ Dump custom field mappings for debugging
+ """
+ if self.opts.verbose:
+ self.opts.log.info(" Custom fields:")
+ all_custom_fields = self.db.custom_field_keys()
+ for cf in all_custom_fields:
+ self.opts.log.info(" %-20s %-20s %s" %
+ (cf, "'%s'" % self.db.metadata_for_field(cf)['name'],
+ self.db.metadata_for_field(cf)['datatype']))
+
def establish_equivalencies(self, item_list, key=None):
""" Return icu equivalent sort letter.
@@ -647,9 +663,9 @@ class CatalogBuilder(object):
# Hack to force the cataloged leading letter to be
# an unadorned character if the accented version sorts before the unaccented
exceptions = {
- u'Ä':u'A',
- u'Ö':u'O',
- u'Ü':u'U'
+ u'Ä': u'A',
+ u'Ö': u'O',
+ u'Ü': u'U'
}
if key is not None:
@@ -697,7 +713,7 @@ class CatalogBuilder(object):
print(" establish_equivalencies():")
if key:
for idx, item in enumerate(item_list):
- print(" %s %s" % (cl_list[idx],item[sort_field]))
+ print(" %s %s" % (cl_list[idx], item[sort_field]))
else:
print(" %s %s" % (cl_list[idx], item))
@@ -716,7 +732,8 @@ class CatalogBuilder(object):
Outputs:
books_by_author: database, sorted by author
- authors: list of unique authors
+ authors: list of book authors. Two credited authors are considered an
+ individual entity
error: author_sort mismatches
Return:
@@ -728,6 +745,12 @@ class CatalogBuilder(object):
books_by_author = list(self.books_to_catalog)
self.detect_author_sort_mismatches(books_by_author)
+
+ # Assumes books_by_title already populated
+ # init books_by_description before relisting multiple authors
+ books_by_description = list(books_by_author) if self.opts.sort_descriptions_by_author \
+ else list(self.books_by_title)
+
if self.opts.cross_reference_authors:
books_by_author = self.relist_multiple_authors(books_by_author)
@@ -737,16 +760,19 @@ class CatalogBuilder(object):
asl = [i['author_sort'] for i in books_by_author]
las = max(asl, key=len)
+ self.books_by_description = sorted(books_by_description,
+ key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
+
books_by_author = sorted(books_by_author,
key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
if self.DEBUG and self.opts.verbose:
tl = [i['title'] for i in books_by_author]
lt = max(tl, key=len)
- fs = '{:<6}{:<%d} {:<%d} {!s}' % (len(lt),len(las))
- print(fs.format('','Title','Author','Series'))
+ fs = '{:<6}{:<%d} {:<%d} {!s}' % (len(lt), len(las))
+ print(fs.format('', 'Title', 'Author', 'Series'))
for i in books_by_author:
- print(fs.format('', i['title'],i['author_sort'],i['series']))
+ print(fs.format('', i['title'], i['author_sort'], i['series']))
# Build the unique_authors set from existing data
authors = [(record['author'], capitalize(record['author_sort'])) for record in books_by_author]
@@ -758,7 +784,8 @@ class CatalogBuilder(object):
current_author = authors[0]
multiple_authors = False
unique_authors = []
- for (i,author) in enumerate(authors):
+ individual_authors = set()
+ for (i, author) in enumerate(authors):
if author != current_author:
# Note that current_author and author are tuples: (friendly, sort)
multiple_authors = True
@@ -768,7 +795,7 @@ class CatalogBuilder(object):
books_by_current_author))
current_author = author
books_by_current_author = 1
- elif i==0 and len(authors) == 1:
+ elif i == 0 and len(authors) == 1:
# Allow for single-book lists
unique_authors.append((current_author[0], icu_title(current_author[1]),
books_by_current_author))
@@ -780,14 +807,23 @@ class CatalogBuilder(object):
unique_authors.append((current_author[0], icu_title(current_author[1]),
books_by_current_author))
+ self.authors = list(unique_authors)
+ self.books_by_author = books_by_author
+
+ for ua in unique_authors:
+ for ia in ua[0].replace(' & ', ' & ').split(' & '):
+ individual_authors.add(ia)
+ self.individual_authors = list(individual_authors)
+
if self.DEBUG and self.opts.verbose:
self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors))
for author in unique_authors:
self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20],
author[2])).encode('utf-8'))
+ self.opts.log.info("\nfetch_books_by_author(): %d individual authors" % len(individual_authors))
+ for author in sorted(individual_authors):
+ self.opts.log.info("%s" % author)
- self.authors = unique_authors
- self.books_by_author = books_by_author
return True
def fetch_books_by_title(self):
@@ -869,6 +905,7 @@ class CatalogBuilder(object):
this_title['title'] = self.convert_html_entities(record['title'])
if record['series']:
this_title['series'] = record['series']
+ self.all_series.add(this_title['series'])
this_title['series_index'] = record['series_index']
else:
this_title['series'] = None
@@ -969,11 +1006,11 @@ class CatalogBuilder(object):
index_is_id=True)
if notes:
if field_md['datatype'] == 'text':
- if isinstance(notes,list):
+ if isinstance(notes, list):
notes = ' · '.join(notes)
elif field_md['datatype'] == 'datetime':
- notes = format_date(notes,'dd MMM yyyy')
- this_title['notes'] = {'source':field_md['name'],'content':notes}
+ notes = format_date(notes, 'dd MMM yyyy')
+ this_title['notes'] = {'source': field_md['name'], 'content': notes}
return this_title
@@ -1000,7 +1037,7 @@ class CatalogBuilder(object):
data = self.plugin.search_sort_db(self.db, self.opts)
data = self.process_exclusions(data)
- if self.opts.verbose and self.prefix_rules:
+ if self.prefix_rules and self.DEBUG:
self.opts.log.info(" Added prefixes:")
# Populate this_title{} from data[{},{}]
@@ -1042,6 +1079,7 @@ class CatalogBuilder(object):
def initialize(self, save_template):
self._save_template = save_template
self.SUPPORTS_SUB_DIRS = True
+
def save_template(self):
return self._save_template
@@ -1069,8 +1107,8 @@ class CatalogBuilder(object):
if bookmark_extension:
for vol in storage:
- bkmk_path = path_map[id]['path'].replace(os.path.abspath('/ tag with book_count at the head
aTag = Tag(soup, "a")
anchor_name = friendly_name.lower()
- aTag['id'] = anchor_name.replace(" ","")
- pTag.insert(0,aTag)
- pTag.insert(1,NavigableString('%s' % friendly_name))
- body.insert(btc,pTag)
+ aTag['id'] = anchor_name.replace(" ", "")
+ pTag.insert(0, aTag)
+ pTag.insert(1, NavigableString('%s' % friendly_name))
+ body.insert(btc, pTag)
btc += 1
# Add the divTag to the body
@@ -2423,7 +2461,7 @@ class CatalogBuilder(object):
pTag = Tag(soup, "p")
pTag['class'] = 'title'
ptc = 0
- aTag = Tag(soup,'a')
+ aTag = Tag(soup, 'a')
aTag['id'] = 'section_start'
pTag.insert(ptc, aTag)
ptc += 1
@@ -2432,11 +2470,11 @@ class CatalogBuilder(object):
# Kindle don't need this because it shows section titles in Periodical format
aTag = Tag(soup, "a")
aTag['id'] = "bytitle"
- pTag.insert(ptc,aTag)
+ pTag.insert(ptc, aTag)
ptc += 1
- pTag.insert(ptc,NavigableString(_('Titles')))
+ pTag.insert(ptc, NavigableString(_('Titles')))
- body.insert(btc,pTag)
+ body.insert(btc, pTag)
btc += 1
divTag = Tag(soup, "div")
@@ -2478,13 +2516,13 @@ class CatalogBuilder(object):
current_letter = self.letter_or_symbol(sort_equivalents[idx])
if current_letter == self.SYMBOLS:
aTag['id'] = self.SYMBOLS + "_titles"
- pIndexTag.insert(0,aTag)
- pIndexTag.insert(1,NavigableString(self.SYMBOLS))
+ pIndexTag.insert(0, aTag)
+ pIndexTag.insert(1, NavigableString(self.SYMBOLS))
else:
aTag['id'] = self.generate_unicode_name(current_letter) + "_titles"
- pIndexTag.insert(0,aTag)
- pIndexTag.insert(1,NavigableString(sort_equivalents[idx]))
- divRunningTag.insert(dtc,pIndexTag)
+ pIndexTag.insert(0, aTag)
+ pIndexTag.insert(1, NavigableString(sort_equivalents[idx]))
+ divRunningTag.insert(dtc, pIndexTag)
drtc += 1
# Add books
@@ -2510,7 +2548,7 @@ class CatalogBuilder(object):
formatted_title = self.by_titles_series_title_template.format(**args).rstrip()
else:
formatted_title = self.by_titles_normal_title_template.format(**args).rstrip()
- aTag.insert(0,NavigableString(escape(formatted_title)))
+ aTag.insert(0, NavigableString(escape(formatted_title)))
spanTag.insert(stc, aTag)
stc += 1
@@ -2524,7 +2562,7 @@ class CatalogBuilder(object):
if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(book['author']))
aTag.insert(0, NavigableString(book['author']))
- emTag.insert(0,aTag)
+ emTag.insert(0, aTag)
spanTag.insert(stc, emTag)
stc += 1
@@ -2611,7 +2649,7 @@ class CatalogBuilder(object):
author = book['author']
if book['prefix']:
- author_prefix = book['prefix'] + ' ' + _("by ")
+ author_prefix = book['prefix'] + ' ' + _("by ")
elif self.opts.connected_kindle and book['id'] in self.bookmarked_books:
author_prefix = self.SYMBOL_READING + ' ' + _("by ")
else:
@@ -2621,16 +2659,16 @@ class CatalogBuilder(object):
genres = ''
if 'genres' in book:
_soup = BeautifulSoup('')
- genresTag = Tag(_soup,'p')
+ genresTag = Tag(_soup, 'p')
gtc = 0
for (i, tag) in enumerate(sorted(book.get('genres', []))):
- aTag = Tag(_soup,'a')
+ aTag = Tag(_soup, 'a')
if self.opts.generate_genres:
aTag['href'] = "Genre_%s.html" % self.genre_tags_dict[tag]
- aTag.insert(0,escape(NavigableString(tag)))
+ aTag.insert(0, escape(NavigableString(tag)))
genresTag.insert(gtc, aTag)
gtc += 1
- if i < len(book['genres'])-1:
+ if i < len(book['genres']) - 1:
genresTag.insert(gtc, NavigableString(' · '))
gtc += 1
genres = genresTag.renderContents()
@@ -2650,12 +2688,12 @@ class CatalogBuilder(object):
pubdate = pubyear = pubmonth = ''
# Thumb
- _soup = BeautifulSoup('',selfClosingTags=['img'])
- thumb = Tag(_soup,"img")
+ _soup = BeautifulSoup('', selfClosingTags=['img'])
+ thumb = Tag(_soup, "img")
if 'cover' in book and book['cover']:
- thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
+ thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id'])
else:
- thumb['src'] = "../images/thumbnail_default.jpg"
+ thumb['src'] = "../images/thumbnail_default.jpg"
thumb['alt'] = "cover thumbnail"
# Publisher
@@ -2669,7 +2707,7 @@ class CatalogBuilder(object):
if stars:
star_string = self.SYMBOL_FULL_RATING * stars
empty_stars = self.SYMBOL_EMPTY_RATING * (5 - stars)
- rating = '%s%s
' % (star_string,empty_stars)
+ rating = '%s%s
' % (star_string, empty_stars)
# Notes
note_source = ''
@@ -2698,44 +2736,44 @@ class CatalogBuilder(object):
btc += 1
# Insert the link to the series or remove
- aTag = body.find('a', attrs={'class':'series_id'})
+ aTag = body.find('a', attrs={'class': 'series_id'})
if aTag:
if book['series']:
if self.opts.generate_series:
- aTag['href'] = "%s.html#%s" % ('BySeries',self.generate_series_anchor(book['series']))
+ aTag['href'] = "%s.html#%s" % ('BySeries', self.generate_series_anchor(book['series']))
else:
aTag.extract()
# Insert the author link
- aTag = body.find('a', attrs={'class':'author'})
+ aTag = body.find('a', attrs={'class': 'author'})
if self.opts.generate_authors and aTag:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generate_author_anchor(book['author']))
if publisher == ' ':
- publisherTag = body.find('td', attrs={'class':'publisher'})
+ publisherTag = body.find('td', attrs={'class': 'publisher'})
if publisherTag:
publisherTag.contents[0].replaceWith(' ')
if not genres:
- genresTag = body.find('p',attrs={'class':'genres'})
+ genresTag = body.find('p', attrs={'class': 'genres'})
if genresTag:
genresTag.extract()
if not formats:
- formatsTag = body.find('p',attrs={'class':'formats'})
+ formatsTag = body.find('p', attrs={'class': 'formats'})
if formatsTag:
formatsTag.extract()
if note_content == '':
- tdTag = body.find('td', attrs={'class':'notes'})
+ tdTag = body.find('td', attrs={'class': 'notes'})
if tdTag:
tdTag.contents[0].replaceWith(' ')
- emptyTags = body.findAll('td', attrs={'class':'empty'})
+ emptyTags = body.findAll('td', attrs={'class': 'empty'})
for mt in emptyTags:
- newEmptyTag = Tag(BeautifulSoup(),'td')
- newEmptyTag.insert(0,NavigableString(' '))
+ newEmptyTag = Tag(BeautifulSoup(), 'td')
+ newEmptyTag.insert(0, NavigableString(' '))
mt.replaceWith(newEmptyTag)
return soup
@@ -2758,7 +2796,7 @@ class CatalogBuilder(object):
self.update_progress_micro_step("%s %d of %d" %
(_("Description HTML"),
title_num, len(self.books_by_title)),
- float(title_num*100/len(self.books_by_title))/100)
+ float(title_num * 100 / len(self.books_by_title)) / 100)
# Generate the header from user-customizable template
soup = self.generate_html_description_header(title)
@@ -2795,7 +2833,7 @@ class CatalogBuilder(object):
# Insert the supplied title
soup = BeautifulSoup(header)
titleTag = soup.find('title')
- titleTag.insert(0,NavigableString(title))
+ titleTag.insert(0, NavigableString(title))
return soup
def generate_html_genre_header(self, title):
@@ -2814,10 +2852,10 @@ class CatalogBuilder(object):
bodyTag = soup.find('body')
pTag = Tag(soup, 'p')
pTag['class'] = 'title'
- bodyTag.insert(0,pTag)
+ bodyTag.insert(0, pTag)
divTag = Tag(soup, 'div')
divTag['class'] = 'authors'
- bodyTag.insert(1,divTag)
+ bodyTag.insert(1, divTag)
return soup
def generate_masthead_image(self, out_path):
@@ -2869,9 +2907,9 @@ class CatalogBuilder(object):
font = ImageFont.truetype(default_font, 48)
text = self.opts.catalog_title.encode('utf-8')
width, height = draw.textsize(text, font=font)
- left = max(int((MI_WIDTH - width)/2.), 0)
- top = max(int((MI_HEIGHT - height)/2.), 0)
- draw.text((left, top), text, fill=(0,0,0), font=font)
+ left = max(int((MI_WIDTH - width) / 2.), 0)
+ top = max(int((MI_HEIGHT - height) / 2.), 0)
+ draw.text((left, top), text, fill=(0, 0, 0), font=font)
img.save(open(out_path, 'wb'), 'GIF')
def generate_ncx_header(self):
@@ -2896,7 +2934,7 @@ class CatalogBuilder(object):
- comments = re.sub('[\r\n]','
', comments)
+ comments = re.sub('[\r\n]', '
', comments)
# Convert two hypens to emdash
- comments = re.sub('--','—',comments)
+ comments = re.sub('--', '—', comments)
soup = BeautifulSoup(comments)
result = BeautifulSoup()
rtc = 0
@@ -4593,15 +4637,15 @@ class CatalogBuilder(object):
for token in all_tokens:
if type(token) is NavigableString:
if not open_pTag:
- pTag = Tag(result,'p')
+ pTag = Tag(result, 'p')
open_pTag = True
ptc = 0
- pTag.insert(ptc,prepare_string_for_xml(token))
+ pTag.insert(ptc, prepare_string_for_xml(token))
ptc += 1
- elif token.name in ['br','b','i','em']:
+ elif token.name in ['br', 'b', 'i', 'em']:
if not open_pTag:
- pTag = Tag(result,'p')
+ pTag = Tag(result, 'p')
open_pTag = True
ptc = 0
pTag.insert(ptc, token)
@@ -4631,7 +4675,7 @@ class CatalogBuilder(object):
# Add back