diff --git a/resources/recipes/guardian.recipe b/resources/recipes/guardian.recipe index 344e061c26..17138fe909 100644 --- a/resources/recipes/guardian.recipe +++ b/resources/recipes/guardian.recipe @@ -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] - - diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index e8fc4557fd..5e4dca4a9e 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -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 diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 6c57e30166..b39b752ac6 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -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) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 40f74425c8..051f871e73 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -500,11 +500,13 @@ class BooksView(QTableView): # {{{ return QTableView.mousePressEvent(self, event) def mouseMoveEvent(self, event): - if not (event.buttons() & Qt.LeftButton) or self.drag_start_pos is None: - return - if (event.pos() - self.drag_start_pos).manhattanLength() \ - < QApplication.startDragDistance(): - return + if not (event.buttons() & Qt.LeftButton) or \ + self.drag_start_pos is None or \ + QApplication.keyboardModifiers() != Qt.NoModifier or \ + (event.pos() - self.drag_start_pos).manhattanLength() \ + < QApplication.startDragDistance(): + return QTableView.mouseMoveEvent(self, event) + index = self.indexAt(event.pos()) if not index.isValid(): return diff --git a/src/calibre/utils/magick/__init__.py b/src/calibre/utils/magick/__init__.py index 2707430c67..3a4fca09c0 100644 --- a/src/calibre/utils/magick/__init__.py +++ b/src/calibre/utils/magick/__init__.py @@ -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'): diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index dcf9d7b671..88f488cb23 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -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): diff --git a/src/calibre/utils/magick/magick.c b/src/calibre/utils/magick/magick.c index 92d68d5afd..b1436a830b 100644 --- a/src/calibre/utils/magick/magick.c +++ b/src/calibre/utils/magick/magick.c @@ -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." },