mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
GwR wip
This commit is contained in:
parent
5d91f7515a
commit
bd04312a76
@ -72,4 +72,9 @@ gui_pubdate_display_format = 'MMM yyyy'
|
|||||||
# without changing anything is sufficient to change the sort.
|
# without changing anything is sufficient to change the sort.
|
||||||
title_series_sorting = 'library_order'
|
title_series_sorting = 'library_order'
|
||||||
|
|
||||||
|
# Apple iDevice
|
||||||
|
# Control whether Series name is used as Category/Genre in iTunes/iBooks
|
||||||
|
# If set to 'True', a Book's Series name will be used as the Category/Genre
|
||||||
|
# If set to 'False', the book's first tag beginning with an alpha character will
|
||||||
|
# be used as the Category/Genre
|
||||||
|
iDevice_use_series_as_category = True
|
||||||
|
@ -237,9 +237,6 @@ class OutputProfile(Plugin):
|
|||||||
# If True the MOBI renderer on the device supports MOBI indexing
|
# If True the MOBI renderer on the device supports MOBI indexing
|
||||||
supports_mobi_indexing = False
|
supports_mobi_indexing = False
|
||||||
|
|
||||||
# Device supports displaying a nested TOC
|
|
||||||
supports_nested_toc = True
|
|
||||||
|
|
||||||
# If True output should be optimized for a touchscreen interface
|
# If True output should be optimized for a touchscreen interface
|
||||||
touchscreen = False
|
touchscreen = False
|
||||||
|
|
||||||
@ -256,8 +253,82 @@ class iPadOutput(OutputProfile):
|
|||||||
screen_size = (768, 1024)
|
screen_size = (768, 1024)
|
||||||
comic_screen_size = (768, 1024)
|
comic_screen_size = (768, 1024)
|
||||||
dpi = 132.0
|
dpi = 132.0
|
||||||
supports_nested_toc = False
|
timefmt = '%A, %d %b %Y'
|
||||||
|
cssutils_addProfile = { 'name':'webkit',
|
||||||
|
'props': {
|
||||||
|
'-webkit-border-bottom-left-radius':'{length}',
|
||||||
|
'-webkit-border-bottom-right-radius':'{length}',
|
||||||
|
'-webkit-border-top-left-radius':'{length}',
|
||||||
|
'-webkit-border-top-right-radius':'{length}',
|
||||||
|
'-webkit-border-radius': r'{border-width}(\s+{border-width}){0,3}|inherit',
|
||||||
|
},
|
||||||
|
'macros': {'border-width': '{length}|medium|thick|thin'}}
|
||||||
touchscreen = True
|
touchscreen = True
|
||||||
|
touchscreen_css = u'''
|
||||||
|
/* hr used in articles */
|
||||||
|
.caption_divider {
|
||||||
|
border:#ccc 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.touchscreen_navbar {
|
||||||
|
background:#ccc;
|
||||||
|
border:#ccc 1px solid;
|
||||||
|
border-collapse:separate;
|
||||||
|
border-spacing:1px;
|
||||||
|
margin-left: 5%;
|
||||||
|
margin-right: 5%;
|
||||||
|
width: 90%;
|
||||||
|
-webkit-border-radius:4px;
|
||||||
|
}
|
||||||
|
.touchscreen_navbar td {
|
||||||
|
background:#fff;
|
||||||
|
font-family:Helvetica;
|
||||||
|
font-size:80%;
|
||||||
|
padding: 5px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Feed summary formatting */
|
||||||
|
.feed_title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 160%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary_headline {
|
||||||
|
font-weight:bold;
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary_byline {
|
||||||
|
text-align:left;
|
||||||
|
font-family:monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary_text {
|
||||||
|
text-align:left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feed {
|
||||||
|
font-family:sans-serif;
|
||||||
|
font-weight:bold;
|
||||||
|
font-size:larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
class SonyReaderOutput(OutputProfile):
|
class SonyReaderOutput(OutputProfile):
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.metadata.epub import set_metadata
|
from calibre.ebooks.metadata.epub import set_metadata
|
||||||
from calibre.library.server.utils import strftime
|
from calibre.library.server.utils import strftime
|
||||||
from calibre.utils.config import Config, config_dir
|
from calibre.utils.config import Config, config_dir, tweaks
|
||||||
from calibre.utils.date import isoformat, now, parse_date
|
from calibre.utils.date import isoformat, now, parse_date
|
||||||
from calibre.utils.logging import Log
|
from calibre.utils.logging import Log
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
@ -78,12 +78,12 @@ class ITUNES(DevicePlugin):
|
|||||||
supported_platforms = ['osx','windows']
|
supported_platforms = ['osx','windows']
|
||||||
author = 'GRiker'
|
author = 'GRiker'
|
||||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||||
version = (0,7,0)
|
version = (0,8,0)
|
||||||
|
|
||||||
OPEN_FEEDBACK_MESSAGE = _(
|
OPEN_FEEDBACK_MESSAGE = _(
|
||||||
'Apple device detected, launching iTunes, please wait ...')
|
'Apple device detected, launching iTunes, please wait ...')
|
||||||
|
|
||||||
FORMATS = ['epub']
|
FORMATS = ['epub','pdf']
|
||||||
|
|
||||||
# Product IDs:
|
# Product IDs:
|
||||||
# 0x1292:iPhone 3G
|
# 0x1292:iPhone 3G
|
||||||
@ -141,6 +141,10 @@ class ITUNES(DevicePlugin):
|
|||||||
'SongNames',
|
'SongNames',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Cover art size limits
|
||||||
|
MAX_COVER_WIDTH = 510
|
||||||
|
MAX_COVER_HEIGHT = 680
|
||||||
|
|
||||||
# Properties
|
# Properties
|
||||||
cached_books = {}
|
cached_books = {}
|
||||||
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
||||||
@ -159,7 +163,7 @@ class ITUNES(DevicePlugin):
|
|||||||
sources = None
|
sources = None
|
||||||
update_msg = None
|
update_msg = None
|
||||||
update_needed = False
|
update_needed = False
|
||||||
use_series_data = True
|
use_series_as_category = tweaks['iDevice_use_series_as_category']
|
||||||
|
|
||||||
# Public methods
|
# Public methods
|
||||||
def add_books_to_metadata(self, locations, metadata, booklists):
|
def add_books_to_metadata(self, locations, metadata, booklists):
|
||||||
@ -173,16 +177,17 @@ class ITUNES(DevicePlugin):
|
|||||||
(L{books}(oncard=None), L{books}(oncard='carda'),
|
(L{books}(oncard=None), L{books}(oncard='carda'),
|
||||||
L{books}(oncard='cardb')).
|
L{books}(oncard='cardb')).
|
||||||
'''
|
'''
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info("ITUNES.add_books_to_metadata()")
|
||||||
|
|
||||||
task_count = float(len(self.update_list))
|
task_count = float(len(self.update_list))
|
||||||
|
|
||||||
# Delete any obsolete copies of the book from the booklist
|
# Delete any obsolete copies of the book from the booklist
|
||||||
if self.update_list:
|
if self.update_list:
|
||||||
if True:
|
if False:
|
||||||
self.log.info("ITUNES.add_books_to_metadata()")
|
self._dump_booklist(booklists[0], header='before',indent=2)
|
||||||
#self._dump_booklist(booklists[0], header='before',indent=2)
|
self._dump_update_list(header='before',indent=2)
|
||||||
#self._dump_update_list(header='before',indent=2)
|
self._dump_cached_books(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 False:
|
||||||
@ -230,12 +235,12 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
# Add new books to booklists[0]
|
# Add new books to booklists[0]
|
||||||
for new_book in locations[0]:
|
for new_book in locations[0]:
|
||||||
if False:
|
if DEBUG:
|
||||||
self.log.info(" adding '%s' by '%s' to booklists[0]" %
|
self.log.info(" adding '%s' by '%s' to booklists[0]" %
|
||||||
(new_book.title, new_book.author))
|
(new_book.title, new_book.author))
|
||||||
booklists[0].append(new_book)
|
booklists[0].append(new_book)
|
||||||
|
|
||||||
if False:
|
if DEBUG:
|
||||||
self._dump_booklist(booklists[0],header='after',indent=2)
|
self._dump_booklist(booklists[0],header='after',indent=2)
|
||||||
self._dump_cached_books(header='after',indent=2)
|
self._dump_cached_books(header='after',indent=2)
|
||||||
|
|
||||||
@ -329,7 +334,8 @@ class ITUNES(DevicePlugin):
|
|||||||
'title':book.Name,
|
'title':book.Name,
|
||||||
'author':book.Artist,
|
'author':book.Artist,
|
||||||
'lib_book':library_books[this_book.path] if this_book.path in library_books else None,
|
'lib_book':library_books[this_book.path] if this_book.path in library_books else None,
|
||||||
'uuid': book.Composer
|
'uuid': book.Composer,
|
||||||
|
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.report_progress is not None:
|
if self.report_progress is not None:
|
||||||
@ -343,9 +349,9 @@ class ITUNES(DevicePlugin):
|
|||||||
if self.report_progress is not None:
|
if self.report_progress is not None:
|
||||||
self.report_progress(1.0, _('finished'))
|
self.report_progress(1.0, _('finished'))
|
||||||
self.cached_books = cached_books
|
self.cached_books = cached_books
|
||||||
# if DEBUG:
|
if DEBUG:
|
||||||
# self._dump_booklist(booklist, 'returning from books():')
|
self._dump_booklist(booklist, 'returning from books()', indent=2)
|
||||||
# self._dump_cached_books('returning from books():')
|
self._dump_cached_books('returning from books()',indent=2)
|
||||||
return booklist
|
return booklist
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@ -685,6 +691,9 @@ class ITUNES(DevicePlugin):
|
|||||||
@param booklists: A tuple containing the result of calls to
|
@param booklists: A tuple containing the result of calls to
|
||||||
(L{books}(oncard=None), L{books}(oncard='carda'),
|
(L{books}(oncard=None), L{books}(oncard='carda'),
|
||||||
L{books}(oncard='cardb')).
|
L{books}(oncard='cardb')).
|
||||||
|
|
||||||
|
NB: This will not find books that were added by a different installation of calibre
|
||||||
|
as uuids are different
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info("ITUNES.remove_books_from_metadata()")
|
self.log.info("ITUNES.remove_books_from_metadata()")
|
||||||
@ -750,6 +759,10 @@ class ITUNES(DevicePlugin):
|
|||||||
(L{books}(oncard=None), L{books}(oncard='carda'),
|
(L{books}(oncard=None), L{books}(oncard='carda'),
|
||||||
L{books}(oncard='cardb')).
|
L{books}(oncard='cardb')).
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info("ITUNES.sync_booklists()")
|
||||||
|
|
||||||
if self.update_needed:
|
if self.update_needed:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(' calling _update_device')
|
self.log.info(' calling _update_device')
|
||||||
@ -812,29 +825,32 @@ class ITUNES(DevicePlugin):
|
|||||||
self.problem_msg = _("Some cover art could not be converted.\n"
|
self.problem_msg = _("Some cover art could not be converted.\n"
|
||||||
"Click 'Show Details' for a list.")
|
"Click 'Show Details' for a list.")
|
||||||
|
|
||||||
if False:
|
if DEBUG:
|
||||||
self.log.info("ITUNES.upload_books()")
|
self.log.info("ITUNES.upload_books()")
|
||||||
self._dump_files(files, header='upload_books()',indent=2)
|
self._dump_files(files, header='upload_books()',indent=2)
|
||||||
self._dump_update_list(header='upload_books()',indent=2)
|
self._dump_update_list(header='upload_books()',indent=2)
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
for (i,file) in enumerate(files):
|
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])
|
||||||
self._remove_existing_copy(path, metadata[i])
|
self._remove_existing_copy(path, metadata[i])
|
||||||
fpath = self._get_fpath(file, metadata[i], update_md=True)
|
fpath = self._get_fpath(file, metadata[i], format, update_md=True)
|
||||||
db_added, lb_added = self._add_new_copy(fpath, metadata[i])
|
db_added, lb_added = self._add_new_copy(fpath, metadata[i])
|
||||||
thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added)
|
thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format)
|
||||||
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb)
|
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format)
|
||||||
new_booklist.append(this_book)
|
new_booklist.append(this_book)
|
||||||
self._update_iTunes_metadata(metadata[i], db_added, lb_added, 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_paths
|
||||||
self.cached_books[this_book.path] = {
|
self.cached_books[this_book.path] = {
|
||||||
'title': metadata[i].title,
|
|
||||||
'author': metadata[i].author,
|
'author': metadata[i].author,
|
||||||
'lib_book': lb_added,
|
|
||||||
'dev_book': db_added,
|
'dev_book': db_added,
|
||||||
'uuid': metadata[i].uuid}
|
'format': format,
|
||||||
|
'lib_book': lb_added,
|
||||||
|
'title': metadata[i].title,
|
||||||
|
'uuid': metadata[i].uuid }
|
||||||
|
|
||||||
|
|
||||||
# Report progress
|
# Report progress
|
||||||
if self.report_progress is not None:
|
if self.report_progress is not None:
|
||||||
@ -846,9 +862,10 @@ class ITUNES(DevicePlugin):
|
|||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
|
|
||||||
for (i,file) in enumerate(files):
|
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])
|
||||||
self._remove_existing_copy(path, metadata[i])
|
self._remove_existing_copy(path, metadata[i])
|
||||||
fpath = self._get_fpath(file, metadata[i], update_md=True)
|
fpath = self._get_fpath(file, metadata[i],format, update_md=True)
|
||||||
db_added, lb_added = self._add_new_copy(fpath, metadata[i])
|
db_added, lb_added = self._add_new_copy(fpath, metadata[i])
|
||||||
|
|
||||||
if self.manual_sync_mode and not db_added:
|
if self.manual_sync_mode and not db_added:
|
||||||
@ -857,17 +874,18 @@ class ITUNES(DevicePlugin):
|
|||||||
"Click 'Show Details...' for affected books.")
|
"Click 'Show Details...' for affected books.")
|
||||||
self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0]))
|
self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0]))
|
||||||
|
|
||||||
thumb = self._cover_to_thumb(path, metadata[i], lb_added, db_added)
|
thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format)
|
||||||
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb)
|
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format)
|
||||||
new_booklist.append(this_book)
|
new_booklist.append(this_book)
|
||||||
self._update_iTunes_metadata(metadata[i], db_added, lb_added, 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_paths
|
||||||
self.cached_books[this_book.path] = {
|
self.cached_books[this_book.path] = {
|
||||||
'title': metadata[i].title,
|
|
||||||
'author': metadata[i].author[0],
|
'author': metadata[i].author[0],
|
||||||
'lib_book': lb_added,
|
|
||||||
'dev_book': db_added,
|
'dev_book': db_added,
|
||||||
|
'format': format,
|
||||||
|
'lib_book': lb_added,
|
||||||
|
'title': metadata[i].title,
|
||||||
'uuid': metadata[i].uuid}
|
'uuid': metadata[i].uuid}
|
||||||
|
|
||||||
# Report progress
|
# Report progress
|
||||||
@ -968,7 +986,8 @@ class ITUNES(DevicePlugin):
|
|||||||
db_added = self._find_device_book(
|
db_added = self._find_device_book(
|
||||||
{'title': metadata.title,
|
{'title': metadata.title,
|
||||||
'author': metadata.authors[0],
|
'author': metadata.authors[0],
|
||||||
'uuid': metadata.uuid})
|
'uuid': metadata.uuid,
|
||||||
|
'format': fpath.rpartition('.')[2].lower()})
|
||||||
|
|
||||||
return db_added
|
return db_added
|
||||||
|
|
||||||
@ -1021,7 +1040,8 @@ class ITUNES(DevicePlugin):
|
|||||||
added = self._find_library_book(
|
added = self._find_library_book(
|
||||||
{ 'title': metadata.title,
|
{ 'title': metadata.title,
|
||||||
'author': metadata.author[0],
|
'author': metadata.author[0],
|
||||||
'uuid': metadata.uuid})
|
'uuid': metadata.uuid,
|
||||||
|
'format': file.rpartition('.')[2].lower()})
|
||||||
return added
|
return added
|
||||||
|
|
||||||
def _add_new_copy(self, fpath, metadata):
|
def _add_new_copy(self, fpath, metadata):
|
||||||
@ -1047,23 +1067,50 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
return db_added, lb_added
|
return db_added, lb_added
|
||||||
|
|
||||||
def _cover_to_thumb(self, path, metadata, db_added, lb_added):
|
def _cover_to_thumb(self, path, metadata, db_added, lb_added, format):
|
||||||
'''
|
'''
|
||||||
assumes pythoncom wrapper for db_added
|
assumes pythoncom wrapper for db_added
|
||||||
|
as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation
|
||||||
'''
|
'''
|
||||||
self.log.info(" ITUNES._cover_to_thumb()")
|
self.log.info(" ITUNES._cover_to_thumb()")
|
||||||
|
|
||||||
thumb = None
|
thumb = None
|
||||||
if metadata.cover:
|
if metadata.cover:
|
||||||
|
|
||||||
|
if (format == 'epub'):
|
||||||
|
# Pre-shrink cover
|
||||||
|
# self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT
|
||||||
|
try:
|
||||||
|
img = PILImage.open(metadata.cover)
|
||||||
|
width = img.size[0]
|
||||||
|
height = img.size[1]
|
||||||
|
scaled, nwidth, nheight = fit_image(width, height, self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT)
|
||||||
|
if scaled:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" '%s' scaled from %sx%s to %sx%s" %
|
||||||
|
(metadata.cover,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:
|
||||||
|
cover_data = cd.read()
|
||||||
|
except:
|
||||||
|
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
|
||||||
|
self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title))
|
||||||
|
return thumb
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
cover_data = open(metadata.cover,'rb')
|
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.artworks[1].data_.set(cover_data.read())
|
lb_added.artworks[1].data_.set(cover_data)
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
# The following command generates an error, but the artwork does in fact
|
# The following command generates an error, but the artwork does in fact
|
||||||
# get sent to the device. Seems like a bug in Apple's automation interface
|
# get sent to the device. Seems like a bug in Apple's automation interface
|
||||||
try:
|
try:
|
||||||
db_added.artworks[1].data_.set(cover_data.read())
|
db_added.artworks[1].data_.set(cover_data)
|
||||||
except:
|
except:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.warning(" iTunes automation interface reported an error"
|
self.log.warning(" iTunes automation interface reported an error"
|
||||||
@ -1076,17 +1123,26 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
|
|
||||||
elif iswindows:
|
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:
|
||||||
|
tmp_cover.write(cover_data)
|
||||||
|
|
||||||
if lb_added:
|
if lb_added:
|
||||||
if lb_added.Artwork.Count:
|
if lb_added.Artwork.Count:
|
||||||
lb_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover)
|
lb_added.Artwork.Item(1).SetArtworkFromFile(tc)
|
||||||
else:
|
else:
|
||||||
lb_added.AddArtworkFromFile(metadata.cover)
|
lb_added.AddArtworkFromFile(tc)
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
if db_added.Artwork.Count:
|
if db_added.Artwork.Count:
|
||||||
db_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover)
|
db_added.Artwork.Item(1).SetArtworkFromFile(tc)
|
||||||
else:
|
else:
|
||||||
db_added.AddArtworkFromFile(metadata.cover)
|
db_added.AddArtworkFromFile(tc)
|
||||||
|
|
||||||
|
elif format == 'pdf':
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" unable to set PDF cover via automation interface")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Resize for thumb
|
# Resize for thumb
|
||||||
@ -1097,6 +1153,7 @@ class ITUNES(DevicePlugin):
|
|||||||
of = cStringIO.StringIO()
|
of = cStringIO.StringIO()
|
||||||
im.convert('RGB').save(of, 'JPEG')
|
im.convert('RGB').save(of, 'JPEG')
|
||||||
thumb = of.getvalue()
|
thumb = of.getvalue()
|
||||||
|
of.close()
|
||||||
|
|
||||||
# Refresh the thumbnail cache
|
# Refresh the thumbnail cache
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1105,14 +1162,15 @@ class ITUNES(DevicePlugin):
|
|||||||
zfw = ZipFile(archive_path, mode='a')
|
zfw = ZipFile(archive_path, mode='a')
|
||||||
thumb_path = path.rpartition('.')[0] + '.jpg'
|
thumb_path = path.rpartition('.')[0] + '.jpg'
|
||||||
zfw.writestr(thumb_path, thumb)
|
zfw.writestr(thumb_path, thumb)
|
||||||
zfw.close()
|
|
||||||
except:
|
except:
|
||||||
self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0]))
|
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()
|
||||||
|
|
||||||
return thumb
|
return thumb
|
||||||
|
|
||||||
def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb):
|
def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb, format):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1122,6 +1180,7 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
this_book.db_id = None
|
this_book.db_id = None
|
||||||
this_book.device_collections = []
|
this_book.device_collections = []
|
||||||
|
this_book.format = format
|
||||||
this_book.library_id = lb_added
|
this_book.library_id = lb_added
|
||||||
this_book.path = path
|
this_book.path = path
|
||||||
this_book.thumbnail = thumb
|
this_book.thumbnail = thumb
|
||||||
@ -1319,10 +1378,11 @@ class ITUNES(DevicePlugin):
|
|||||||
self.cached_books[cb]['uuid']))
|
self.cached_books[cb]['uuid']))
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
for cb in self.cached_books.keys():
|
for cb in self.cached_books.keys():
|
||||||
self.log.info("%s%-40.40s %-30.30s %s" %
|
self.log.info("%s%-40.40s %-30.30s %-4.4s %s" %
|
||||||
(' '*indent,
|
(' '*indent,
|
||||||
self.cached_books[cb]['title'],
|
self.cached_books[cb]['title'],
|
||||||
self.cached_books[cb]['author'],
|
self.cached_books[cb]['author'],
|
||||||
|
self.cached_books[cb]['format'],
|
||||||
self.cached_books[cb]['uuid']))
|
self.cached_books[cb]['uuid']))
|
||||||
|
|
||||||
self.log.info()
|
self.log.info()
|
||||||
@ -1338,8 +1398,9 @@ class ITUNES(DevicePlugin):
|
|||||||
fnames = zf.namelist()
|
fnames = zf.namelist()
|
||||||
opf = [x for x in fnames if '.opf' in x][0]
|
opf = [x for x in fnames if '.opf' in x][0]
|
||||||
if opf:
|
if opf:
|
||||||
opf_raw = cStringIO.StringIO(zf.read(opf)).getvalue()
|
opf_raw = cStringIO.StringIO(zf.read(opf))
|
||||||
soup = BeautifulSoup(opf_raw)
|
soup = BeautifulSoup(opf_raw.getvalue())
|
||||||
|
opf_raw.close()
|
||||||
title = soup.find('dc:title').renderContents()
|
title = soup.find('dc:title').renderContents()
|
||||||
author = soup.find('dc:creator').renderContents()
|
author = soup.find('dc:creator').renderContents()
|
||||||
ts = soup.find('meta',attrs={'name':'calibre:timestamp'})
|
ts = soup.find('meta',attrs={'name':'calibre:timestamp'})
|
||||||
@ -1440,6 +1501,22 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
||||||
return hit
|
return hit
|
||||||
|
|
||||||
|
# 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'])
|
||||||
|
author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author'])
|
||||||
|
if DEBUG:
|
||||||
|
self.log.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]
|
||||||
|
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
||||||
|
return hit
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" no PDF hits")
|
||||||
|
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1509,6 +1586,22 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
||||||
return hit
|
return hit
|
||||||
|
|
||||||
|
# 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'])
|
||||||
|
author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author'])
|
||||||
|
if DEBUG:
|
||||||
|
self.log.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]
|
||||||
|
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer))
|
||||||
|
return hit
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" no PDF hits")
|
||||||
|
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1523,10 +1616,12 @@ class ITUNES(DevicePlugin):
|
|||||||
Convert iTunes artwork to thumbnail
|
Convert iTunes artwork to thumbnail
|
||||||
Cache generated thumbnails
|
Cache generated thumbnails
|
||||||
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
cache_dir = os.path.join(config_dir, 'caches', 'itunes')
|
||||||
|
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")
|
archive_path = os.path.join(self.cache_dir, "thumbs.zip")
|
||||||
thumb_path = book_path.rpartition('.')[0] + '.jpg'
|
thumb_path = book_path.rpartition('.')[0] + '.jpg'
|
||||||
|
format = book_path.rpartition('.')[2].lower()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
zfr = ZipFile(archive_path)
|
zfr = ZipFile(archive_path)
|
||||||
@ -1539,22 +1634,30 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
self.log.info(" ITUNES._generate_thumbnail()")
|
self.log.info(" ITUNES._generate_thumbnail()")
|
||||||
if isosx:
|
if isosx:
|
||||||
|
if format == 'epub':
|
||||||
try:
|
try:
|
||||||
|
if False:
|
||||||
|
self.log.info(" fetching artwork from %s\n %s" % (book_path,book))
|
||||||
# Resize the cover
|
# Resize the cover
|
||||||
data = book.artworks[1].raw_data().data
|
data = book.artworks[1].raw_data().data
|
||||||
#self._dump_hex(data[:256])
|
#self._dump_hex(data[:256])
|
||||||
im = PILImage.open(cStringIO.StringIO(data))
|
img_data = cStringIO.StringIO(data)
|
||||||
|
im = PILImage.open(img_data)
|
||||||
scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80)
|
scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80)
|
||||||
im = im.resize((int(width),int(height)), PILImage.ANTIALIAS)
|
im = im.resize((int(width),int(height)), PILImage.ANTIALIAS)
|
||||||
|
img_data.close()
|
||||||
|
|
||||||
thumb = cStringIO.StringIO()
|
thumb = cStringIO.StringIO()
|
||||||
im.convert('RGB').save(thumb,'JPEG')
|
im.convert('RGB').save(thumb,'JPEG')
|
||||||
|
thumb_data = thumb.getvalue()
|
||||||
|
thumb.close()
|
||||||
|
|
||||||
# Cache the tagged thumb
|
# Cache the tagged thumb
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" generated thumb for '%s', caching" % book.name())
|
self.log.info(" generated thumb for '%s', caching" % book.name())
|
||||||
zfw.writestr(thumb_path, thumb.getvalue())
|
zfw.writestr(thumb_path, thumb_data)
|
||||||
zfw.close()
|
zfw.close()
|
||||||
return thumb.getvalue()
|
return thumb_data
|
||||||
except:
|
except:
|
||||||
self.log.error(" error generating thumb for '%s'" % book.name())
|
self.log.error(" error generating thumb for '%s'" % book.name())
|
||||||
try:
|
try:
|
||||||
@ -1562,14 +1665,19 @@ class ITUNES(DevicePlugin):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" unable to generate PDF thumbs")
|
||||||
|
return None
|
||||||
|
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
|
|
||||||
if not book.Artwork.Count:
|
if not book.Artwork.Count:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" no artwork available")
|
self.log.info(" no artwork available for '%s'" % book.Name)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if format == 'epub':
|
||||||
# Save the cover from iTunes
|
# Save the cover from iTunes
|
||||||
try:
|
try:
|
||||||
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format])
|
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format])
|
||||||
@ -1580,14 +1688,16 @@ class ITUNES(DevicePlugin):
|
|||||||
im = im.resize((int(width),int(height)), PILImage.ANTIALIAS)
|
im = im.resize((int(width),int(height)), PILImage.ANTIALIAS)
|
||||||
thumb = cStringIO.StringIO()
|
thumb = cStringIO.StringIO()
|
||||||
im.convert('RGB').save(thumb,'JPEG')
|
im.convert('RGB').save(thumb,'JPEG')
|
||||||
|
thumb_data = thmb.getvalue()
|
||||||
os.remove(tmp_thumb)
|
os.remove(tmp_thumb)
|
||||||
|
thumb.close()
|
||||||
|
|
||||||
# Cache the tagged thumb
|
# Cache the tagged thumb
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" generated thumb for '%s', caching" % book.Name)
|
self.log.info(" generated thumb for '%s', caching" % book.Name)
|
||||||
zfw.writestr(thumb_path, thumb.getvalue())
|
zfw.writestr(thumb_path, thumb_data)
|
||||||
zfw.close()
|
zfw.close()
|
||||||
return thumb.getvalue()
|
return thumb_data
|
||||||
except:
|
except:
|
||||||
self.log.error(" error generating thumb for '%s'" % book.Name)
|
self.log.error(" error generating thumb for '%s'" % book.Name)
|
||||||
try:
|
try:
|
||||||
@ -1595,11 +1705,18 @@ class ITUNES(DevicePlugin):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" unable to generate PDF thumbs")
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_device_book_size(self, file, compressed_size):
|
def _get_device_book_size(self, file, compressed_size):
|
||||||
'''
|
'''
|
||||||
Calculate the exploded size of file
|
Calculate the exploded size of file
|
||||||
'''
|
'''
|
||||||
|
exploded_file_size = compressed_size
|
||||||
|
format = file.rpartition('.')[2].lower()
|
||||||
|
if format == 'epub':
|
||||||
myZip = ZipFile(file,'r')
|
myZip = ZipFile(file,'r')
|
||||||
myZipList = myZip.infolist()
|
myZipList = myZip.infolist()
|
||||||
exploded_file_size = 0
|
exploded_file_size = 0
|
||||||
@ -1701,7 +1818,7 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.error(" no iPad|Books playlist found")
|
self.log.error(" no iPad|Books playlist found")
|
||||||
return pl
|
return pl
|
||||||
|
|
||||||
def _get_fpath(self,file, metadata, update_md=False):
|
def _get_fpath(self,file, metadata, format, update_md=False):
|
||||||
'''
|
'''
|
||||||
If the database copy will be deleted after upload, we have to
|
If the database copy will be deleted after upload, we have to
|
||||||
use file (the PersistentTemporaryFile), which will be around until
|
use file (the PersistentTemporaryFile), which will be around until
|
||||||
@ -1725,7 +1842,7 @@ class ITUNES(DevicePlugin):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" file will be deleted after upload")
|
self.log.info(" file will be deleted after upload")
|
||||||
|
|
||||||
if update_md:
|
if format == 'epub' and update_md:
|
||||||
self._update_epub_metadata(fpath, metadata)
|
self._update_epub_metadata(fpath, metadata)
|
||||||
|
|
||||||
return fpath
|
return fpath
|
||||||
@ -1950,10 +2067,12 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
# Read the current storage path for iTunes media from the XML file
|
# Read the current storage path for iTunes media from the XML file
|
||||||
with open(self.iTunes.LibraryXMLPath, 'r') as xml:
|
with open(self.iTunes.LibraryXMLPath, 'r') as xml:
|
||||||
soup = BeautifulSoup(xml.read().decode('utf-8'))
|
for line in xml:
|
||||||
mf = soup.find('key',text="Music Folder").parent
|
if line.strip().startswith('<key>Music Folder'):
|
||||||
string = mf.findNext('string').renderContents()
|
soup = BeautifulSoup(line)
|
||||||
|
string = soup.find('string').renderContents()
|
||||||
media_dir = os.path.abspath(string[len('file://localhost/'):].replace('%20',' '))
|
media_dir = os.path.abspath(string[len('file://localhost/'):].replace('%20',' '))
|
||||||
|
break
|
||||||
if os.path.exists(media_dir):
|
if os.path.exists(media_dir):
|
||||||
self.iTunes_media = media_dir
|
self.iTunes_media = media_dir
|
||||||
else:
|
else:
|
||||||
@ -2028,7 +2147,9 @@ class ITUNES(DevicePlugin):
|
|||||||
# Delete existing from Library|Books, add to self.update_list
|
# Delete existing from Library|Books, add to self.update_list
|
||||||
# for deletion from booklist[0] during add_books_to_metadata
|
# for deletion from booklist[0] during add_books_to_metadata
|
||||||
for book in self.cached_books:
|
for book in self.cached_books:
|
||||||
if self.cached_books[book]['uuid'] == metadata.uuid:
|
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]):
|
||||||
self.update_list.append(self.cached_books[book])
|
self.update_list.append(self.cached_books[book])
|
||||||
self._remove_from_iTunes(self.cached_books[book])
|
self._remove_from_iTunes(self.cached_books[book])
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -2036,7 +2157,7 @@ class ITUNES(DevicePlugin):
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" '%s' not in cached_books" % metadata.title)
|
self.log.info(" '%s' not found in cached_books" % metadata.title)
|
||||||
|
|
||||||
def _remove_from_device(self, cached_book):
|
def _remove_from_device(self, cached_book):
|
||||||
'''
|
'''
|
||||||
@ -2158,12 +2279,14 @@ class ITUNES(DevicePlugin):
|
|||||||
fnames = zf_opf.namelist()
|
fnames = zf_opf.namelist()
|
||||||
opf = [x for x in fnames if '.opf' in x][0]
|
opf = [x for x in fnames if '.opf' in x][0]
|
||||||
if opf:
|
if opf:
|
||||||
opf_raw = cStringIO.StringIO(zf_opf.read(opf)).getvalue()
|
opf_raw = cStringIO.StringIO(zf_opf.read(opf))
|
||||||
soup = BeautifulSoup(opf_raw)
|
soup = BeautifulSoup(opf_raw.getvalue())
|
||||||
|
opf_raw.close()
|
||||||
|
|
||||||
|
# Touch existing calibre timestamp
|
||||||
md = soup.find('metadata')
|
md = soup.find('metadata')
|
||||||
ts = md.find('meta',attrs={'name':'calibre:timestamp'})
|
ts = md.find('meta',attrs={'name':'calibre:timestamp'})
|
||||||
if ts:
|
if ts:
|
||||||
# Touch existing calibre timestamp
|
|
||||||
timestamp = ts['content']
|
timestamp = ts['content']
|
||||||
old_ts = parse_date(timestamp)
|
old_ts = parse_date(timestamp)
|
||||||
metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
|
metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
|
||||||
@ -2172,6 +2295,15 @@ class ITUNES(DevicePlugin):
|
|||||||
metadata.timestamp = isoformat(now())
|
metadata.timestamp = isoformat(now())
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self.log.info(" add timestamp: %s" % metadata.timestamp)
|
self.log.info(" add timestamp: %s" % metadata.timestamp)
|
||||||
|
|
||||||
|
# Fix the language declaration for iBooks 1.1
|
||||||
|
patched_language = 'en-US'
|
||||||
|
language = md.find('dc:language')
|
||||||
|
if language:
|
||||||
|
self.log.info(" changing <dc:language> from '%s' to '%s'" %
|
||||||
|
(language.renderContents(),patched_language))
|
||||||
|
metadata.language = patched_language
|
||||||
|
|
||||||
zf_opf.close()
|
zf_opf.close()
|
||||||
|
|
||||||
# If 'News' in tags, tweak the title/author for friendlier display in iBooks
|
# If 'News' in tags, tweak the title/author for friendlier display in iBooks
|
||||||
@ -2257,6 +2389,9 @@ class ITUNES(DevicePlugin):
|
|||||||
lb_added.enabled.set(True)
|
lb_added.enabled.set(True)
|
||||||
lb_added.sort_artist.set(metadata.author_sort.title())
|
lb_added.sort_artist.set(metadata.author_sort.title())
|
||||||
lb_added.sort_name.set(this_book.title_sorter)
|
lb_added.sort_name.set(this_book.title_sorter)
|
||||||
|
if this_book.format == 'pdf':
|
||||||
|
lb_added.artist.set(metadata.authors[0])
|
||||||
|
lb_added.name.set(metadata.title)
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.album.set(metadata.title)
|
db_added.album.set(metadata.title)
|
||||||
@ -2265,6 +2400,9 @@ class ITUNES(DevicePlugin):
|
|||||||
db_added.enabled.set(True)
|
db_added.enabled.set(True)
|
||||||
db_added.sort_artist.set(metadata.author_sort.title())
|
db_added.sort_artist.set(metadata.author_sort.title())
|
||||||
db_added.sort_name.set(this_book.title_sorter)
|
db_added.sort_name.set(this_book.title_sorter)
|
||||||
|
if this_book.format == 'pdf':
|
||||||
|
db_added.artist.set(metadata.authors[0])
|
||||||
|
db_added.name.set(metadata.title)
|
||||||
|
|
||||||
if metadata.comments:
|
if metadata.comments:
|
||||||
if lb_added:
|
if lb_added:
|
||||||
@ -2284,7 +2422,9 @@ class ITUNES(DevicePlugin):
|
|||||||
|
|
||||||
# Set genre from series if available, else first alpha tag
|
# Set genre from series if available, else first alpha tag
|
||||||
# Otherwise iTunes grabs the first dc:subject from the opf metadata
|
# Otherwise iTunes grabs the first dc:subject from the opf metadata
|
||||||
if self.use_series_data and metadata.series:
|
if self.use_series_as_category and metadata.series:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" using Series name as Genre")
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.sort_name.set("%s %03d" % (metadata.series, metadata.series_index))
|
lb_added.sort_name.set("%s %03d" % (metadata.series, metadata.series_index))
|
||||||
lb_added.genre.set(metadata.series)
|
lb_added.genre.set(metadata.series)
|
||||||
@ -2298,6 +2438,8 @@ class ITUNES(DevicePlugin):
|
|||||||
db_added.episode_number.set(metadata.series_index)
|
db_added.episode_number.set(metadata.series_index)
|
||||||
|
|
||||||
elif metadata.tags:
|
elif metadata.tags:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" using Tag as Genre")
|
||||||
for tag in metadata.tags:
|
for tag in metadata.tags:
|
||||||
if self._is_alpha(tag[0]):
|
if self._is_alpha(tag[0]):
|
||||||
if lb_added:
|
if lb_added:
|
||||||
@ -2314,6 +2456,9 @@ class ITUNES(DevicePlugin):
|
|||||||
lb_added.Enabled = True
|
lb_added.Enabled = True
|
||||||
lb_added.SortArtist = (metadata.author_sort.title())
|
lb_added.SortArtist = (metadata.author_sort.title())
|
||||||
lb_added.SortName = (this_book.title_sorter)
|
lb_added.SortName = (this_book.title_sorter)
|
||||||
|
if this_book.format == 'pdf':
|
||||||
|
lb_added.Artist = metadata.authors[0]
|
||||||
|
lb_added.Name = metadata.title
|
||||||
|
|
||||||
if db_added:
|
if db_added:
|
||||||
db_added.Album = metadata.title
|
db_added.Album = metadata.title
|
||||||
@ -2322,6 +2467,9 @@ class ITUNES(DevicePlugin):
|
|||||||
db_added.Enabled = True
|
db_added.Enabled = True
|
||||||
db_added.SortArtist = (metadata.author_sort.title())
|
db_added.SortArtist = (metadata.author_sort.title())
|
||||||
db_added.SortName = (this_book.title_sorter)
|
db_added.SortName = (this_book.title_sorter)
|
||||||
|
if this_book.format == 'pdf':
|
||||||
|
db_added.Artist = metadata.authors[0]
|
||||||
|
db_added.Name = metadata.title
|
||||||
|
|
||||||
if metadata.comments:
|
if metadata.comments:
|
||||||
if lb_added:
|
if lb_added:
|
||||||
@ -2345,7 +2493,9 @@ class ITUNES(DevicePlugin):
|
|||||||
# Otherwise iBooks uses first <dc:subject> from opf
|
# Otherwise iBooks uses first <dc:subject> from opf
|
||||||
# iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12)
|
# iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12)
|
||||||
|
|
||||||
if self.use_series_data and metadata.series:
|
if self.use_series_as_category and metadata.series:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" using Series name as Genre")
|
||||||
if lb_added:
|
if lb_added:
|
||||||
lb_added.SortName = "%s %03d" % (metadata.series, metadata.series_index)
|
lb_added.SortName = "%s %03d" % (metadata.series, metadata.series_index)
|
||||||
lb_added.Genre = metadata.series
|
lb_added.Genre = metadata.series
|
||||||
@ -2365,6 +2515,8 @@ class ITUNES(DevicePlugin):
|
|||||||
self.log.warning(" iTunes automation interface reported an error"
|
self.log.warning(" iTunes automation interface reported an error"
|
||||||
" setting EpisodeNumber on iDevice")
|
" setting EpisodeNumber on iDevice")
|
||||||
elif metadata.tags:
|
elif metadata.tags:
|
||||||
|
if DEBUG:
|
||||||
|
self.log.info(" using Tag as Genre")
|
||||||
for tag in metadata.tags:
|
for tag in metadata.tags:
|
||||||
if self._is_alpha(tag[0]):
|
if self._is_alpha(tag[0]):
|
||||||
if lb_added:
|
if lb_added:
|
||||||
|
@ -127,15 +127,12 @@ class Stylizer(object):
|
|||||||
else:
|
else:
|
||||||
head = []
|
head = []
|
||||||
|
|
||||||
# GwR : Add webkit profile to cssutils before validating
|
# Add optional cssutils parsing profile from output_profile
|
||||||
if True:
|
if hasattr(self.opts.output_profile, 'cssutils_addProfile'):
|
||||||
wk_macros = {
|
profile = self.opts.output_profile.cssutils_addProfile
|
||||||
'border-width': '{length}|thin|medium|thick'
|
cssutils.profile.addProfile(profile['name'],
|
||||||
}
|
profile['props'],
|
||||||
wk_props = {
|
profile['macros'])
|
||||||
'-webkit-border-radius': r'{border-width}(\s+{border-width}){0,3}|inherit'
|
|
||||||
}
|
|
||||||
cssutils.profile.addProfile('webkit', wk_props, wk_macros)
|
|
||||||
|
|
||||||
parser = cssutils.CSSParser(fetcher=self._fetch_css_file,
|
parser = cssutils.CSSParser(fetcher=self._fetch_css_file,
|
||||||
log=logging.getLogger('calibre.css'))
|
log=logging.getLogger('calibre.css'))
|
||||||
|
@ -176,6 +176,7 @@ class AnnotationsAction(object): # {{{
|
|||||||
|
|
||||||
def mark_book_as_read(self,id):
|
def mark_book_as_read(self,id):
|
||||||
read_tag = gprefs.get('catalog_epub_mobi_read_tag')
|
read_tag = gprefs.get('catalog_epub_mobi_read_tag')
|
||||||
|
if read_tag:
|
||||||
self.db.set_tags(id, [read_tag], append=True)
|
self.db.set_tags(id, [read_tag], append=True)
|
||||||
|
|
||||||
def canceled(self):
|
def canceled(self):
|
||||||
|
@ -280,46 +280,6 @@ class BasicNewsRecipe(Recipe):
|
|||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
#: The CSS that is used to style the touchscreen elements, i.e., the navigation bars and
|
|
||||||
#: the Feed summaries.
|
|
||||||
touchscreen_css = u'''
|
|
||||||
.article_navbar {
|
|
||||||
-webkit-border-radius:4px;
|
|
||||||
background-color:#eee;
|
|
||||||
border:1px solid #888;
|
|
||||||
margin-left: 5%;
|
|
||||||
margin-right: 5%;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed_navbar {
|
|
||||||
-webkit-border-radius:4px;
|
|
||||||
background-color:#eee;
|
|
||||||
border:1px solid #888;
|
|
||||||
margin-left: 5%;
|
|
||||||
margin-right: 5%;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary_headline {
|
|
||||||
font-weight:bold; text-align:left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary_byline {
|
|
||||||
text-align:left;
|
|
||||||
font-family:monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary_text {
|
|
||||||
text-align:left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feed {
|
|
||||||
font-family:sans-serif; font-weight:bold; font-size:larger;
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
#: By default, calibre will use a default image for the masthead (Kindle only).
|
#: By default, calibre will use a default image for the masthead (Kindle only).
|
||||||
#: Override this in your recipe to provide a url to use as a masthead.
|
#: Override this in your recipe to provide a url to use as a masthead.
|
||||||
masthead_url = None
|
masthead_url = None
|
||||||
@ -625,6 +585,8 @@ class BasicNewsRecipe(Recipe):
|
|||||||
self.lrf = options.lrf
|
self.lrf = options.lrf
|
||||||
self.output_profile = options.output_profile
|
self.output_profile = options.output_profile
|
||||||
self.touchscreen = getattr(self.output_profile, 'touchscreen', False)
|
self.touchscreen = getattr(self.output_profile, 'touchscreen', False)
|
||||||
|
if self.touchscreen and getattr(self.output_profile, 'touchscreen_css',False):
|
||||||
|
self.extra_css += self.output_profile.touchscreen_css
|
||||||
|
|
||||||
self.output_dir = os.path.abspath(self.output_dir)
|
self.output_dir = os.path.abspath(self.output_dir)
|
||||||
if options.test:
|
if options.test:
|
||||||
@ -678,10 +640,8 @@ class BasicNewsRecipe(Recipe):
|
|||||||
if self.delay > 0:
|
if self.delay > 0:
|
||||||
self.simultaneous_downloads = 1
|
self.simultaneous_downloads = 1
|
||||||
|
|
||||||
if self.touchscreen:
|
self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else \
|
||||||
self.extra_css += self.touchscreen_css
|
templates.NavBarTemplate()
|
||||||
|
|
||||||
self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else templates.NavBarTemplate()
|
|
||||||
self.failed_downloads = []
|
self.failed_downloads = []
|
||||||
self.partial_failures = []
|
self.partial_failures = []
|
||||||
|
|
||||||
@ -768,7 +728,8 @@ class BasicNewsRecipe(Recipe):
|
|||||||
timefmt = self.timefmt
|
timefmt = self.timefmt
|
||||||
if self.touchscreen:
|
if self.touchscreen:
|
||||||
templ = templates.TouchscreenIndexTemplate()
|
templ = templates.TouchscreenIndexTemplate()
|
||||||
timefmt = '%A, %d %b %Y'
|
if getattr(self.output_profile,'timefmt',False):
|
||||||
|
timefmt = self.output_profile.timefmt
|
||||||
return templ.generate(self.title, "mastheadImage.jpg", timefmt, feeds,
|
return templ.generate(self.title, "mastheadImage.jpg", timefmt, feeds,
|
||||||
extra_css=css).render(doctype='xhtml')
|
extra_css=css).render(doctype='xhtml')
|
||||||
|
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
from lxml import html, etree
|
from lxml import html, etree
|
||||||
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \
|
from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \
|
||||||
STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \
|
STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \
|
||||||
@ -73,6 +76,7 @@ class EmbeddedContent(Template):
|
|||||||
class IndexTemplate(Template):
|
class IndexTemplate(Template):
|
||||||
|
|
||||||
def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None):
|
def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None):
|
||||||
|
self.IS_HTML = False
|
||||||
if isinstance(datefmt, unicode):
|
if isinstance(datefmt, unicode):
|
||||||
datefmt = datefmt.encode(preferred_encoding)
|
datefmt = datefmt.encode(preferred_encoding)
|
||||||
date = strftime(datefmt)
|
date = strftime(datefmt)
|
||||||
@ -198,6 +202,9 @@ class NavBarTemplate(Template):
|
|||||||
class TouchscreenIndexTemplate(Template):
|
class TouchscreenIndexTemplate(Template):
|
||||||
|
|
||||||
def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None):
|
def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None):
|
||||||
|
|
||||||
|
self.IS_HTML = False
|
||||||
|
|
||||||
if isinstance(datefmt, unicode):
|
if isinstance(datefmt, unicode):
|
||||||
datefmt = datefmt.encode(preferred_encoding)
|
datefmt = datefmt.encode(preferred_encoding)
|
||||||
date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
|
date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
|
||||||
@ -238,6 +245,8 @@ class TouchscreenFeedTemplate(Template):
|
|||||||
tokens = title.split(' ')
|
tokens = title.split(' ')
|
||||||
new_title_tokens = []
|
new_title_tokens = []
|
||||||
new_title_len = 0
|
new_title_len = 0
|
||||||
|
if len(tokens[0]) > clip:
|
||||||
|
return tokens[0][:clip] + '...'
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if len(token) + new_title_len < clip:
|
if len(token) + new_title_len < clip:
|
||||||
new_title_tokens.append(token)
|
new_title_tokens.append(token)
|
||||||
@ -248,29 +257,37 @@ class TouchscreenFeedTemplate(Template):
|
|||||||
break
|
break
|
||||||
return title
|
return title
|
||||||
|
|
||||||
|
self.IS_HTML = False
|
||||||
feed = feeds[f]
|
feed = feeds[f]
|
||||||
|
|
||||||
# Construct the navbar
|
# Construct the navbar
|
||||||
navbar_t = TABLE(CLASS('feed_navbar'))
|
navbar_t = TABLE(CLASS('touchscreen_navbar'))
|
||||||
navbar_tr = TR()
|
navbar_tr = TR()
|
||||||
|
|
||||||
|
# Previous Section
|
||||||
link = ''
|
link = ''
|
||||||
if f > 0:
|
if f > 0:
|
||||||
link = A(EM( '< ' + trim_title(feeds[f-1].title)),
|
link = A(CLASS('feed_link'),
|
||||||
|
trim_title(feeds[f-1].title),
|
||||||
href = '../feed_%d/index.html' % int(f-1))
|
href = '../feed_%d/index.html' % int(f-1))
|
||||||
navbar_tr.append(TD(link, width="40%", align="center"))
|
navbar_tr.append(TD(link, width="40%", align="center"))
|
||||||
|
|
||||||
|
# Up to Sections
|
||||||
link = A(STRONG('Sections'), href="../index.html")
|
link = A(STRONG('Sections'), href="../index.html")
|
||||||
navbar_tr.append(TD(link,width="20%",align="center"))
|
navbar_tr.append(TD(link,width="20%",align="center"))
|
||||||
|
|
||||||
|
# Next Section
|
||||||
link = ''
|
link = ''
|
||||||
if f < len(feeds)-1:
|
if f < len(feeds)-1:
|
||||||
link = A(EM(trim_title(feeds[f+1].title) + ' >'),
|
link = A(CLASS('feed_link'),
|
||||||
|
trim_title(feeds[f+1].title),
|
||||||
href = '../feed_%d/index.html' % int(f+1))
|
href = '../feed_%d/index.html' % int(f+1))
|
||||||
navbar_tr.append(TD(link, width="40%", align="center"))
|
navbar_tr.append(TD(link, width="40%", align="center", ))
|
||||||
|
|
||||||
navbar_t.append(navbar_tr)
|
navbar_t.append(navbar_tr)
|
||||||
navbar = navbar_t
|
top_navbar = navbar_t
|
||||||
|
bottom_navbar = copy.copy(navbar_t)
|
||||||
|
#print "\n%s\n" % etree.tostring(navbar_t, pretty_print=True)
|
||||||
|
|
||||||
|
|
||||||
# Build the page
|
# Build the page
|
||||||
head = HEAD(TITLE(feed.title))
|
head = HEAD(TITLE(feed.title))
|
||||||
@ -280,8 +297,8 @@ class TouchscreenFeedTemplate(Template):
|
|||||||
head.append(STYLE(extra_css, type='text/css'))
|
head.append(STYLE(extra_css, type='text/css'))
|
||||||
body = BODY(style='page-break-before:always')
|
body = BODY(style='page-break-before:always')
|
||||||
div = DIV(
|
div = DIV(
|
||||||
H2(feed.title, CLASS('calibre_feed_title', 'calibre_rescale_160')),
|
top_navbar,
|
||||||
DIV(style="border-top:1px solid gray;border-bottom:1em solid white")
|
H2(feed.title, CLASS('feed_title'))
|
||||||
)
|
)
|
||||||
body.append(div)
|
body.append(div)
|
||||||
|
|
||||||
@ -317,9 +334,8 @@ class TouchscreenFeedTemplate(Template):
|
|||||||
toc.append(tr)
|
toc.append(tr)
|
||||||
|
|
||||||
div.append(toc)
|
div.append(toc)
|
||||||
#div.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
|
|
||||||
div.append(BR())
|
div.append(BR())
|
||||||
div.append(navbar)
|
div.append(bottom_navbar)
|
||||||
self.root = HTML(head, body)
|
self.root = HTML(head, body)
|
||||||
|
|
||||||
class TouchscreenNavBarTemplate(Template):
|
class TouchscreenNavBarTemplate(Template):
|
||||||
@ -334,24 +350,23 @@ class TouchscreenNavBarTemplate(Template):
|
|||||||
head.append(STYLE(extra_css, type='text/css'))
|
head.append(STYLE(extra_css, type='text/css'))
|
||||||
|
|
||||||
navbar = DIV()
|
navbar = DIV()
|
||||||
if bottom:
|
navbar_t = TABLE(CLASS('touchscreen_navbar'))
|
||||||
navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white"))
|
|
||||||
|
|
||||||
navbar_t = TABLE(CLASS('article_navbar'))
|
|
||||||
navbar_tr = TR()
|
navbar_tr = TR()
|
||||||
|
|
||||||
# | Previous
|
# | Previous
|
||||||
if art > 0:
|
if art > 0:
|
||||||
href = '%s../article_%d/index.html'%(prefix, art-1)
|
href = '%s../article_%d/index.html'%(prefix, art-1)
|
||||||
navbar_tr.append(TD(A(EM('< Previous'), href=href), width="32%", align="center"))
|
navbar_tr.append(TD(A(EM('Previous'),href=href),
|
||||||
|
width="32%"))
|
||||||
else:
|
else:
|
||||||
navbar_tr.append(TD('', width="25%"))
|
navbar_tr.append(TD('', width="32%"))
|
||||||
|
|
||||||
# | Articles | Sections |
|
# | Articles | Sections |
|
||||||
href = '%s../index.html#article_%d'%(prefix, art)
|
href = '%s../index.html#article_%d'%(prefix, art)
|
||||||
navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%", align="center"))
|
navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%"))
|
||||||
|
|
||||||
href = '%s../../index.html#feed_%d'%(prefix, feed)
|
href = '%s../../index.html#feed_%d'%(prefix, feed)
|
||||||
navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%", align="center"))
|
navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%"))
|
||||||
|
|
||||||
# | Next
|
# | Next
|
||||||
next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \
|
next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \
|
||||||
@ -359,7 +374,8 @@ class TouchscreenNavBarTemplate(Template):
|
|||||||
up = '../..' if art == number_of_articles_in_feed - 1 else '..'
|
up = '../..' if art == number_of_articles_in_feed - 1 else '..'
|
||||||
href = '%s%s/%s/index.html'%(prefix, up, next)
|
href = '%s%s/%s/index.html'%(prefix, up, next)
|
||||||
|
|
||||||
navbar_tr.append(TD(A(EM('Next >'), href=href),width="32%", align="center"))
|
navbar_tr.append(TD(A(EM('Next'),href=href),
|
||||||
|
width="32%"))
|
||||||
navbar_t.append(navbar_tr)
|
navbar_t.append(navbar_tr)
|
||||||
navbar.append(navbar_t)
|
navbar.append(navbar_t)
|
||||||
#print "\n%s\n" % etree.tostring(navbar, pretty_print=True)
|
#print "\n%s\n" % etree.tostring(navbar, pretty_print=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user