Merge from trunk

This commit is contained in:
Charles Haley 2010-10-04 21:19:31 +01:00
commit 39bc676f86
7 changed files with 143 additions and 37 deletions

View File

@ -8,10 +8,16 @@ www.guardian.co.uk
''' '''
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from datetime import date
class Guardian(BasicNewsRecipe): 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' __author__ = 'Seabound and Sujata Raman'
language = 'en_GB' language = 'en_GB'
@ -19,6 +25,10 @@ class Guardian(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
remove_javascript = True remove_javascript = True
# List of section titles to ignore
# For example: ['Sport']
ignore_sections = []
timefmt = ' [%a, %d %b %Y]' timefmt = ' [%a, %d %b %Y]'
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'id':["content","article_header","main-article-info",]}), 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='div', attrs={'id':["article-toolbox","subscribe-feeds",]}),
dict(name='ul', attrs={'class':["pagination"]}), dict(name='ul', attrs={'class':["pagination"]}),
dict(name='ul', attrs={'id':["content-actions"]}), dict(name='ul', attrs={'id':["content-actions"]}),
dict(name='img'),
] ]
use_embedded_content = False 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;} #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): def get_article_url(self, article):
url = article.get('guid', None) url = article.get('guid', None)
if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \ if '/video/' in url or '/flyer/' in url or '/quiz/' in url or \
@ -76,7 +75,8 @@ class Guardian(BasicNewsRecipe):
return soup return soup
def find_sections(self): 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 # find cover pic
img = soup.find( 'img',attrs ={'alt':'Guardian digital edition'}) img = soup.find( 'img',attrs ={'alt':'Guardian digital edition'})
if img is not None: if img is not None:
@ -113,13 +113,10 @@ class Guardian(BasicNewsRecipe):
try: try:
feeds = [] feeds = []
for title, href in self.find_sections(): 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 return feeds
except: except:
raise NotImplementedError raise NotImplementedError
def postprocess_html(self,soup,first):
return soup.findAll('html')[0]

View File

@ -112,15 +112,34 @@ def align_block(raw, multiple=4, pad='\0'):
def rescale_image(data, maxsizeb, dimen=None): def rescale_image(data, maxsizeb, dimen=None):
if dimen is not None: if dimen is not None:
return thumbnail(data, width=dimen, height=dimen)[-1] data = thumbnail(data, width=dimen, height=dimen)[-1]
# Replace transparent pixels with white pixels and convert to JPEG else:
data = save_cover_data_to(data, 'img.jpg', return_data=True) # 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 scale = 0.9
while len(data) >= maxsizeb and scale >= 0.05: while len(data) >= maxsizeb and scale >= 0.05:
img = Image() img = Image()
img.load(data) img.load(orig_data)
w, h = img.size w, h = img.size
img.size = (int(scale*w), int(scale*h)) img.size = (int(scale*w), int(scale*h))
if hasattr(img, 'set_compression_quality'):
img.set_compression_quality(quality)
data = img.export('jpg') data = img.export('jpg')
scale -= 0.05 scale -= 0.05
return data return data

View File

@ -308,7 +308,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
im = Image() im = Image()
im.load(cdata) im.load(cdata)
im.trim(10) im.trim(10)
cdata = im.export('jpg') cdata = im.export('png')
pix = QPixmap() pix = QPixmap()
pix.loadFromData(cdata) pix.loadFromData(cdata)
self.cover.setPixmap(pix) self.cover.setPixmap(pix)

View File

@ -500,11 +500,13 @@ class BooksView(QTableView): # {{{
return QTableView.mousePressEvent(self, event) return QTableView.mousePressEvent(self, event)
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if not (event.buttons() & Qt.LeftButton) or self.drag_start_pos is None: if not (event.buttons() & Qt.LeftButton) or \
return self.drag_start_pos is None or \
if (event.pos() - self.drag_start_pos).manhattanLength() \ QApplication.keyboardModifiers() != Qt.NoModifier or \
< QApplication.startDragDistance(): (event.pos() - self.drag_start_pos).manhattanLength() \
return < QApplication.startDragDistance():
return QTableView.mouseMoveEvent(self, event)
index = self.indexAt(event.pos()) index = self.indexAt(event.pos())
if not index.isValid(): if not index.isValid():
return return

View File

@ -158,7 +158,7 @@ class Image(_magick.Image): # {{{
format = ext[1:] format = ext[1:]
format = format.upper() format = format.upper()
with open(path, 'wb') as f: with lopen(path, 'wb') as f:
f.write(self.export(format)) f.write(self.export(format))
def compose(self, img, left=0, top=0, operation='OverCompositeOp'): def compose(self, img, left=0, top=0, operation='OverCompositeOp'):

View File

@ -11,22 +11,57 @@ from calibre.utils.magick import Image, DrawingWand, create_canvas
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre import fit_image 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, 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 Saves image in data to path, in the format specified by the path
extension. Composes the image onto a blank canvas so as to extension. Removes any transparency. If there is no transparency and no
properly convert transparent images. 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 = Image()
img.load(data) 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: if resize_to is not None:
img.size = (resize_to[0], resize_to[1]) img.size = (resize_to[0], resize_to[1])
canvas = create_canvas(img.size[0], img.size[1], bgcolor) changed = True
canvas.compose(img) 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: if return_data:
return canvas.export(os.path.splitext(path)[1][1:]) if changed:
canvas.save(path) 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'): def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
img = Image() img = Image()
@ -37,6 +72,8 @@ def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
img.size = (nwidth, nheight) img.size = (nwidth, nheight)
canvas = create_canvas(img.size[0], img.size[1], bgcolor) canvas = create_canvas(img.size[0], img.size[1], bgcolor)
canvas.compose(img) 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)) return (canvas.size[0], canvas.size[1], canvas.export(fmt))
def identify_data(data): def identify_data(data):

View File

@ -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 {{{ // Image.normalize {{{
static PyObject * 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_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", (PyCFunction)magick_Image_thumbnail, METH_VARARGS,
"thumbnail(width, height) \n\n Convert to a thumbnail of specified size." "thumbnail(width, height) \n\n Convert to a thumbnail of specified size."
}, },