diff --git a/resources/recipes/mainichi_it_news.recipe b/resources/recipes/mainichi_it_news.recipe index 4c285a2c01..eddab149cd 100644 --- a/resources/recipes/mainichi_it_news.recipe +++ b/resources/recipes/mainichi_it_news.recipe @@ -1,4 +1,5 @@ from calibre.web.feeds.news import BasicNewsRecipe +import re class MainichiDailyITNews(BasicNewsRecipe): title = u'\u6bce\u65e5\u65b0\u805e(IT&\u5bb6\u96fb)' @@ -14,6 +15,7 @@ class MainichiDailyITNews(BasicNewsRecipe): remove_tags_before = {'class':"NewsTitle"} remove_tags = [{'class':"RelatedArticle"}] + remove_tags_after = {'class':"Credit"} def parse_feeds(self): @@ -29,4 +31,4 @@ class MainichiDailyITNews(BasicNewsRecipe): index = curfeed.articles.index(d) curfeed.articles[index:index+1] = [] - return feeds remove_tags_after = {'class':"Credit"} + return feeds diff --git a/resources/recipes/ming_pao.recipe b/resources/recipes/ming_pao.recipe index 385dbdbdb7..726181f57b 100644 --- a/resources/recipes/ming_pao.recipe +++ b/resources/recipes/ming_pao.recipe @@ -1,8 +1,9 @@ __license__ = 'GPL v3' __copyright__ = '2010, Eddie Lau' ''' -modified from Singtao Toronto calibre recipe by rty Change Log: +2010/12/07: add entertainment section, use newspaper front page as ebook cover, suppress date display in section list + (to avoid wrong date display in case the user generates the ebook in a time zone different from HKT) 2010/11/22: add English section, remove eco-news section which is not updated daily, correct ordering of articles 2010/11/12: add news image and eco-news section @@ -17,14 +18,15 @@ from calibre.web.feeds.recipes import BasicNewsRecipe from contextlib import nested -from calibre import __appname__, strftime +from calibre import __appname__ from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata import MetaInformation -from calibre.utils.date import now as nowf class MPHKRecipe(BasicNewsRecipe): + IsKindleUsed = True # to avoid generating periodical in which CJK characters can't be displayed in section/article view + title = 'Ming Pao - Hong Kong' oldest_article = 1 max_articles_per_feed = 100 @@ -39,13 +41,13 @@ class MPHKRecipe(BasicNewsRecipe): encoding = 'Big5-HKSCS' recursions = 0 conversion_options = {'linearize_tables':True} - extra_css = 'img {display: block; margin-left: auto; margin-right: auto; margin-top: 10px; margin-bottom: 10px;}' - #extra_css = 'img {float:right; margin:4px;}' + timefmt = '' + extra_css = 'img {display: block; margin-left: auto; margin-right: auto; margin-top: 10px; margin-bottom: 10px;} font>b {font-size:200%; font-weight:bold;}' masthead_url = 'http://news.mingpao.com/image/portals_top_logo_news.gif' keep_only_tags = [dict(name='h1'), - #dict(name='font', attrs={'style':['font-size:14pt; line-height:160%;']}), # for entertainment page + dict(name='font', attrs={'style':['font-size:14pt; line-height:160%;']}), # for entertainment page title dict(attrs={'class':['photo']}), - dict(attrs={'id':['newscontent']}), + dict(attrs={'id':['newscontent']}), # entertainment page content dict(attrs={'id':['newscontent01','newscontent02']})] remove_tags = [dict(name='style'), dict(attrs={'id':['newscontent135']})] # for the finance page @@ -55,51 +57,68 @@ class MPHKRecipe(BasicNewsRecipe): lambda match: '

'), (re.compile(r'

', re.DOTALL|re.IGNORECASE), lambda match: ''), + (re.compile(r'

', re.DOTALL|re.IGNORECASE), # for entertainment page + lambda match: '') ] def image_url_processor(cls, baseurl, url): # trick: break the url at the first occurance of digit, add an additional # '_' at the front # not working, may need to move this to preprocess_html() method - #minIdx = 10000 - #i0 = url.find('0') - #if i0 >= 0 and i0 < minIdx: - # minIdx = i0 - #i1 = url.find('1') - #if i1 >= 0 and i1 < minIdx: - # minIdx = i1 - #i2 = url.find('2') - #if i2 >= 0 and i2 < minIdx: - # minIdx = i2 - #i3 = url.find('3') - #if i3 >= 0 and i0 < minIdx: - # minIdx = i3 - #i4 = url.find('4') - #if i4 >= 0 and i4 < minIdx: - # minIdx = i4 - #i5 = url.find('5') - #if i5 >= 0 and i5 < minIdx: - # minIdx = i5 - #i6 = url.find('6') - #if i6 >= 0 and i6 < minIdx: - # minIdx = i6 - #i7 = url.find('7') - #if i7 >= 0 and i7 < minIdx: - # minIdx = i7 - #i8 = url.find('8') - #if i8 >= 0 and i8 < minIdx: - # minIdx = i8 - #i9 = url.find('9') - #if i9 >= 0 and i9 < minIdx: - # minIdx = i9 - #return url[0:minIdx] + '_' + url[minIdx+1:] +# minIdx = 10000 +# i0 = url.find('0') +# if i0 >= 0 and i0 < minIdx: +# minIdx = i0 +# i1 = url.find('1') +# if i1 >= 0 and i1 < minIdx: +# minIdx = i1 +# i2 = url.find('2') +# if i2 >= 0 and i2 < minIdx: +# minIdx = i2 +# i3 = url.find('3') +# if i3 >= 0 and i0 < minIdx: +# minIdx = i3 +# i4 = url.find('4') +# if i4 >= 0 and i4 < minIdx: +# minIdx = i4 +# i5 = url.find('5') +# if i5 >= 0 and i5 < minIdx: +# minIdx = i5 +# i6 = url.find('6') +# if i6 >= 0 and i6 < minIdx: +# minIdx = i6 +# i7 = url.find('7') +# if i7 >= 0 and i7 < minIdx: +# minIdx = i7 +# i8 = url.find('8') +# if i8 >= 0 and i8 < minIdx: +# minIdx = i8 +# i9 = url.find('9') +# if i9 >= 0 and i9 < minIdx: +# minIdx = i9 return url - def get_fetchdate(self): + def get_dtlocal(self): dt_utc = datetime.datetime.utcnow() # convert UTC to local hk time - at around HKT 6.00am, all news are available dt_local = dt_utc - datetime.timedelta(-2.0/24) - return dt_local.strftime("%Y%m%d") + return dt_local + + def get_fetchdate(self): + return self.get_dtlocal().strftime("%Y%m%d") + + def get_fetchday(self): + # convert UTC to local hk time - at around HKT 6.00am, all news are available + return self.get_dtlocal().strftime("%d") + + def get_cover_url(self): + cover = 'http://news.mingpao.com/' + self.get_fetchdate() + '/' + self.get_fetchdate() + '_' + self.get_fetchday() + 'gacov.jpg' + br = BasicNewsRecipe.get_browser() + try: + br.open(cover) + except: + cover = None + return cover def parse_index(self): feeds = [] @@ -127,9 +146,9 @@ class MPHKRecipe(BasicNewsRecipe): # if eco_articles: # feeds.append((u'\u74b0\u4fdd Eco News', eco_articles)) # special - entertainment - #ent_articles = self.parse_ent_section('http://ol.mingpao.com/cfm/star1.cfm') - #if ent_articles: - # feeds.append(('Entertainment', ent_articles)) + ent_articles = self.parse_ent_section('http://ol.mingpao.com/cfm/star1.cfm') + if ent_articles: + feeds.append((u'\u5f71\u8996 Entertainment', ent_articles)) return feeds def parse_section(self, url): @@ -164,6 +183,7 @@ class MPHKRecipe(BasicNewsRecipe): return current_articles def parse_eco_section(self, url): + dateStr = self.get_fetchdate() soup = self.index_to_soup(url) divs = soup.findAll(attrs={'class': ['bullet']}) current_articles = [] @@ -173,23 +193,25 @@ class MPHKRecipe(BasicNewsRecipe): title = self.tag_to_string(a) url = a.get('href', False) url = 'http://tssl.mingpao.com/htm/marketing/eco/cfm/' +url - if url not in included_urls and url.rfind('Redirect') == -1: + if url not in included_urls and url.rfind('Redirect') == -1 and not url.rfind('.txt') == -1 and not url.rfind(dateStr) == -1: current_articles.append({'title': title, 'url': url, 'description':''}) included_urls.append(url) return current_articles - #def parse_ent_section(self, url): - # dateStr = self.get_fetchdate() - # soup = self.index_to_soup(url) - # a = soup.findAll('a', href=True) - # current_articles = [] - # included_urls = [] - # for i in a: - # title = self.tag_to_string(i) - # url = 'http://ol.mingpao.com/cfm/' + i.get('href', False) - # if url not in included_urls and not url.rfind('.txt') == -1 and not url.rfind(dateStr) == -1 and not title == '': - # current_articles.append({'title': title, 'url': url, 'description': ''}) - # return current_articles + def parse_ent_section(self, url): + soup = self.index_to_soup(url) + a = soup.findAll('a', href=True) + a.reverse() + current_articles = [] + included_urls = [] + for i in a: + title = self.tag_to_string(i) + url = 'http://ol.mingpao.com/cfm/' + i.get('href', False) + if (url not in included_urls) and (not url.rfind('.txt') == -1) and (not url.rfind('star') == -1): + current_articles.append({'title': title, 'url': url, 'description': ''}) + included_urls.append(url) + current_articles.reverse() + return current_articles def preprocess_html(self, soup): for item in soup.findAll(style=True): @@ -201,21 +223,26 @@ class MPHKRecipe(BasicNewsRecipe): return soup def create_opf(self, feeds, dir=None): - #super(MPHKRecipe,self).create_opf(feeds, dir) + if self.IsKindleUsed == False: + super(MPHKRecipe,self).create_opf(feeds, dir) + return if dir is None: dir = self.output_dir title = self.short_title() - if self.output_profile.periodical_date_in_title: - title += strftime(self.timefmt) + title += ' ' + self.get_fetchdate() + #if self.output_profile.periodical_date_in_title: + # title += strftime(self.timefmt) mi = MetaInformation(title, [__appname__]) mi.publisher = __appname__ mi.author_sort = __appname__ mi.publication_type = self.publication_type+':'+self.short_title() - mi.timestamp = nowf() + #mi.timestamp = nowf() + mi.timestamp = self.get_dtlocal() mi.comments = self.description if not isinstance(mi.comments, unicode): mi.comments = mi.comments.decode('utf-8', 'replace') - mi.pubdate = nowf() + #mi.pubdate = nowf() + mi.pubdate = self.get_dtlocal() opf_path = os.path.join(dir, 'index.opf') ncx_path = os.path.join(dir, 'index.ncx') opf = OPFCreator(dir, mi) diff --git a/resources/recipes/the_h.recipe b/resources/recipes/the_h.recipe index dbfad7e32a..28a1571dc5 100644 --- a/resources/recipes/the_h.recipe +++ b/resources/recipes/the_h.recipe @@ -14,7 +14,7 @@ class TheHeiseOnline(BasicNewsRecipe): oldest_article = 3 description = 'In association with Heise Online' publisher = 'Heise Media UK Ltd.' - category = 'news, technology, security' + category = 'news, technology, security, OSS, internet' max_articles_per_feed = 100 language = 'en' encoding = 'utf-8' @@ -27,6 +27,12 @@ class TheHeiseOnline(BasicNewsRecipe): feeds = [ (u'The H News Feed', u'http://www.h-online.com/news/atom.xml') ] + cover_url = 'http://www.h-online.com/icons/logo_theH.gif' + + remove_tags = [ + dict(id="logo"), + dict(id="footer") + ] def print_version(self, url): return url + '?view=print' diff --git a/resources/recipes/toyokeizai.recipe b/resources/recipes/toyokeizai.recipe new file mode 100644 index 0000000000..395a8bb9b7 --- /dev/null +++ b/resources/recipes/toyokeizai.recipe @@ -0,0 +1,68 @@ +__license__ = 'GPL v3' +__copyright__ = '2010, Hiroshi Miura ' +''' +www.toyokeizai.net +''' + +from calibre.web.feeds.news import BasicNewsRecipe +import re + +class Toyokeizai(BasicNewsRecipe): + title = u'ToyoKeizai News' + __author__ = 'Hiroshi Miura' + oldest_article = 1 + max_articles_per_feed = 50 + description = 'Japanese traditional economy and business magazine, only for advanced subscribers supported' + publisher = 'Toyokeizai Shinbun Sha' + category = 'economy, magazine, japan' + language = 'ja' + encoding = 'euc-jp' + index = 'http://member.toyokeizai.net/news/' + remove_javascript = True + no_stylesheets = True + masthead_title = u'TOYOKEIZAI' + needs_subscription = True + timefmt = '[%y/%m/%d]' + recursions = 5 + match_regexps =[ r'page/\d+'] + + keep_only_tags = [ + dict(name='div', attrs={'class':['news']}), + dict(name='div', attrs={'class':["news_cont"]}), + dict(name='div', attrs={'class':["news_con"]}), +# dict(name='div', attrs={'class':["norightsMessage"]}) + ] + remove_tags = [{'class':"mt35 mgz"}, + {'class':"mt20 newzia"}, + {'class':"mt20 fontS"}, + {'class':"bk_btn_m"}, + dict(id='newzia_connect_member') + ] + + def parse_index(self): + feeds = [] + soup = self.index_to_soup(self.index) + topstories = soup.find('ul',attrs={'class':'list6'}) + if topstories: + newsarticles = [] + for itt in topstories.findAll('li'): + itema = itt.find('a',href=True) + itemd = itt.find('span') + newsarticles.append({ + 'title' :itema.string + ,'date' :re.compile(r"\- ").sub("",itemd.string) + ,'url' :'http://member.toyokeizai.net' + itema['href'] + ,'description':itema['title'] + }) + feeds.append(('news', newsarticles)) + return feeds + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://member.toyokeizai.net/norights/form/') + br.select_form(nr=0) + br['kaiin_id'] = self.username + br['password'] = self.password + res = br.submit() + return br diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 5b6b79e3df..a99c9df125 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -129,6 +129,7 @@ class CoverCache(Thread): # {{{ self.keep_running = True self.cache = {} self.lock = RLock() + self.allowed_ids = frozenset([]) self.null_image = QImage() def stop(self): @@ -175,6 +176,11 @@ class CoverCache(Thread): # {{{ break for id_ in ids: time.sleep(0.050) # Limit 20/second to not overwhelm the GUI + if not self.keep_running: + return + with self.lock: + if id_ not in self.allowed_ids: + continue try: img = self._image_for_id(id_) except: @@ -193,6 +199,7 @@ class CoverCache(Thread): # {{{ def set_cache(self, ids): with self.lock: + self.allowed_ids = frozenset(ids) already_loaded = set([]) for id in self.cache.keys(): if id in ids: @@ -213,8 +220,9 @@ class CoverCache(Thread): # {{{ def refresh(self, ids): with self.lock: for id_ in ids: - self.cache.pop(id_, None) - self.load_queue.put(id_) + cover = self.cache.pop(id_, None) + if cover is not None: + self.load_queue.put(id_) # }}} ### Global utility function for get_match here and in gui2/library.py diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index d3cbd58c7d..c03a8660c8 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -54,19 +54,23 @@ def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None, changed = True if not changed: changed = fmt != orig_fmt + + ret = None if return_data: + ret = data 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) + ret = img.export(fmt) else: - with lopen(path, 'wb') as f: - f.write(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) + return ret def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'): img = Image() diff --git a/src/calibre/utils/magick/magick.c b/src/calibre/utils/magick/magick.c index 9dac37f2ff..fd9563529a 100644 --- a/src/calibre/utils/magick/magick.c +++ b/src/calibre/utils/magick/magick.c @@ -5,6 +5,9 @@ #include "magick_constants.h" +// Ensure that the underlying MagickWand has not been deleted +#define NULL_CHECK(x) if(self->wand == NULL) {PyErr_SetString(PyExc_ValueError, "Underlying ImageMagick Wand has been destroyed"); return x; } + // magick_set_exception {{{ PyObject* magick_set_exception(MagickWand *wand) { ExceptionType ext; @@ -54,6 +57,7 @@ magick_PixelWand_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static PyObject * magick_PixelWand_color_getter(magick_PixelWand *self, void *closure) { const char *fp; + NULL_CHECK(NULL); fp = PixelGetColorAsNormalizedString(self->wand); return Py_BuildValue("s", fp); } @@ -62,6 +66,8 @@ static int magick_PixelWand_color_setter(magick_PixelWand *self, PyObject *val, void *closure) { char *fmt; + NULL_CHECK(-1); + if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete PixelWand color"); return -1; @@ -80,8 +86,21 @@ magick_PixelWand_color_setter(magick_PixelWand *self, PyObject *val, void *closu // }}} +// PixelWand.destroy {{{ + +static PyObject * +magick_PixelWand_destroy(magick_PixelWand *self, PyObject *args, PyObject *kwargs) { + NULL_CHECK(NULL) + self->wand = DestroyPixelWand(self->wand); + Py_RETURN_NONE; +} +// }}} + // PixelWand attr list {{{ static PyMethodDef magick_PixelWand_methods[] = { + {"destroy", (PyCFunction)magick_PixelWand_destroy, METH_VARARGS, + "Destroy the underlying ImageMagick Wand. WARNING: After using this method, all methods on this object will raise an exception."}, + {NULL} /* Sentinel */ }; @@ -175,10 +194,21 @@ magick_DrawingWand_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return (PyObject *)self; } +// DrawingWand.destroy {{{ + +static PyObject * +magick_DrawingWand_destroy(magick_DrawingWand *self, PyObject *args, PyObject *kwargs) { + NULL_CHECK(NULL) + self->wand = DestroyDrawingWand(self->wand); + Py_RETURN_NONE; +} +// }}} + // DrawingWand.font {{{ static PyObject * magick_DrawingWand_font_getter(magick_DrawingWand *self, void *closure) { const char *fp; + NULL_CHECK(NULL); fp = DrawGetFont(self->wand); return Py_BuildValue("s", fp); } @@ -186,6 +216,7 @@ magick_DrawingWand_font_getter(magick_DrawingWand *self, void *closure) { static int magick_DrawingWand_font_setter(magick_DrawingWand *self, PyObject *val, void *closure) { char *fmt; + NULL_CHECK(-1); if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand font"); @@ -208,11 +239,13 @@ magick_DrawingWand_font_setter(magick_DrawingWand *self, PyObject *val, void *cl // DrawingWand.font_size {{{ static PyObject * magick_DrawingWand_fontsize_getter(magick_DrawingWand *self, void *closure) { + NULL_CHECK(NULL) return Py_BuildValue("d", DrawGetFontSize(self->wand)); } static int magick_DrawingWand_fontsize_setter(magick_DrawingWand *self, PyObject *val, void *closure) { + NULL_CHECK(-1) if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand fontsize"); return -1; @@ -233,12 +266,14 @@ magick_DrawingWand_fontsize_setter(magick_DrawingWand *self, PyObject *val, void // DrawingWand.text_antialias {{{ static PyObject * magick_DrawingWand_textantialias_getter(magick_DrawingWand *self, void *closure) { + NULL_CHECK(NULL); if (DrawGetTextAntialias(self->wand)) Py_RETURN_TRUE; Py_RETURN_FALSE; } static int magick_DrawingWand_textantialias_setter(magick_DrawingWand *self, PyObject *val, void *closure) { + NULL_CHECK(-1); if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand textantialias"); return -1; @@ -253,6 +288,7 @@ magick_DrawingWand_textantialias_setter(magick_DrawingWand *self, PyObject *val, // DrawingWand.gravity {{{ static PyObject * magick_DrawingWand_gravity_getter(magick_DrawingWand *self, void *closure) { + NULL_CHECK(NULL); return Py_BuildValue("n", DrawGetGravity(self->wand)); } @@ -260,6 +296,8 @@ static int magick_DrawingWand_gravity_setter(magick_DrawingWand *self, PyObject *val, void *closure) { int grav; + NULL_CHECK(-1); + if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete DrawingWand gravity"); return -1; @@ -281,6 +319,9 @@ magick_DrawingWand_gravity_setter(magick_DrawingWand *self, PyObject *val, void // DrawingWand attr list {{{ static PyMethodDef magick_DrawingWand_methods[] = { + {"destroy", (PyCFunction)magick_DrawingWand_destroy, METH_VARARGS, + "Destroy the underlying ImageMagick Wand. WARNING: After using this method, all methods on this object will raise an exception."}, + {NULL} /* Sentinel */ }; @@ -402,6 +443,7 @@ magick_Image_load(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t dlen; MagickBooleanType res; + NULL_CHECK(NULL) if (!PyArg_ParseTuple(args, "s#", &data, &dlen)) return NULL; res = MagickReadImageBlob(self->wand, data, dlen); @@ -420,7 +462,8 @@ magick_Image_read(magick_Image *self, PyObject *args, PyObject *kwargs) { const char *data; MagickBooleanType res; - if (!PyArg_ParseTuple(args, "s", &data)) return NULL; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "s", &data)) return NULL; res = MagickReadImage(self->wand, data); @@ -441,6 +484,8 @@ magick_Image_create_canvas(magick_Image *self, PyObject *args, PyObject *kwargs) PixelWand *pw; MagickBooleanType res = MagickFalse; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "nns", &width, &height, &bgcolor)) return NULL; pw = NewPixelWand(); @@ -464,6 +509,8 @@ magick_Image_font_metrics(magick_Image *self, PyObject *args, PyObject *kwargs) DrawingWand *dw; double *metrics; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "O!s", &magick_DrawingWandType, &dw_, &text)) return NULL; dw = ((magick_DrawingWand*)dw_)->wand; if (!IsDrawingWand(dw)) { PyErr_SetString(PyExc_TypeError, "Invalid drawing wand"); return NULL; } @@ -490,6 +537,8 @@ magick_Image_annotate(magick_Image *self, PyObject *args, PyObject *kwargs) { PyObject *dw_; DrawingWand *dw; double x, y, angle; + + NULL_CHECK(NULL) if (!PyArg_ParseTuple(args, "O!ddds", &magick_DrawingWandType, &dw_, &x, &y, &angle, &text)) return NULL; dw = ((magick_DrawingWand*)dw_)->wand; @@ -510,6 +559,8 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) { PyObject *ans; size_t len = 0; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "s", &fmt)) return NULL; if (!MagickSetFormat(self->wand, fmt)) { @@ -533,6 +584,8 @@ magick_Image_export(magick_Image *self, PyObject *args, PyObject *kwargs) { static PyObject * magick_Image_size_getter(magick_Image *self, void *closure) { size_t width, height; + NULL_CHECK(NULL) + width = MagickGetImageWidth(self->wand); height = MagickGetImageHeight(self->wand); return Py_BuildValue("nn", width, height); @@ -545,6 +598,9 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) { double blur; MagickBooleanType res; + NULL_CHECK(-1) + + if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete image size"); return -1; @@ -592,6 +648,8 @@ magick_Image_size_setter(magick_Image *self, PyObject *val, void *closure) { static PyObject * magick_Image_format_getter(magick_Image *self, void *closure) { const char *fmt; + NULL_CHECK(NULL) + fmt = MagickGetImageFormat(self->wand); return Py_BuildValue("s", fmt); } @@ -599,6 +657,8 @@ magick_Image_format_getter(magick_Image *self, void *closure) { static int magick_Image_format_setter(magick_Image *self, PyObject *val, void *closure) { char *fmt; + NULL_CHECK(-1) + if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete image format"); @@ -628,6 +688,8 @@ magick_Image_distort(magick_Image *self, PyObject *args, PyObject *kwargs) { MagickBooleanType res; double *arguments = NULL; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "iOO", &method, &argv, &bestfit)) return NULL; if (!PySequence_Check(argv)) { PyErr_SetString(PyExc_TypeError, "arguments must be a sequence"); return NULL; } @@ -658,6 +720,8 @@ static PyObject * magick_Image_trim(magick_Image *self, PyObject *args, PyObject *kwargs) { double fuzz; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "d", &fuzz)) return NULL; if (!MagickTrimImage(self->wand, fuzz)) return magick_set_exception(self->wand); @@ -672,6 +736,8 @@ static PyObject * magick_Image_thumbnail(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t width, height; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "nn", &width, &height)) return NULL; if (!MagickThumbnailImage(self->wand, width, height)) return magick_set_exception(self->wand); @@ -686,6 +752,8 @@ static PyObject * magick_Image_crop(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t width, height, x, y; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "nnnn", &width, &height, &x, &y)) return NULL; if (!MagickCropImage(self->wand, width, height, x, y)) return magick_set_exception(self->wand); @@ -701,6 +769,8 @@ magick_Image_set_border_color(magick_Image *self, PyObject *args, PyObject *kwar PyObject *obj; magick_PixelWand *pw; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "O!", &magick_PixelWandType, &obj)) return NULL; pw = (magick_PixelWand*)obj; if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return NULL; } @@ -719,6 +789,8 @@ magick_Image_rotate(magick_Image *self, PyObject *args, PyObject *kwargs) { magick_PixelWand *pw; double degrees; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "O!d", &magick_PixelWandType, &obj, °rees)) return NULL; pw = (magick_PixelWand*)obj; if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return NULL; } @@ -735,6 +807,8 @@ static PyObject * magick_Image_set_page(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t width, height, x, y; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "nnnn", &width, &height, &x, &y)) return NULL; if (!MagickSetImagePage(self->wand, width, height, x, y)) return magick_set_exception(self->wand); @@ -749,6 +823,8 @@ static PyObject * magick_Image_set_compression_quality(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t quality; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "n", &quality)) return NULL; if (!MagickSetImageCompressionQuality(self->wand, quality)) return magick_set_exception(self->wand); @@ -767,6 +843,8 @@ magick_Image_has_transparent_pixels(magick_Image *self, PyObject *args, PyObject size_t r, c, width, height; double alpha; + NULL_CHECK(NULL) + height = MagickGetImageHeight(self->wand); pi = NewPixelIterator(self->wand); @@ -790,6 +868,8 @@ magick_Image_has_transparent_pixels(magick_Image *self, PyObject *args, PyObject static PyObject * magick_Image_normalize(magick_Image *self, PyObject *args, PyObject *kwargs) { + NULL_CHECK(NULL) + if (!MagickNormalizeImage(self->wand)) return magick_set_exception(self->wand); Py_RETURN_NONE; @@ -804,6 +884,8 @@ magick_Image_add_border(magick_Image *self, PyObject *args, PyObject *kwargs) { PyObject *obj; magick_PixelWand *pw; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "O!nn", &magick_PixelWandType, &obj, &dx, &dy)) return NULL; pw = (magick_PixelWand*)obj; if (!IsPixelWand(pw->wand)) { PyErr_SetString(PyExc_TypeError, "Invalid PixelWand"); return NULL; } @@ -820,6 +902,8 @@ static PyObject * magick_Image_sharpen(magick_Image *self, PyObject *args, PyObject *kwargs) { double radius, sigma; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "dd", &radius, &sigma)) return NULL; if (!MagickSharpenImage(self->wand, radius, sigma)) return magick_set_exception(self->wand); @@ -835,6 +919,9 @@ magick_Image_quantize(magick_Image *self, PyObject *args, PyObject *kwargs) { Py_ssize_t number_colors, treedepth; int colorspace; PyObject *dither, *measure_error; + + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "ninOO", &number_colors, &colorspace, &treedepth, &dither, &measure_error)) return NULL; @@ -848,6 +935,8 @@ magick_Image_quantize(magick_Image *self, PyObject *args, PyObject *kwargs) { static PyObject * magick_Image_despeckle(magick_Image *self, PyObject *args, PyObject *kwargs) { + NULL_CHECK(NULL) + if (!MagickDespeckleImage(self->wand)) return magick_set_exception(self->wand); Py_RETURN_NONE; @@ -857,6 +946,8 @@ magick_Image_despeckle(magick_Image *self, PyObject *args, PyObject *kwargs) { // Image.type {{{ static PyObject * magick_Image_type_getter(magick_Image *self, void *closure) { + NULL_CHECK(NULL) + return Py_BuildValue("n", MagickGetImageType(self->wand)); } @@ -864,6 +955,8 @@ static int magick_Image_type_setter(magick_Image *self, PyObject *val, void *closure) { int type; + NULL_CHECK(-1) + if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete image type"); return -1; @@ -885,8 +978,21 @@ magick_Image_type_setter(magick_Image *self, PyObject *val, void *closure) { // }}} +// Image.destroy {{{ + +static PyObject * +magick_Image_destroy(magick_Image *self, PyObject *args, PyObject *kwargs) { + NULL_CHECK(NULL) + self->wand = DestroyMagickWand(self->wand); + Py_RETURN_NONE; +} +// }}} + // Image attr list {{{ static PyMethodDef magick_Image_methods[] = { + {"destroy", (PyCFunction)magick_Image_destroy, METH_VARARGS, + "Destroy the underlying ImageMagick Wand. WARNING: After using this method, all methods on this object will raise an exception."}, + {"load", (PyCFunction)magick_Image_load, METH_VARARGS, "Load an image from a byte buffer (string)" }, @@ -1001,6 +1107,7 @@ static PyGetSetDef magick_Image_getsetters[] = { // }}} + static PyTypeObject magick_ImageType = { // {{{ PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ @@ -1053,6 +1160,9 @@ magick_Image_compose(magick_Image *self, PyObject *args, PyObject *kwargs) magick_Image *src; MagickBooleanType res = MagickFalse; + NULL_CHECK(NULL) + + if (!PyArg_ParseTuple(args, "O!nnO", &magick_ImageType, &img, &left, &top, &op_)) return NULL; src = (magick_Image*)img; if (!IsMagickWand(src->wand)) {PyErr_SetString(PyExc_TypeError, "Not a valid ImageMagick wand"); return NULL;} @@ -1078,6 +1188,8 @@ magick_Image_copy(magick_Image *self, PyObject *args, PyObject *kwargs) PyObject *img; magick_Image *src; + NULL_CHECK(NULL) + if (!PyArg_ParseTuple(args, "O!", &magick_ImageType, &img)) return NULL; src = (magick_Image*)img; if (!IsMagickWand(src->wand)) {PyErr_SetString(PyExc_TypeError, "Not a valid ImageMagick wand"); return NULL;} @@ -1153,3 +1265,4 @@ initmagick(void) MagickWandGenesis(); } // }}} +