diff --git a/resources/recipes/nytimes.recipe b/resources/recipes/nytimes.recipe index 3e02363f68..a2d5135045 100644 --- a/resources/recipes/nytimes.recipe +++ b/resources/recipes/nytimes.recipe @@ -89,6 +89,7 @@ class NYTimes(BasicNewsRecipe): 'relatedSearchesModule', 'side_tool', 'singleAd', + 'subNavigation clearfix', 'subNavigation tabContent active', 'subNavigation tabContent active clearfix', ]}), @@ -460,8 +461,10 @@ class NYTimes(BasicNewsRecipe): if mp_off >= 0: c = c[:mp_off] emTag.insert(0, c) - hrTag = Tag(soup, 'hr') - #hrTag['style'] = "margin-top:0em;margin-bottom:0em" + #hrTag = Tag(soup, 'hr') + #hrTag['class'] = 'caption_divider' + hrTag = Tag(soup, 'div') + hrTag['class'] = 'divider' emTag.insert(1, hrTag) caption.replaceWith(emTag) diff --git a/resources/recipes/nytimes_sub.recipe b/resources/recipes/nytimes_sub.recipe index f8ad12afe9..f4101ca299 100644 --- a/resources/recipes/nytimes_sub.recipe +++ b/resources/recipes/nytimes_sub.recipe @@ -76,6 +76,7 @@ class NYTimes(BasicNewsRecipe): 'relatedSearchesModule', 'side_tool', 'singleAd', + 'subNavigation clearfix', 'subNavigation tabContent active', 'subNavigation tabContent active clearfix', ]}), @@ -350,8 +351,10 @@ class NYTimes(BasicNewsRecipe): if mp_off >= 0: c = c[:mp_off] emTag.insert(0, c) - hrTag = Tag(soup, 'hr') - #hrTag['style'] = "margin-top:0em;margin-bottom:0em" + #hrTag = Tag(soup, 'hr') + #hrTag['class'] = 'caption_divider' + hrTag = Tag(soup, 'div') + hrTag['class'] = 'divider' emTag.insert(1, hrTag) caption.replaceWith(emTag) diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index d352ce5de7..0db3ce6a15 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -275,13 +275,44 @@ class iPadOutput(OutputProfile): # touchscreen_news_css {{{ touchscreen_news_css = u''' /* hr used in articles */ + .article_articles_list { + width:18%; + } + .article_link { + color: #593f29; + font-style: italic; + } + .article_next { + -webkit-border-top-right-radius:4px; + -webkit-border-bottom-right-radius:4px; + font-style: italic; + width:32%; + } + + .article_prev { + -webkit-border-top-left-radius:4px; + -webkit-border-bottom-left-radius:4px; + font-style: italic; + width:32%; + } + .article_sections_list { + width:18%; + } + .articles_link { + font-weight: bold; + } + .sections_link { + font-weight: bold; + } + + .caption_divider { border:#ccc 1px solid; } .touchscreen_navbar { - background:#ccc; - border:#ccc 1px solid; + background:#c3bab2; + border:#ccc 0px solid; border-collapse:separate; border-spacing:1px; margin-left: 5%; @@ -292,22 +323,16 @@ class iPadOutput(OutputProfile): .touchscreen_navbar td { background:#fff; font-family:Helvetica; - font-size:90%; - padding: 5px; + font-size:80%; + /* UI touchboxes use 8px padding */ + padding: 6px; text-align:center; } - .touchscreen_navbar td:first-child { - -webkit-border-top-left-radius:4px; - -webkit-border-bottom-left-radius:4px; - } - .touchscreen_navbar td:last-child { - -webkit-border-top-right-radius:4px; - -webkit-border-bottom-right-radius:4px; - } - .feed_link { - font-style: italic; - } + .touchscreen_navbar td a:link { + color: #593f29; + text-decoration: none; + } /* Index formatting */ .publish_date { @@ -318,12 +343,50 @@ class iPadOutput(OutputProfile): border-top:1px solid gray; } + hr.caption_divider { + border-color:black; + border-style:solid; + border-width:1px; + } + /* Feed summary formatting */ + .article_summary { + display:inline-block; + } + .feed { + font-family:sans-serif; + font-weight:bold; + font-size:larger; + } + + .feed_link { + font-style: italic; + } + + .feed_next { + -webkit-border-top-right-radius:4px; + -webkit-border-bottom-right-radius:4px; + font-style: italic; + width:40%; + } + + .feed_prev { + -webkit-border-top-left-radius:4px; + -webkit-border-bottom-left-radius:4px; + font-style: italic; + width:40%; + } + .feed_title { text-align: center; font-size: 160%; } + .feed_up { + font-weight: bold; + width:20%; + } + .summary_headline { font-weight:bold; text-align:left; @@ -338,12 +401,6 @@ class iPadOutput(OutputProfile): text-align:left; } - .feed { - font-family:sans-serif; - font-weight:bold; - font-size:larger; - } - ''' # }}} diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 59bef5b334..6c9235c0d4 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -20,7 +20,7 @@ from calibre.utils.config import config_dir from calibre.utils.date import isoformat, now, parse_date from calibre.utils.localization import get_lang from calibre.utils.logging import Log -from calibre.utils.zipfile import ZipFile +from calibre.utils.zipfile import ZipFile, safe_replace from PIL import Image as PILImage @@ -38,6 +38,7 @@ if iswindows: class DriverBase(DeviceConfig, DevicePlugin): # Needed for config_widget to work FORMATS = ['epub', 'pdf'] + SUPPORTS_SUB_DIRS = True # To enable second checkbox in customize widget @classmethod def _config_base_name(cls): @@ -87,7 +88,7 @@ class ITUNES(DriverBase): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0,8,0) + version = (0,9,0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -106,53 +107,55 @@ class ITUNES(DriverBase): BCD = [0x01] # iTunes enumerations - Sources = [ - 'Unknown', - 'Library', - 'iPod', - 'AudioCD', - 'MP3CD', - 'Device', - 'RadioTuner', - 'SharedLibrary'] - + Audiobooks = [ + 'Audible file', + 'MPEG audio file', + 'Protected AAC audio file' + ] ArtworkFormat = [ - 'Unknown', - 'JPEG', - 'PNG', - 'BMP' - ] - + 'Unknown', + 'JPEG', + 'PNG', + 'BMP' + ] PlaylistKind = [ - 'Unknown', - 'Library', - 'User', - 'CD', - 'Device', - 'Radio Tuner' - ] - + 'Unknown', + 'Library', + 'User', + 'CD', + 'Device', + 'Radio Tuner' + ] PlaylistSpecialKind = [ - 'Unknown', - 'Purchased Music', - 'Party Shuffle', - 'Podcasts', - 'Folder', - 'Video', - 'Music', - 'Movies', - 'TV Shows', - 'Books', - ] - + 'Unknown', + 'Purchased Music', + 'Party Shuffle', + 'Podcasts', + 'Folder', + 'Video', + 'Music', + 'Movies', + 'TV Shows', + 'Books', + ] SearchField = [ - 'All', - 'Visible', - 'Artists', - 'Albums', - 'Composers', - 'SongNames', - ] + 'All', + 'Visible', + 'Artists', + 'Albums', + 'Composers', + 'SongNames', + ] + Sources = [ + 'Unknown', + 'Library', + 'iPod', + 'AudioCD', + 'MP3CD', + 'Device', + 'RadioTuner', + 'SharedLibrary' + ] # Cover art size limits MAX_COVER_WIDTH = 510 @@ -161,6 +164,7 @@ class ITUNES(DriverBase): # Properties cached_books = {} cache_dir = os.path.join(config_dir, 'caches', 'itunes') + archive_path = os.path.join(cache_dir, "thumbs.zip") description_prefix = "added by calibre" ejected = False iTunes= None @@ -168,7 +172,7 @@ class ITUNES(DriverBase): library_orphans = None log = Log() manual_sync_mode = False - path_template = 'iTunes/%s - %s.epub' + path_template = 'iTunes/%s - %s.%s' problem_titles = [] problem_msg = None report_progress = None @@ -252,7 +256,7 @@ class ITUNES(DriverBase): (new_book.title, new_book.author)) booklists[0].append(new_book) - if DEBUG: + if False: self._dump_booklist(booklists[0],header='after',indent=2) self._dump_cached_books(header='after',indent=2) @@ -273,10 +277,13 @@ class ITUNES(DriverBase): """ if not oncard: if DEBUG: - self.log.info("ITUNES:books(oncard=%s)" % oncard) + self.log.info("ITUNES:books():") + if self.settings().use_subdirs: + self.log.info(" Cover fetching/caching enabled") + else: + self.log.info(" Cover fetching/caching disabled") # Fetch a list of books from iPod device connected to iTunes - if 'iPod' in self.sources: booklist = BookList(self.log) cached_books = {} @@ -287,11 +294,12 @@ class ITUNES(DriverBase): book_count = float(len(device_books)) for (i,book) in enumerate(device_books): this_book = Book(book.name(), book.artist()) - this_book.path = self.path_template % (book.name(), book.artist()) + format = 'pdf' if book.kind().startswith('PDF') else 'epub' + this_book.path = self.path_template % (book.name(), book.artist(),format) try: this_book.datetime = parse_date(str(book.date_added())).timetuple() except: - pass + this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None @@ -325,11 +333,12 @@ class ITUNES(DriverBase): book_count = float(len(device_books)) for (i,book) in enumerate(device_books): this_book = Book(book.Name, book.Artist) - this_book.path = self.path_template % (book.Name, book.Artist) + format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' + this_book.path = self.path_template % (book.Name, book.Artist,format) try: this_book.datetime = parse_date(str(book.DateAdded)).timetuple() except: - pass + this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None @@ -532,8 +541,10 @@ class ITUNES(DriverBase): # Turn off the Save template cw.opt_save_template.setVisible(False) cw.label.setVisible(False) - # Repurpose the checkbox + # Repurpose the metadata checkbox cw.opt_read_metadata.setText(_("Use Series as Category in iTunes/iBooks")) + # Repurpose the use_subdirs checkbox + cw.opt_use_subdirs.setText(_("Cache covers from iTunes/iBooks")) return cw def delete_books(self, paths, end_session=True): @@ -691,21 +702,19 @@ class ITUNES(DriverBase): self.log.info("ITUNES.open()") # Confirm/create thumbs archive - archive_path = os.path.join(self.cache_dir, "thumbs.zip") - if not os.path.exists(self.cache_dir): if DEBUG: self.log.info(" creating thumb cache '%s'" % self.cache_dir) os.makedirs(self.cache_dir) - if not os.path.exists(archive_path): + if not os.path.exists(self.archive_path): self.log.info(" creating zip archive") - zfw = ZipFile(archive_path, mode='w') + zfw = ZipFile(self.archive_path, mode='w') zfw.writestr("iTunes Thumbs Archive",'') zfw.close() else: if DEBUG: - self.log.info(" existing thumb cache at '%s'" % archive_path) + self.log.info(" existing thumb cache at '%s'" % self.archive_path) def remove_books_from_metadata(self, paths, booklists): ''' @@ -722,21 +731,61 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.remove_books_from_metadata()") for path in paths: - self._dump_cached_book(self.cached_books[path], indent=2) + if DEBUG: + self._dump_cached_book(self.cached_books[path], indent=2) + self.log.info(" looking for '%s' by '%s' (%s)" % + (self.cached_books[path]['title'], + self.cached_books[path]['author'], + self.cached_books[path]['uuid'])) - # Purge the booklist, self.cached_books + # Purge the booklist, self.cached_books, thumb cache for i,bl_book in enumerate(booklists[0]): if False: - self.log.info(" evaluating '%s'" % bl_book.uuid) - if bl_book.uuid == self.cached_books[path]['uuid']: - # Remove from booklists[0] - booklists[0].pop(i) + self.log.info(" evaluating '%s' by '%s' (%s)" % + (bl_book.title, bl_book.author,bl_book.uuid)) + found = False + if bl_book.uuid == self.cached_books[path]['uuid']: + if False: + self.log.info(" matched with 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: + self.log.info(" matched with 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']: self.cached_books.pop(cb) break + + # Remove from thumb from thumb cache + thumb_path = path.rpartition('.')[0] + '.jpg' + zf = ZipFile(self.archive_path,'a') + fnames = zf.namelist() + try: + thumb = [x for x in fnames if thumb_path in x][0] + except: + thumb = None + if thumb: + if DEBUG: + self.log.info(" deleting '%s' from cover cache" % (thumb_path)) + zf.delete(thumb_path) + else: + if DEBUG: + self.log.info(" '%s' not found in cover cache" % thumb_path) + zf.close() + break +# else: +# if DEBUG: +# self.log.error(" unable to find '%s' by '%s' (%s)" % +# (bl_book.title, bl_book.author,bl_book.uuid)) if False: self._dump_booklist(booklists[0], indent = 2) @@ -842,12 +891,11 @@ class ITUNES(DriverBase): self.log.info("ITUNES.upload_books()") self._dump_files(files, header='upload_books()',indent=2) self._dump_update_list(header='upload_books()',indent=2) - #self.log.info(" self.settings().format_map: %s" % self.settings().format_map) if isosx: for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0]) + path = self.path_template % (metadata[i].title, metadata[i].author[0],format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -856,7 +904,10 @@ class ITUNES(DriverBase): new_booklist.append(this_book) self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book) - # Add new_book to self.cached_paths + # Add new_book to self.cached_books + if DEBUG: + self.log.info(" adding '%s' by '%s' ['%s'] to self.cached_books" % + ( metadata[i].title, metadata[i].author, metadata[i].uuid)) self.cached_books[this_book.path] = { 'author': metadata[i].author, 'dev_book': db_added, @@ -877,7 +928,7 @@ class ITUNES(DriverBase): for (i,file) in enumerate(files): format = file.rpartition('.')[2].lower() - path = self.path_template % (metadata[i].title, metadata[i].author[0]) + path = self.path_template % (metadata[i].title, metadata[i].author[0],format) self._remove_existing_copy(path, metadata[i]) fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) @@ -1091,7 +1142,7 @@ class ITUNES(DriverBase): thumb = None if metadata.cover: - if (format == 'epub'): + if format == 'epub': # Pre-shrink cover # self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT try: @@ -1171,17 +1222,18 @@ class ITUNES(DriverBase): # Refresh the thumbnail cache if DEBUG: - self.log.info( " refreshing cached thumb for '%s'" % metadata.title) - archive_path = os.path.join(self.cache_dir, "thumbs.zip") - zfw = ZipFile(archive_path, mode='a') + self.log.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, metadata.author[0])) - self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) + self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) finally: zfw.close() - + else: + if DEBUG: + self.log.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): @@ -1190,8 +1242,9 @@ class ITUNES(DriverBase): if DEBUG: self.log.info(" ITUNES._create_new_book()") - this_book = Book(metadata.title, metadata.author[0]) - + #this_book = Book(metadata.title, metadata.author[0]) + this_book = Book(metadata.title, ' & '.join(metadata.author)) + this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] this_book.format = format @@ -1207,13 +1260,13 @@ class ITUNES(DriverBase): try: this_book.datetime = parse_date(str(lb_added.date_added())).timetuple() except: - this_book.datetime = time.gmtime() + pass elif db_added: this_book.size = self._get_device_book_size(fpath, db_added.size()) try: this_book.datetime = parse_date(str(db_added.date_added())).timetuple() except: - this_book.datetime = time.gmtime() + pass elif iswindows: if lb_added: @@ -1221,13 +1274,13 @@ class ITUNES(DriverBase): try: this_book.datetime = parse_date(str(lb_added.DateAdded)).timetuple() except: - this_book.datetime = time.gmtime() + pass elif db_added: this_book.size = self._get_device_book_size(fpath, db_added.Size) try: this_book.datetime = parse_date(str(db_added.DateAdded)).timetuple() except: - this_book.datetime = time.gmtime() + pass return this_book @@ -1244,7 +1297,8 @@ class ITUNES(DriverBase): plist = None if plist: if DEBUG: - self.log.info(" deleting %s from %s" % (pl_name,fpath)) + self.log.info(" _delete_iTunesMetadata_plist():") + self.log.info(" deleting '%s'\n from '%s'" % (pl_name,fpath)) zf.delete(pl_name) zf.close() @@ -1494,27 +1548,45 @@ class ITUNES(DriverBase): if iswindows: dev_books = self._get_device_books_playlist() if DEBUG: - self.log.info(" ITUNES._find_device_book(uuid)") - self.log.info(" searching for %s ('%s' by %s)" % - (search['uuid'], search['title'], search['author'])) + self.log.info(" ITUNES._find_device_book()") + self.log.info(" searching for '%s' by '%s' (%s)" % + (search['title'], search['author'],search['uuid'])) attempts = 9 while attempts: # Try by uuid - only one hit - hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) - if hits: - hit = hits[0] - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) - return hit + if 'uuid' in search and search['uuid']: + if DEBUG: + self.log.info(" searching by uuid '%s' ..." % search['uuid']) + hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) + if hits: + hit = hits[0] + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + return hit # Try by author - there could be multiple hits - hits = dev_books.Search(search['author'],self.SearchField.index('Artists')) + if search['author']: + if DEBUG: + self.log.info(" searching by author '%s' ..." % search['author']) + hits = dev_books.Search(search['author'],self.SearchField.index('Artists')) + if hits: + for hit in hits: + if hit.Name == search['title']: + if DEBUG: + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + return hit + + # Search by title if no author available + if DEBUG: + self.log.info(" searching by title '%s' ..." % search['title']) + hits = dev_books.Search(search['title'],self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s'" % (hit.Name)) return hit + # PDF just sent, title not updated yet, look for export pattern # PDF metadata was rewritten at export as 'safe(title) - safe(author)' if search['format'] == 'pdf': title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) @@ -1547,12 +1619,14 @@ class ITUNES(DriverBase): if iswindows: if DEBUG: self.log.info(" ITUNES._find_library_book()") + ''' if 'uuid' in search: self.log.info(" looking for '%s' by %s (%s)" % (search['title'], search['author'], search['uuid'])) else: self.log.info(" looking for '%s' by %s" % (search['title'], search['author'])) + ''' for source in self.iTunes.sources: if source.Kind == self.Sources.index('Library'): @@ -1577,10 +1651,11 @@ class ITUNES(DriverBase): if DEBUG: self.log.error(" no Books playlist found") + attempts = 9 while attempts: # Find book whose Album field = search['uuid'] - if 'uuid' in search: + if 'uuid' in search and search['uuid']: if DEBUG: self.log.info(" searching by uuid '%s' ..." % search['uuid']) hits = lib_books.Search(search['uuid'],self.SearchField.index('All')) @@ -1590,16 +1665,30 @@ class ITUNES(DriverBase): self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit + # Search by author if known + if search['author']: + if DEBUG: + self.log.info(" searching by author '%s' ..." % search['author']) + hits = lib_books.Search(search['author'],self.SearchField.index('Artists')) + if hits: + for hit in hits: + if hit.Name == search['title']: + if DEBUG: + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + return hit + + # Search by title if no author available if DEBUG: - self.log.info(" searching by author '%s' ..." % search['author']) - hits = lib_books.Search(search['author'],self.SearchField.index('Artists')) + self.log.info(" searching by title '%s' ..." % search['title']) + hits = lib_books.Search(search['title'],self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s'" % (hit.Name)) return hit + # PDF just sent, title not updated yet, look for export pattern # PDF metadata was rewritten at export as 'safe(title) - safe(author)' if search['format'] == 'pdf': title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) @@ -1633,96 +1722,116 @@ class ITUNES(DriverBase): as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation ''' - archive_path = os.path.join(self.cache_dir, "thumbs.zip") - thumb_path = book_path.rpartition('.')[0] + '.jpg' - format = book_path.rpartition('.')[2].lower() - - try: - zfr = ZipFile(archive_path) - thumb_data = zfr.read(thumb_path) - zfr.close() - except: - zfw = ZipFile(archive_path, mode='a') - else: + # self.settings().use_subdirs is a repurposed DeviceConfig field + # We're using it to skip fetching/caching covers to speed things up + if not self.settings().use_subdirs: + thumb_data = None return thumb_data - self.log.info(" ITUNES._generate_thumbnail()") + thumb_path = book_path.rpartition('.')[0] + '.jpg' + format = book_path.rpartition('.')[2].lower() if isosx: - if format == 'epub': - try: - if False: - self.log.info(" fetching artwork from %s\n %s" % (book_path,book)) - # Resize the cover - data = book.artworks[1].raw_data().data - #self._dump_hex(data[:256]) - 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) - img_data.close() + title = book.name() + elif iswindows: + title = book.Name - thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') - thumb_data = thumb.getvalue() - thumb.close() + try: + zfr = ZipFile(self.archive_path) + thumb_data = zfr.read(thumb_path) + if thumb_data == 'None': + if False: + self.log.info(" ITUNES._generate_thumbnail()\n returning None from cover cache for '%s'" % title) + zfr.close() + return None + except: + zfw = ZipFile(self.archive_path, mode='a') + else: + if False: + self.log.info(" returning thumb from cache for '%s'" % title) + return thumb_data - # Cache the tagged thumb - if DEBUG: - self.log.info(" generated thumb for '%s', caching" % book.name()) - zfw.writestr(thumb_path, thumb_data) - zfw.close() - return thumb_data - except: - self.log.error(" error generating thumb for '%s'" % book.name()) - try: - zfw.close() - except: - pass - return None - else: + if DEBUG: + self.log.info(" ITUNES._generate_thumbnail():") + if isosx: + + # Fetch the artwork from iTunes + try: + data = book.artworks[1].raw_data().data + except: + # If no artwork, write an empty marker to cache if DEBUG: - self.log.info(" unable to generate PDF thumbs") + self.log.error(" error fetching iTunes artwork for '%s'" % title) + zfw.writestr(thumb_path, 'None') + zfw.close() return None - elif iswindows: + # Generate a thumb + 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) + thumb = cStringIO.StringIO() + im.convert('RGB').save(thumb,'JPEG') + thumb_data = thumb.getvalue() + thumb.close() + if False: + self.log.info(" generated thumb for '%s', caching" % title) + # Cache the tagged thumb + zfw.writestr(thumb_path, thumb_data) + except: + if DEBUG: + self.log.error(" error generating thumb for '%s', caching empty marker" % book.name()) + self._dump_hex(data[:32]) + thumb_data = None + # Cache the empty cover + zfw.writestr(thumb_path, 'None') + finally: + img_data.close() + zfw.close() + + return thumb_data + + + elif iswindows: if not book.Artwork.Count: if DEBUG: self.log.info(" no artwork available for '%s'" % book.Name) + zfw.writestr(thumb_path, 'None') + zfw.close() return None - if format == 'epub': - # Save the cover from iTunes - try: - tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format]) - 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) - thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') - thumb_data = thumb.getvalue() - os.remove(tmp_thumb) - thumb.close() + # Fetch the artwork from iTunes - # Cache the tagged thumb - if DEBUG: - self.log.info(" generated thumb for '%s', caching" % book.Name) - zfw.writestr(thumb_path, thumb_data) - zfw.close() - return thumb_data - except: - self.log.error(" error generating thumb for '%s'" % book.Name) - try: - zfw.close() - except: - pass - return None - else: + try: + tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format]) + 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) + thumb = cStringIO.StringIO() + im.convert('RGB').save(thumb,'JPEG') + thumb_data = thumb.getvalue() + os.remove(tmp_thumb) + thumb.close() + if False: + self.log.info(" generated thumb for '%s', caching" % book.Name) + # Cache the tagged thumb + zfw.writestr(thumb_path, thumb_data) + except: if DEBUG: - self.log.info(" unable to generate PDF thumbs") - return None + self.log.error(" error generating thumb for '%s', caching empty marker" % book.Name) + self._dump_hex(data[:32]) + thumb_data = None + # Cache the empty cover + zfw.writestr(thumb_path,'None') + + finally: + zfw.close() + + return thumb_data def _get_device_book_size(self, file, compressed_size): ''' @@ -1766,7 +1875,7 @@ class ITUNES(DriverBase): for book in books: # This may need additional entries for international iTunes users - if book.kind() in ['MPEG audio file']: + if book.kind() in self.Audiobooks: if DEBUG: self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) else: @@ -1798,7 +1907,7 @@ class ITUNES(DriverBase): for book in dev_books: # This may need additional entries for international iTunes users - if book.KindAsString in ['MPEG audio file']: + if book.KindAsString in self.Audiobooks: if DEBUG: self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) else: @@ -1899,12 +2008,13 @@ class ITUNES(DriverBase): lib_books = pl.file_tracks() for book in lib_books: # This may need additional entries for international iTunes users - if book.kind() in ['MPEG audio file']: + if book.kind() in self.Audiobooks: if DEBUG: self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) else: # Collect calibre orphans - remnants of recipe uploads - path = self.path_template % (book.name(), book.artist()) + format = 'pdf' if book.kind().startswith('PDF') else 'epub' + 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: @@ -1917,7 +2027,8 @@ class ITUNES(DriverBase): library_books[path] = book if DEBUG: - self.log.info(" %-30.30s %-30.30s %-40.40s [%s]" % (book.name(), book.artist(), book.album(), book.kind())) + self.log.info(" %-30.30s %-30.30s %-40.40s [%s]" % + (book.name(), book.artist(), book.album(), book.kind())) else: if DEBUG: self.log.info(' no Library playlists') @@ -1955,11 +2066,12 @@ class ITUNES(DriverBase): try: for book in lib_books: # This may need additional entries for international iTunes users - if book.KindAsString in ['MPEG audio file']: + if book.KindAsString in self.Audiobooks: if DEBUG: self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) else: - path = self.path_template % (book.Name, book.Artist) + format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' + path = self.path_template % (book.Name, book.Artist,format) # Collect calibre orphans if book.Description.startswith(self.description_prefix): @@ -2174,7 +2286,9 @@ 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: + if self.cached_books[book]['uuid'] == metadata.uuid and \ + self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.authors[0]: self.update_list.append(self.cached_books[book]) self._remove_from_device(self.cached_books[book]) if DEBUG: @@ -2191,17 +2305,17 @@ 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'] == metadata.authors[0]): + if self.cached_books[book]['uuid'] == metadata.uuid and \ + self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.authors[0]: self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: self.log.info( " deleting library book '%s'" % metadata.title) break - else: - if DEBUG: - self.log.info(" '%s' not found in cached_books" % metadata.title) + else: + if DEBUG: + self.log.info(" '%s' not found in cached_books" % metadata.title) def _remove_from_device(self, cached_book): ''' @@ -2209,18 +2323,20 @@ class ITUNES(DriverBase): ''' self.log.info(" ITUNES._remove_from_device()") if isosx: - if False: - self.log.info(" deleting %s" % cached_book['dev_book']) + if DEBUG: + self.log.info(" deleting '%s' from iDevice" % cached_book['title']) cached_book['dev_book'].delete() elif iswindows: - dev_pl = self._get_device_books_playlist() - hits = dev_pl.Search(cached_book['uuid'],self.SearchField.index('All')) - if hits: - hit = hits[0] - if False: - self.log.info(" deleting '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album)) + hit = self._find_device_book(cached_book) + if hit: + if DEBUG: + self.log.info(" deleting '%s' from iDevice" % cached_book['title']) hit.Delete() + else: + if DEBUG: + self.log.warning(" unable to remove '%s' by '%s' (%s) from device" % + (cached_book['title'],cached_book['author'],cached_book['uuid'])) def _remove_from_iTunes(self, cached_book): ''' @@ -2263,7 +2379,7 @@ class ITUNES(DriverBase): except: # We get here if there was an error with .location().path if DEBUG: - self.log.info(" '%s' not found in iTunes" % cached_book['title']) + self.log.info(" '%s' not in iTunes storage" % cached_book['title']) try: self.iTunes.delete(cached_book['lib_book']) @@ -2281,7 +2397,8 @@ class ITUNES(DriverBase): path = book.Location except: book = self._find_library_book(cached_book) - path = book.Location + if book: + path = book.Location if book: if self.iTunes_media and path.startswith(self.iTunes_media): @@ -2292,7 +2409,7 @@ class ITUNES(DriverBase): try: os.remove(path) except: - self.log.warning(" could not find '%s' in iTunes storage" % path) + self.log.warning(" '%s' not in iTunes storage" % path) try: os.rmdir(storage_path[0]) self.log.info(" removed folder '%s'" % storage_path[0]) @@ -2431,7 +2548,8 @@ class ITUNES(DriverBase): if isosx: if lb_added: lb_added.album.set(metadata.title) - lb_added.artist.set(metadata.authors[0]) + #lb_added.artist.set(metadata.authors[0]) + lb_added.artist.set(' & '.join(metadata.authors)) lb_added.composer.set(metadata.uuid) lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.enabled.set(True) @@ -2442,7 +2560,8 @@ class ITUNES(DriverBase): if db_added: db_added.album.set(metadata.title) - db_added.artist.set(metadata.authors[0]) + #db_added.artist.set(metadata.authors[0]) + db_added.artist.set(' & '.join(metadata.authors)) db_added.composer.set(metadata.uuid) db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.enabled.set(True) @@ -2499,7 +2618,8 @@ class ITUNES(DriverBase): elif iswindows: if lb_added: lb_added.Album = metadata.title - lb_added.Artist = metadata.authors[0] + #lb_added.Artist = metadata.authors[0] + lb_added.Artist = ' & '.join(metadata.authors) lb_added.Composer = metadata.uuid lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Enabled = True @@ -2510,7 +2630,8 @@ class ITUNES(DriverBase): if db_added: db_added.Album = metadata.title - db_added.Artist = metadata.authors[0] + #db_added.Artist = metadata.authors[0] + db_added.Artist = ' & '.join(metadata.authors) db_added.Composer = metadata.uuid db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.Enabled = True @@ -2627,7 +2748,11 @@ class ITUNES_ASYNC(ITUNES): """ if not oncard: if DEBUG: - self.log.info("ITUNES_ASYNC:books(oncard=%s)" % oncard) + self.log.info("ITUNES_ASYNC:books()") + if self.settings().use_subdirs: + self.log.info(" Cover fetching/caching enabled") + else: + self.log.info(" Cover fetching/caching disabled") # Fetch a list of books from iTunes @@ -2638,13 +2763,15 @@ class ITUNES_ASYNC(ITUNES): library_books = self._get_library_books() book_count = float(len(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 = self.path_template % (library_books[book].name(), - library_books[book].artist()) + library_books[book].artist(), + format) try: this_book.datetime = parse_date(str(library_books[book].date_added())).timetuple() except: - pass + this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] #this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None @@ -2664,7 +2791,7 @@ class ITUNES_ASYNC(ITUNES): 'lib_book':library_books[book], 'dev_book':None, 'uuid': library_books[book].composer(), - #'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' + 'format': format } if self.report_progress is not None: @@ -2678,12 +2805,14 @@ class ITUNES_ASYNC(ITUNES): book_count = float(len(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, - library_books[book].Artist) + library_books[book].Artist, + format) try: this_book.datetime = parse_date(str(library_books[book].DateAdded)).timetuple() except: - pass + this_book.datetime = time.gmtime() this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[book] @@ -2700,7 +2829,7 @@ class ITUNES_ASYNC(ITUNES): 'author':library_books[book].Artist, 'lib_book':library_books[book], 'uuid': library_books[book].Composer, - 'format': 'pdf' if library_books[book].KindAsString.startswith('PDF') else 'epub' + 'format': format } if self.report_progress is not None: @@ -2721,13 +2850,6 @@ class ITUNES_ASYNC(ITUNES): else: return BookList(self.log) - def unmount_device(self): - ''' - ''' - if DEBUG: - self.log.info("ITUNES_ASYNC:unmount_device()") - self.connected = False - def eject(self): ''' Un-mount / eject the device from the OS. This does not check if there @@ -2793,6 +2915,13 @@ class ITUNES_ASYNC(ITUNES): self.problem_msg = None self.update_list = [] + def unmount_device(self): + ''' + ''' + if DEBUG: + self.log.info("ITUNES_ASYNC:unmount_device()") + self.connected = False + class BookList(list): ''' A list of books. Each Book object must have the fields: diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index df414e2e52..4f26a35a02 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -269,11 +269,11 @@ class TouchscreenFeedTemplate(Template): link = A(CLASS('feed_link'), trim_title(feeds[f-1].title), href = '../feed_%d/index.html' % int(f-1)) - navbar_tr.append(TD(link, width="40%", align="center")) + navbar_tr.append(TD(CLASS('feed_prev'),link)) # Up to Sections - link = A(STRONG('Sections'), href="../index.html") - navbar_tr.append(TD(link,width="20%",align="center")) + link = A('Sections', href="../index.html") + navbar_tr.append(TD(CLASS('feed_up'),link)) # Next Section link = '' @@ -281,7 +281,7 @@ class TouchscreenFeedTemplate(Template): link = A(CLASS('feed_link'), trim_title(feeds[f+1].title), href = '../feed_%d/index.html' % int(f+1)) - navbar_tr.append(TD(link, width="40%", align="center", )) + navbar_tr.append(TD(CLASS('feed_next'),link)) navbar_t.append(navbar_tr) top_navbar = navbar_t bottom_navbar = copy.copy(navbar_t) @@ -319,10 +319,9 @@ class TouchscreenFeedTemplate(Template): continue tr = TR() - div_td = DIV( + div_td = DIV(CLASS('article_summary'), A(article.title, CLASS('summary_headline','calibre_rescale_120', - href=article.url)), - style="display:inline-block") + href=article.url))) if article.author: div_td.append(DIV(article.author, CLASS('summary_byline', 'calibre_rescale_100'))) @@ -354,27 +353,25 @@ class TouchscreenNavBarTemplate(Template): # | Previous if art > 0: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar_tr.append(TD(A(EM('Previous'),href=href), - width="32%")) + link = A(CLASS('article_link'),'Previous',href='%s../article_%d/index.html'%(prefix, art-1)) + navbar_tr.append(TD(CLASS('article_prev'),link)) else: - navbar_tr.append(TD('', width="32%")) + navbar_tr.append(TD(CLASS('article_prev'),'')) # | Articles | Sections | - href = '%s../index.html#article_%d'%(prefix, art) - navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%")) + link = A(CLASS('articles_link'),'Articles', href='%s../index.html#article_%d'%(prefix, art)) + navbar_tr.append(TD(CLASS('article_articles_list'),link)) - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%")) + link = A(CLASS('sections_link'),'Sections', href='%s../../index.html#feed_%d'%(prefix, feed)) + navbar_tr.append(TD(CLASS('article_sections_list'),link)) # | Next next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ else 'article_%d'%(art+1) up = '../..' if art == number_of_articles_in_feed - 1 else '..' - href = '%s%s/%s/index.html'%(prefix, up, next) - navbar_tr.append(TD(A(EM('Next'),href=href), - width="32%")) + link = A(CLASS('article_link'),'Next', href='%s%s/%s/index.html'%(prefix, up, next)) + navbar_tr.append(TD(CLASS('article_next'),link)) navbar_t.append(navbar_tr) navbar.append(navbar_t) #print "\n%s\n" % etree.tostring(navbar, pretty_print=True)