mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add support for mast head images when creating news for the Kindle
This commit is contained in:
commit
508b60712d
@ -50,7 +50,7 @@ class MOBIOutput(OutputFormatPlugin):
|
|||||||
def check_for_masthead(self):
|
def check_for_masthead(self):
|
||||||
found = 'masthead' in self.oeb.guide
|
found = 'masthead' in self.oeb.guide
|
||||||
if not found:
|
if not found:
|
||||||
self.oeb.log.debug('No masthead found, generating default one...')
|
self.oeb.log.debug('No masthead found in manifest, generating default mastheadImage...')
|
||||||
try:
|
try:
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
PILImage
|
PILImage
|
||||||
@ -65,6 +65,9 @@ class MOBIOutput(OutputFormatPlugin):
|
|||||||
id, href = self.oeb.manifest.generate('masthead', 'masthead')
|
id, href = self.oeb.manifest.generate('masthead', 'masthead')
|
||||||
self.oeb.manifest.add(id, href, 'image/gif', data=raw)
|
self.oeb.manifest.add(id, href, 'image/gif', data=raw)
|
||||||
self.oeb.guide.add('masthead', 'Masthead Image', href)
|
self.oeb.guide.add('masthead', 'Masthead Image', href)
|
||||||
|
else:
|
||||||
|
self.oeb.log.debug('Using mastheadImage supplied in manifest...')
|
||||||
|
|
||||||
|
|
||||||
def dump_toc(self, toc) :
|
def dump_toc(self, toc) :
|
||||||
self.log( "\n >>> TOC contents <<<")
|
self.log( "\n >>> TOC contents <<<")
|
||||||
|
@ -14,19 +14,6 @@
|
|||||||
<string>Form</string>
|
<string>Form</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="text">
|
|
||||||
<string>Tags to exclude as genres (regex):</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::LogText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -37,7 +24,7 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="exclude_tags">
|
<widget class="QLineEdit" name="exclude_tags">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string extracomment="Tooltip comment here"/>
|
<string extracomment="Default: ~,Catalog"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -51,7 +38,7 @@
|
|||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLineEdit" name="read_tag">
|
<widget class="QLineEdit" name="read_tag">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string extracomment="Tooltip comment here"/>
|
<string extracomment="Default: +"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -65,18 +52,67 @@
|
|||||||
<item row="3" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QLineEdit" name="note_tag">
|
<widget class="QLineEdit" name="note_tag">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string extracomment="Tooltip comment here"/>
|
<string extracomment="Default: *"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1">
|
<item row="8" column="0">
|
||||||
|
<widget class="QCheckBox" name="numbers_as_text">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sort numbers as text</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
<widget class="QLineEdit" name="exclude_genre">
|
<widget class="QLineEdit" name="exclude_genre">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string extracomment="Tooltip comment here"/>
|
<string extracomment="Default: \[[\w]*\]"/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Regex pattern describing tags to exclude as genres:</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::LogText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>14</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Special marker tags for catalog generation</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Regex tips:
|
||||||
|
- The default regex of '\[[\w]*\]' ignores tags of the form '[tag]', e.g., '[Amazon Freebie]'
|
||||||
|
- A regex of '.' ignores all tags, generating no genre categories in the catalog</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
<spacer name="verticalSpacer">
|
<spacer name="verticalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
@ -89,13 +125,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QCheckBox" name="numbers_as_text">
|
|
||||||
<property name="text">
|
|
||||||
<string>Sort numbers as text</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>12</x>
|
<x>12</x>
|
||||||
<y>12</y>
|
<y>12</y>
|
||||||
<width>205</width>
|
<width>301</width>
|
||||||
<height>17</height>
|
<height>17</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -1773,7 +1773,8 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.playOrder += 1
|
self.playOrder += 1
|
||||||
navLabelTag = Tag(soup, 'navLabel')
|
navLabelTag = Tag(soup, 'navLabel')
|
||||||
textTag = Tag(soup, 'text')
|
textTag = Tag(soup, 'text')
|
||||||
textTag.insert(0, NavigableString("Titles beginning with %s" % (title_letters[i])))
|
textTag.insert(0, NavigableString(u"Titles beginning with %s" % \
|
||||||
|
(title_letters[i] if len(title_letters[i])>1 else "'" + title_letters[i] + "'")))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
navPointByLetterTag.insert(0,navLabelTag)
|
navPointByLetterTag.insert(0,navLabelTag)
|
||||||
contentTag = Tag(soup, 'content')
|
contentTag = Tag(soup, 'content')
|
||||||
|
@ -274,6 +274,10 @@ class BasicNewsRecipe(Recipe):
|
|||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
#: 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.
|
||||||
|
masthead_url = None
|
||||||
|
|
||||||
#: Set to a non empty string to disable this recipe
|
#: Set to a non empty string to disable this recipe
|
||||||
#: The string will be used as the disabled message
|
#: The string will be used as the disabled message
|
||||||
recipe_disabled = None
|
recipe_disabled = None
|
||||||
@ -294,6 +298,17 @@ class BasicNewsRecipe(Recipe):
|
|||||||
'''
|
'''
|
||||||
return getattr(self, 'cover_url', None)
|
return getattr(self, 'cover_url', None)
|
||||||
|
|
||||||
|
def get_masthead_url(self):
|
||||||
|
'''
|
||||||
|
Return a :term:`URL` to the masthead image for this issue or `None`.
|
||||||
|
By default it returns the value of the member `self.masthead_url` which
|
||||||
|
is normally `None`. If you want your recipe to download a masthead for the e-book
|
||||||
|
override this method in your subclass, or set the member variable `self.masthead_url`
|
||||||
|
before this method is called.
|
||||||
|
Masthead images are used in Kindle MOBI files.
|
||||||
|
'''
|
||||||
|
return getattr(self, 'masthead_url', None)
|
||||||
|
|
||||||
def get_feeds(self):
|
def get_feeds(self):
|
||||||
'''
|
'''
|
||||||
Return a list of :term:`RSS` feeds to fetch for this profile. Each element of the list
|
Return a list of :term:`RSS` feeds to fetch for this profile. Each element of the list
|
||||||
@ -745,6 +760,18 @@ class BasicNewsRecipe(Recipe):
|
|||||||
|
|
||||||
self.report_progress(0, _('Trying to download cover...'))
|
self.report_progress(0, _('Trying to download cover...'))
|
||||||
self.download_cover()
|
self.download_cover()
|
||||||
|
self.report_progress(0, _('Generating masthead...'))
|
||||||
|
try:
|
||||||
|
murl = self.get_masthead_url()
|
||||||
|
except:
|
||||||
|
self.log.exception('Failed to get masthead url')
|
||||||
|
murl = None
|
||||||
|
if murl is not None:
|
||||||
|
self.download_masthead(murl)
|
||||||
|
else:
|
||||||
|
self.masthead_path = os.path.join(self.output_dir, 'mastheadImage.jpg')
|
||||||
|
self.default_masthead_image(self.masthead_path)
|
||||||
|
|
||||||
if self.test:
|
if self.test:
|
||||||
feeds = feeds[:2]
|
feeds = feeds[:2]
|
||||||
self.has_single_feed = len(feeds) == 1
|
self.has_single_feed = len(feeds) == 1
|
||||||
@ -861,6 +888,33 @@ class BasicNewsRecipe(Recipe):
|
|||||||
self.log.exception('Failed to download cover')
|
self.log.exception('Failed to download cover')
|
||||||
self.cover_path = None
|
self.cover_path = None
|
||||||
|
|
||||||
|
def _download_masthead(self, mu):
|
||||||
|
ext = mu.rpartition('.')[-1]
|
||||||
|
if '?' in ext:
|
||||||
|
ext = ''
|
||||||
|
ext = ext.lower() if ext else 'jpg'
|
||||||
|
mpath = os.path.join(self.output_dir, 'masthead_source.'+ext)
|
||||||
|
outfile = os.path.join(self.output_dir, 'mastheadImage.jpg')
|
||||||
|
if os.access(mu, os.R_OK):
|
||||||
|
with open(mpath, 'wb') as mfile:
|
||||||
|
mfile.write(open(mu, 'rb').read())
|
||||||
|
else:
|
||||||
|
with nested(open(mpath, 'wb'), closing(self.browser.open(mu))) as (mfile, r):
|
||||||
|
mfile.write(r.read())
|
||||||
|
self.report_progress(1, _('Masthead image downloaded'))
|
||||||
|
self.prepare_masthead_image(mpath, outfile)
|
||||||
|
self.masthead_path = outfile
|
||||||
|
if os.path.exists(mpath):
|
||||||
|
os.remove(mpath)
|
||||||
|
|
||||||
|
|
||||||
|
def download_masthead(self, url):
|
||||||
|
try:
|
||||||
|
self._download_masthead(url)
|
||||||
|
except:
|
||||||
|
self.log.exception('Failed to download masthead')
|
||||||
|
|
||||||
|
|
||||||
def default_cover(self, cover_file):
|
def default_cover(self, cover_file):
|
||||||
'''
|
'''
|
||||||
Create a generic cover for recipes that dont have a cover
|
Create a generic cover for recipes that dont have a cover
|
||||||
@ -964,7 +1018,7 @@ class BasicNewsRecipe(Recipe):
|
|||||||
raise IOError('Failed to read image from: %s: %s'
|
raise IOError('Failed to read image from: %s: %s'
|
||||||
%(path_to_image, msg))
|
%(path_to_image, msg))
|
||||||
pw.PixelSetColor(p, 'white')
|
pw.PixelSetColor(p, 'white')
|
||||||
width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img)[1:]
|
width, height = pw.MagickGetImageWidth(img),pw.MagickGetImageHeight(img)
|
||||||
scaled, nwidth, nheight = fit_image(width, height, 600, 100)
|
scaled, nwidth, nheight = fit_image(width, height, 600, 100)
|
||||||
if not pw.MagickNewImage(img2, width, height, p):
|
if not pw.MagickNewImage(img2, width, height, p):
|
||||||
raise RuntimeError('Out of memory')
|
raise RuntimeError('Out of memory')
|
||||||
@ -988,7 +1042,6 @@ class BasicNewsRecipe(Recipe):
|
|||||||
for x in (img, img2, frame):
|
for x in (img, img2, frame):
|
||||||
pw.DestroyMagickWand(x)
|
pw.DestroyMagickWand(x)
|
||||||
|
|
||||||
|
|
||||||
def create_opf(self, feeds, dir=None):
|
def create_opf(self, feeds, dir=None):
|
||||||
if dir is None:
|
if dir is None:
|
||||||
dir = self.output_dir
|
dir = self.output_dir
|
||||||
@ -1003,11 +1056,22 @@ class BasicNewsRecipe(Recipe):
|
|||||||
mi.pubdate = datetime.now()
|
mi.pubdate = datetime.now()
|
||||||
opf_path = os.path.join(dir, 'index.opf')
|
opf_path = os.path.join(dir, 'index.opf')
|
||||||
ncx_path = os.path.join(dir, 'index.ncx')
|
ncx_path = os.path.join(dir, 'index.ncx')
|
||||||
|
|
||||||
opf = OPFCreator(dir, mi)
|
opf = OPFCreator(dir, mi)
|
||||||
|
# Add mastheadImage entry to <guide> section
|
||||||
|
mp = getattr(self, 'masthead_path', None)
|
||||||
|
if mp is not None:
|
||||||
|
from calibre.ebooks.metadata.opf2 import Guide
|
||||||
|
ref = Guide.Reference(os.path.basename(self.masthead_path), os.getcwdu())
|
||||||
|
ref.type = 'masthead'
|
||||||
|
ref.title = 'Masthead Image'
|
||||||
|
opf.guide.append(ref)
|
||||||
|
|
||||||
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
|
||||||
manifest.append(os.path.join(dir, 'index.html'))
|
manifest.append(os.path.join(dir, 'index.html'))
|
||||||
manifest.append(os.path.join(dir, 'index.ncx'))
|
manifest.append(os.path.join(dir, 'index.ncx'))
|
||||||
|
|
||||||
|
# Get cover
|
||||||
cpath = getattr(self, 'cover_path', None)
|
cpath = getattr(self, 'cover_path', None)
|
||||||
if cpath is None:
|
if cpath is None:
|
||||||
pf = open(os.path.join(dir, 'cover.jpg'), 'wb')
|
pf = open(os.path.join(dir, 'cover.jpg'), 'wb')
|
||||||
@ -1016,10 +1080,18 @@ class BasicNewsRecipe(Recipe):
|
|||||||
if cpath is not None and os.access(cpath, os.R_OK):
|
if cpath is not None and os.access(cpath, os.R_OK):
|
||||||
opf.cover = cpath
|
opf.cover = cpath
|
||||||
manifest.append(cpath)
|
manifest.append(cpath)
|
||||||
|
|
||||||
|
# Get masthead
|
||||||
|
mpath = getattr(self, 'masthead_path', None)
|
||||||
|
if mpath is not None and os.access(mpath, os.R_OK):
|
||||||
|
manifest.append(mpath)
|
||||||
|
|
||||||
opf.create_manifest_from_files_in(manifest)
|
opf.create_manifest_from_files_in(manifest)
|
||||||
for mani in opf.manifest:
|
for mani in opf.manifest:
|
||||||
if mani.path.endswith('.ncx'):
|
if mani.path.endswith('.ncx'):
|
||||||
mani.id = 'ncx'
|
mani.id = 'ncx'
|
||||||
|
if mani.path.endswith('mastheadImage.jpg'):
|
||||||
|
mani.id = 'masthead-image'
|
||||||
|
|
||||||
entries = ['index.html']
|
entries = ['index.html']
|
||||||
toc = TOC(base_path=dir)
|
toc = TOC(base_path=dir)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user