mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merged changes from trunk
This commit is contained in:
commit
68ab64166d
@ -8,10 +8,16 @@ www.guardian.co.uk
|
||||
'''
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from datetime import date
|
||||
|
||||
class Guardian(BasicNewsRecipe):
|
||||
|
||||
title = u'The Guardian'
|
||||
title = u'The Guardian / The Observer'
|
||||
if date.today().weekday() == 6:
|
||||
base_url = "http://www.guardian.co.uk/theobserver"
|
||||
else:
|
||||
base_url = "http://www.guardian.co.uk/theguardian"
|
||||
|
||||
__author__ = 'Seabound and Sujata Raman'
|
||||
language = 'en_GB'
|
||||
|
||||
@ -19,6 +25,10 @@ class Guardian(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
remove_javascript = True
|
||||
|
||||
# List of section titles to ignore
|
||||
# For example: ['Sport']
|
||||
ignore_sections = []
|
||||
|
||||
timefmt = ' [%a, %d %b %Y]'
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':["content","article_header","main-article-info",]}),
|
||||
@ -28,6 +38,7 @@ class Guardian(BasicNewsRecipe):
|
||||
dict(name='div', attrs={'id':["article-toolbox","subscribe-feeds",]}),
|
||||
dict(name='ul', attrs={'class':["pagination"]}),
|
||||
dict(name='ul', attrs={'id':["content-actions"]}),
|
||||
dict(name='img'),
|
||||
]
|
||||
use_embedded_content = False
|
||||
|
||||
@ -43,18 +54,6 @@ class Guardian(BasicNewsRecipe):
|
||||
#match-stats-summary{font-size:small; font-family:Arial,Helvetica,sans-serif;font-weight:normal;}
|
||||
'''
|
||||
|
||||
feeds = [
|
||||
('Front Page', 'http://www.guardian.co.uk/rss'),
|
||||
('Business', 'http://www.guardian.co.uk/business/rss'),
|
||||
('Sport', 'http://www.guardian.co.uk/sport/rss'),
|
||||
('Culture', 'http://www.guardian.co.uk/culture/rss'),
|
||||
('Money', 'http://www.guardian.co.uk/money/rss'),
|
||||
('Life & Style', 'http://www.guardian.co.uk/lifeandstyle/rss'),
|
||||
('Travel', 'http://www.guardian.co.uk/travel/rss'),
|
||||
('Environment', 'http://www.guardian.co.uk/environment/rss'),
|
||||
('Comment','http://www.guardian.co.uk/commentisfree/rss'),
|
||||
]
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = article.get('guid', None)
|
||||
if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \
|
||||
@ -76,7 +75,8 @@ class Guardian(BasicNewsRecipe):
|
||||
return soup
|
||||
|
||||
def find_sections(self):
|
||||
soup = self.index_to_soup('http://www.guardian.co.uk/theguardian')
|
||||
# soup = self.index_to_soup("http://www.guardian.co.uk/theobserver")
|
||||
soup = self.index_to_soup(self.base_url)
|
||||
# find cover pic
|
||||
img = soup.find( 'img',attrs ={'alt':'Guardian digital edition'})
|
||||
if img is not None:
|
||||
@ -113,13 +113,10 @@ class Guardian(BasicNewsRecipe):
|
||||
try:
|
||||
feeds = []
|
||||
for title, href in self.find_sections():
|
||||
feeds.append((title, list(self.find_articles(href))))
|
||||
if not title in self.ignore_sections:
|
||||
feeds.append((title, list(self.find_articles(href))))
|
||||
return feeds
|
||||
except:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def postprocess_html(self,soup,first):
|
||||
return soup.findAll('html')[0]
|
||||
|
||||
|
||||
|
@ -404,14 +404,16 @@ class MetadataUpdater(object):
|
||||
if self.cover_record is not None:
|
||||
size = len(self.cover_record)
|
||||
cover = rescale_image(data, size)
|
||||
cover += '\0' * (size - len(cover))
|
||||
self.cover_record[:] = cover
|
||||
if len(cover) <= size:
|
||||
cover += '\0' * (size - len(cover))
|
||||
self.cover_record[:] = cover
|
||||
if self.thumbnail_record is not None:
|
||||
size = len(self.thumbnail_record)
|
||||
thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN)
|
||||
thumbnail += '\0' * (size - len(thumbnail))
|
||||
self.thumbnail_record[:] = thumbnail
|
||||
return
|
||||
if len(thumbnail) <= size:
|
||||
thumbnail += '\0' * (size - len(thumbnail))
|
||||
self.thumbnail_record[:] = thumbnail
|
||||
return
|
||||
|
||||
def set_metadata(stream, mi):
|
||||
mu = MetadataUpdater(stream)
|
||||
|
@ -112,15 +112,34 @@ def align_block(raw, multiple=4, pad='\0'):
|
||||
|
||||
def rescale_image(data, maxsizeb, dimen=None):
|
||||
if dimen is not None:
|
||||
return thumbnail(data, width=dimen, height=dimen)[-1]
|
||||
# Replace transparent pixels with white pixels and convert to JPEG
|
||||
data = save_cover_data_to(data, 'img.jpg', return_data=True)
|
||||
data = thumbnail(data, width=dimen, height=dimen)[-1]
|
||||
else:
|
||||
# Replace transparent pixels with white pixels and convert to JPEG
|
||||
data = save_cover_data_to(data, 'img.jpg', return_data=True)
|
||||
if len(data) <= maxsizeb:
|
||||
return data
|
||||
orig_data = data
|
||||
img = Image()
|
||||
quality = 95
|
||||
|
||||
if hasattr(img, 'set_compression_quality'):
|
||||
img.load(data)
|
||||
while len(data) >= maxsizeb and quality >= 10:
|
||||
quality -= 5
|
||||
img.set_compression_quality(quality)
|
||||
data = img.export('jpg')
|
||||
if len(data) <= maxsizeb:
|
||||
return data
|
||||
orig_data = data
|
||||
|
||||
scale = 0.9
|
||||
while len(data) >= maxsizeb and scale >= 0.05:
|
||||
img = Image()
|
||||
img.load(data)
|
||||
img.load(orig_data)
|
||||
w, h = img.size
|
||||
img.size = (int(scale*w), int(scale*h))
|
||||
if hasattr(img, 'set_compression_quality'):
|
||||
img.set_compression_quality(quality)
|
||||
data = img.export('jpg')
|
||||
scale -= 0.05
|
||||
return data
|
||||
|
@ -308,7 +308,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
im = Image()
|
||||
im.load(cdata)
|
||||
im.trim(10)
|
||||
cdata = im.export('jpg')
|
||||
cdata = im.export('png')
|
||||
pix = QPixmap()
|
||||
pix.loadFromData(cdata)
|
||||
self.cover.setPixmap(pix)
|
||||
|
@ -490,26 +490,39 @@ class BooksView(QTableView): # {{{
|
||||
drag.setMimeData(md)
|
||||
cover = self.drag_icon(m.cover(self.currentIndex().row()),
|
||||
len(selected) > 1)
|
||||
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
|
||||
drag.setHotSpot(QPoint(-15, -15))
|
||||
drag.setPixmap(cover)
|
||||
return drag
|
||||
|
||||
def event_has_mods(self, event=None):
|
||||
mods = event.modifiers() if event is not None else \
|
||||
QApplication.keyboardModifiers()
|
||||
return mods & Qt.ControlModifier or mods & Qt.ShiftModifier
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
if event.button() == Qt.LeftButton and not self.event_has_mods():
|
||||
self.drag_start_pos = event.pos()
|
||||
return QTableView.mousePressEvent(self, event)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if not (event.buttons() & Qt.LeftButton) or self.drag_start_pos is None:
|
||||
if self.drag_start_pos is None:
|
||||
return QTableView.mouseMoveEvent(self, event)
|
||||
|
||||
if self.event_has_mods():
|
||||
self.drag_start_pos = None
|
||||
return
|
||||
if (event.pos() - self.drag_start_pos).manhattanLength() \
|
||||
< QApplication.startDragDistance():
|
||||
|
||||
if not (event.buttons() & Qt.LeftButton) or \
|
||||
(event.pos() - self.drag_start_pos).manhattanLength() \
|
||||
< QApplication.startDragDistance():
|
||||
return
|
||||
|
||||
index = self.indexAt(event.pos())
|
||||
if not index.isValid():
|
||||
return
|
||||
drag = self.drag_data()
|
||||
drag.exec_(Qt.CopyAction)
|
||||
self.drag_start_pos = None
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
if int(event.possibleActions() & Qt.CopyAction) + \
|
||||
@ -643,7 +656,7 @@ class DeviceBooksView(BooksView): # {{{
|
||||
drag.setMimeData(md)
|
||||
cover = self.drag_icon(m.cover(self.currentIndex().row()), len(paths) >
|
||||
1)
|
||||
drag.setHotSpot(QPoint(cover.width()//3, cover.height()//3))
|
||||
drag.setHotSpot(QPoint(-15, -15))
|
||||
drag.setPixmap(cover)
|
||||
return drag
|
||||
|
||||
|
@ -80,6 +80,7 @@ class TagsView(QTreeView): # {{{
|
||||
self.setItemDelegate(TagDelegate(self))
|
||||
self.made_connections = False
|
||||
self.setAcceptDrops(True)
|
||||
self.setDragDropMode(self.DropOnly)
|
||||
self.setDropIndicatorShown(True)
|
||||
|
||||
def set_database(self, db, tag_match, sort_by):
|
||||
|
@ -158,7 +158,7 @@ class Image(_magick.Image): # {{{
|
||||
format = ext[1:]
|
||||
format = format.upper()
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
with lopen(path, 'wb') as f:
|
||||
f.write(self.export(format))
|
||||
|
||||
def compose(self, img, left=0, top=0, operation='OverCompositeOp'):
|
||||
|
@ -11,22 +11,57 @@ from calibre.utils.magick import Image, DrawingWand, create_canvas
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre import fit_image
|
||||
|
||||
def normalize_format_name(fmt):
|
||||
fmt = fmt.lower()
|
||||
if fmt == 'jpeg':
|
||||
fmt = 'jpg'
|
||||
return fmt
|
||||
|
||||
def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
|
||||
return_data=False):
|
||||
return_data=False, compression_quality=90):
|
||||
'''
|
||||
Saves image in data to path, in the format specified by the path
|
||||
extension. Composes the image onto a blank canvas so as to
|
||||
properly convert transparent images.
|
||||
extension. Removes any transparency. If there is no transparency and no
|
||||
resize and the input and output image formats are the same, no changes are
|
||||
made.
|
||||
|
||||
:param compression_quality: The quality of the image after compression.
|
||||
Number between 1 and 100. 1 means highest compression, 100 means no
|
||||
compression (lossless).
|
||||
:param bgcolor: The color for transparent pixels. Must be specified in hex.
|
||||
:param resize_to: A tuple (width, height) or None for no resizing
|
||||
|
||||
'''
|
||||
changed = False
|
||||
img = Image()
|
||||
img.load(data)
|
||||
orig_fmt = normalize_format_name(img.format)
|
||||
fmt = os.path.splitext(path)[1]
|
||||
fmt = normalize_format_name(fmt[1:])
|
||||
|
||||
if resize_to is not None:
|
||||
img.size = (resize_to[0], resize_to[1])
|
||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||
canvas.compose(img)
|
||||
changed = True
|
||||
if not hasattr(img, 'has_transparent_pixels') or img.has_transparent_pixels():
|
||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||
canvas.compose(img)
|
||||
img = canvas
|
||||
changed = True
|
||||
if not changed:
|
||||
changed = fmt != orig_fmt
|
||||
if return_data:
|
||||
return canvas.export(os.path.splitext(path)[1][1:])
|
||||
canvas.save(path)
|
||||
if changed:
|
||||
if hasattr(img, 'set_compression_quality') and fmt == 'jpg':
|
||||
img.set_compression_quality(compression_quality)
|
||||
return img.export(fmt)
|
||||
return data
|
||||
if changed:
|
||||
if hasattr(img, 'set_compression_quality') and fmt == 'jpg':
|
||||
img.set_compression_quality(compression_quality)
|
||||
img.save(path)
|
||||
else:
|
||||
with lopen(path, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
|
||||
img = Image()
|
||||
@ -37,6 +72,8 @@ def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
|
||||
img.size = (nwidth, nheight)
|
||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||
canvas.compose(img)
|
||||
if fmt == 'jpg' and hasattr(canvas, 'set_compression_quality'):
|
||||
canvas.set_compression_quality(70)
|
||||
return (canvas.size[0], canvas.size[1], canvas.export(fmt))
|
||||
|
||||
def identify_data(data):
|
||||
|
@ -725,6 +725,49 @@ magick_Image_set_page(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||
}
|
||||
// }}}
|
||||
|
||||
// Image.set_compression_quality {{{
|
||||
|
||||
static PyObject *
|
||||
magick_Image_set_compression_quality(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||
Py_ssize_t quality;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "n", &quality)) return NULL;
|
||||
|
||||
if (!MagickSetImageCompressionQuality(self->wand, quality)) return magick_set_exception(self->wand);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
// }}}
|
||||
|
||||
// Image.has_transparent_pixels {{{
|
||||
|
||||
static PyObject *
|
||||
magick_Image_has_transparent_pixels(magick_Image *self, PyObject *args, PyObject *kwargs) {
|
||||
PixelIterator *pi = NULL;
|
||||
PixelWand **pixels = NULL;
|
||||
int found = 0;
|
||||
size_t r, c, width, height;
|
||||
double alpha;
|
||||
|
||||
height = MagickGetImageHeight(self->wand);
|
||||
pi = NewPixelIterator(self->wand);
|
||||
|
||||
for (r = 0; r < height; r++) {
|
||||
pixels = PixelGetNextIteratorRow(pi, &width);
|
||||
for (c = 0; c < width; c++) {
|
||||
alpha = PixelGetAlpha(pixels[c]);
|
||||
if (alpha < 1.00) {
|
||||
found = 1;
|
||||
c = width; r = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
pi = DestroyPixelIterator(pi);
|
||||
if (found) Py_RETURN_TRUE;
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
// }}}
|
||||
|
||||
// Image.normalize {{{
|
||||
|
||||
static PyObject *
|
||||
@ -872,6 +915,14 @@ static PyMethodDef magick_Image_methods[] = {
|
||||
"set_page(width, height, x, y) \n\n Sets the page geometry of the image."
|
||||
},
|
||||
|
||||
{"set_compression_quality", (PyCFunction)magick_Image_set_compression_quality, METH_VARARGS,
|
||||
"set_compression_quality(quality) \n\n Sets the compression quality when exporting the image."
|
||||
},
|
||||
|
||||
{"has_transparent_pixels", (PyCFunction)magick_Image_has_transparent_pixels, METH_VARARGS,
|
||||
"has_transparent_pixels() \n\n Returns True iff image has a (semi-) transparent pixel"
|
||||
},
|
||||
|
||||
{"thumbnail", (PyCFunction)magick_Image_thumbnail, METH_VARARGS,
|
||||
"thumbnail(width, height) \n\n Convert to a thumbnail of specified size."
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user