davemds pushed a commit to branch master. http://git.enlightenment.org/bindings/python/python-efl.git/commit/?id=17df9e69aa34477574f80e1599eca1721a3e6dfd
commit 17df9e69aa34477574f80e1599eca1721a3e6dfd Author: Dave Andreoli <d...@gurumeditation.it> Date: Sun Jan 3 18:50:33 2016 +0100 Revamp old and broken EthumbClient module This was not functional at all, so I broke the API "a bit" --- doc/ethumb/class-ethumb_client.rst | 6 + doc/ethumb/ethumb.rst | 1 + doc/ethumb/module-ethumb.rst | 14 +- doc/ethumb/module-ethumb_client.rst | 3 + efl/ethumb/efl.ethumb.pyx | 14 +- efl/ethumb/efl.ethumb_client.pyx | 835 ++++++++++++++++++++---------------- examples/ethumb/ethumb_client.py | 110 +++++ include/efl.ethumb_client.pxd | 52 ++- setup.py | 5 +- 9 files changed, 651 insertions(+), 389 deletions(-) diff --git a/doc/ethumb/class-ethumb_client.rst b/doc/ethumb/class-ethumb_client.rst new file mode 100644 index 0000000..71f4d60 --- /dev/null +++ b/doc/ethumb/class-ethumb_client.rst @@ -0,0 +1,6 @@ +.. currentmodule:: efl.ethumb_client + +:class:`efl.ethumb_client.EthumbClient` Class +============================================= + +.. autoclass:: efl.ethumb_client.EthumbClient diff --git a/doc/ethumb/ethumb.rst b/doc/ethumb/ethumb.rst index 9f33fe0..35e19f8 100644 --- a/doc/ethumb/ethumb.rst +++ b/doc/ethumb/ethumb.rst @@ -44,6 +44,7 @@ API Reference :titlesonly: module-ethumb.rst + module-ethumb_client.rst Inheritance diagram diff --git a/doc/ethumb/module-ethumb.rst b/doc/ethumb/module-ethumb.rst index 2891d11..eb94bea 100644 --- a/doc/ethumb/module-ethumb.rst +++ b/doc/ethumb/module-ethumb.rst @@ -1,11 +1,3 @@ -diff --git doc/ethumb/ethumb_module.rst doc/ethumb/ethumb_module.rst -deleted file mode 100644 -index a7189f9..0000000 ---- doc/ethumb/ethumb_module.rst -+++ /dev/null -@@ -1,5 +0,0 @@ --:mod:`efl.ethumb` Module --======================== -- --.. automodule:: efl.ethumb -- :exclude-members: PyEthumb + +.. automodule:: efl.ethumb + :exclude-members: Ethumb diff --git a/doc/ethumb/module-ethumb_client.rst b/doc/ethumb/module-ethumb_client.rst new file mode 100644 index 0000000..81f64c7 --- /dev/null +++ b/doc/ethumb/module-ethumb_client.rst @@ -0,0 +1,3 @@ + +.. automodule:: efl.ethumb_client + :exclude-members: EthumbClient diff --git a/efl/ethumb/efl.ethumb.pyx b/efl/ethumb/efl.ethumb.pyx index d9f3d5b..6cc6ce8 100644 --- a/efl/ethumb/efl.ethumb.pyx +++ b/efl/ethumb/efl.ethumb.pyx @@ -338,7 +338,7 @@ cdef class Ethumb(object): const char *group const char *swallow ethumb_frame_get(self.obj, &theme, &group, &swallow) - return tuple(_ctouni(theme), _ctouni(group), _ctouni(swallow)) + return _ctouni(theme), _ctouni(group), _ctouni(swallow) # destination thumb properties property thumb_path: @@ -412,9 +412,7 @@ cdef class Ethumb(object): <const char *>cat if cat is not None else NULL) def __get__(self): - cdef const char *cat - cat = ethumb_thumb_category_get(self.obj) - return _ctouni(cat) + return _ctouni(ethumb_thumb_category_get(self.obj)) property thumb_fdo: """ Set a standard FDO thumbnail size @@ -441,11 +439,9 @@ cdef class Ethumb(object): ethumb_thumb_size_set(self.obj, w, h) def __get__(self): - cdef: - int w - int h + cdef int w, h ethumb_thumb_size_get(self.obj, &w, &h) - return tuple(w, h) + return w, h property thumb_format: """ The fileformat for the thumbnails. @@ -511,7 +507,7 @@ cdef class Ethumb(object): float x float y ethumb_thumb_crop_align_get(self.obj, &x, &y) - return tuple(x, y) + return x, y property thumb_quality: """ The thumbnail compression quality. diff --git a/efl/ethumb/efl.ethumb_client.pyx b/efl/ethumb/efl.ethumb_client.pyx index 569a176..f36132c 100644 --- a/efl/ethumb/efl.ethumb_client.pyx +++ b/efl/ethumb/efl.ethumb_client.pyx @@ -14,19 +14,132 @@ # # You should have received a copy of the GNU Lesser General Public License # along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. +""" -from cpython cimport Py_INCREF, Py_DECREF +:mod:`efl.ethumb_client` Module +############################### + + +Classes +======= + +.. toctree:: + + class-ethumb_client.rst + + +Enumerations +============ + +.. _Ethumb_Client_Thumb_FDO_Size: + +Ethumb_Thumb_FDO_Size +--------------------- + +.. data:: ETHUMB_THUMB_NORMAL + + 128x128 as defined by FreeDesktop.Org standard + +.. data:: ETHUMB_THUMB_LARGE + + 256x256 as defined by FreeDesktop.Org standard + + +.. _Ethumb_Client_Thumb_Format: + +Ethumb_Thumb_Format +------------------- + +.. data:: ETHUMB_THUMB_FDO + + PNG as defined by FreeDesktop.Org standard. + +.. data:: ETHUMB_THUMB_JPEG + + JPEGs are often smaller and faster to read/write. + +.. data:: ETHUMB_THUMB_EET + + EFL's own storage system, supports key parameter. + + +.. _Ethumb_Client_Thumb_Aspect: + +Ethumb_Thumb_Aspect +------------------- + +.. data:: ETHUMB_THUMB_KEEP_ASPECT + + Keep original proportion between width and height + +.. data:: ETHUMB_THUMB_IGNORE_ASPECT + + Ignore aspect and foce it to match thumbnail's width and height + +.. data:: ETHUMB_THUMB_CROP + + keep aspect but crop (cut) the largest dimension + + +.. _Ethumb_Client_Thumb_Orientation: + +Ethumb_Thumb_Orientation +------------------------ + +.. data:: ETHUMB_THUMB_ORIENT_NONE + + Keep orientation as pixel data is + +.. data:: ETHUMB_THUMB_ROTATE_90_CW + + Rotate 90° clockwise + +.. data:: ETHUMB_THUMB_ROTATE_180 + + Rotate 180° + +.. data:: ETHUMB_THUMB_ROTATE_90_CCW + + Rotate 90° counter-clockwise + +.. data:: ETHUMB_THUMB_FLIP_HORIZONTAL + + Flip horizontally + +.. data:: ETHUMB_THUMB_FLIP_VERTICAL + + Flip vertically + +.. data:: ETHUMB_THUMB_FLIP_TRANSPOSE + + Transpose + +.. data:: ETHUMB_THUMB_FLIP_TRANSVERSE + + Transverse + +.. data:: ETHUMB_THUMB_ORIENT_ORIGINAL + + Use orientation from metadata (EXIF-only currently) + + +Module level functions +====================== + +""" + +from cpython cimport Py_INCREF, Py_DECREF, PyUnicode_AsUTF8String from libc.stdint cimport uintptr_t + import traceback +import atexit -def shutdown(): - ethumb_client_shutdown() +from efl.utils.conversions cimport _ctouni, _touni +from efl.ethumb_client cimport Ethumb_Thumb_Orientation -def init(): - return ethumb_client_init() cdef void _connect_cb(void *data, Ethumb_Client *client, Eina_Bool success) with gil: - cdef Client self = <Client>data + cdef EthumbClient self = <EthumbClient>data s = bool(success) try: func, args, kargs = self._on_connect_callback @@ -34,14 +147,8 @@ cdef void _connect_cb(void *data, Ethumb_Client *client, Eina_Bool success) with except Exception: traceback.print_exc() - if not s and self.obj != NULL: - ethumb_client_disconnect(self.obj) - self.obj = NULL - self._on_connect_callback = None - - cdef void _on_server_die_cb(void *data, Ethumb_Client *client) with gil: - cdef Client self = <Client>data + cdef EthumbClient self = <EthumbClient>data if self._on_server_die_callback is not None: try: func, args, kargs = self._on_server_die_callback @@ -49,22 +156,15 @@ cdef void _on_server_die_cb(void *data, Ethumb_Client *client) with gil: except Exception: traceback.print_exc() - if self.obj != NULL: - ethumb_client_disconnect(self.obj) - self.obj = NULL - self._on_server_die_callback = None - + self.disconnect() cdef void _generated_cb(void *data, Ethumb_Client *client, int id, const char *file, const char *key, const char *thumb_path, const char *thumb_key, Eina_Bool success) with gil: obj = <object>data (self, func, args, kargs) = obj - f = str_from_c(file) - k = str_from_c(key) - tp = str_from_c(thumb_path) - tk = str_from_c(thumb_key) - s = bool(success != 0) + status = bool(success != 0) try: - func(self, id, f, k, tp, tk, s, *args, **kargs) + func(self, id, _ctouni(file), _ctouni(key), _ctouni(thumb_path), + _ctouni(thumb_key), status, *args, **kargs) except Exception: traceback.print_exc() @@ -75,21 +175,28 @@ cdef void _generated_cb_free_data(void *data) with gil: cdef void _thumb_exists_cb(void *data, Ethumb_Client *client, Ethumb_Exists *thread, Eina_Bool exists) with gil: #TODO print("Not implemented") - #pass -cdef char *str_to_c(object s): - cdef char *mystr - if s is None: - mystr = NULL - else: - mystr = s - return mystr -cdef object str_from_c(const char *mystr): - if mystr != NULL: - return mystr +def init(): + """ Initialize the ethumb_client library. + + .. note:: You never need to call this function, it is automatically called + on module import. + + """ + return ethumb_client_init() + +def shutdown(): + """ Shutdown the ethumb_client library. + + .. note:: You never need to call this function, it is automatically called + at exit. -cdef class Client: + """ + ethumb_client_shutdown() + + +cdef class EthumbClient: """ Client for Ethumbd server. @@ -99,22 +206,20 @@ cdef class Client: be connected to server, configure thumbnail parameters and then start feed it with file_set(), exists() generate(). Basic steps are: - - instantiate Client, wait for func to be called with success. + - instantiate EthumbClient, wait for func to be called with success. - set various parameters, like format and size. - loop on original files: - ``c.file_set(file)`` - ``if not c.exists(): c.generate(generated_cb)`` - When the last reference to client is released, server is - automatically disconnected. Since callback may contain references - to server itself, it is recommended explicit call to - :py:func:`disconnect` function. + It is recommended explicit call to :py:func:`disconnect` function when + you don't need the thumbnailer anymore. """ def __init__(self, func, *args, **kargs): - """Client(...) + """ EthumbClient thumbnail generator. :param func: function to call when connection with server is established. @@ -147,15 +252,12 @@ cdef class Client: ethumb_client_on_server_die_callback_set( self.obj, _on_server_die_cb, <void*>self, NULL) - def __dealloc__(self): - if self.obj != NULL: - ethumb_client_disconnect(self.obj) - def disconnect(self): """Explicitly request server disconnection. After this call object becomes shallow, that is operations will be void. + """ if self.obj != NULL: ethumb_client_disconnect(self.obj) @@ -173,14 +275,13 @@ cdef class Client: if self.aspect == 2: aspect = "CROP[%f, %f]" % self.crop return ( - "<%s(obj=%#x, file=(%r, %r), thumb=(%r, %r), exists=%s, " + "<%s(obj=%#x, file=(%r, %r), thumb=(%r, %r), " "size=%dx%d, format=%s, aspect=%s, quality=%d, compress=%d, " - "directory=%r, category=%r)>" + "dir_path=%r, category=%r)>" ) % ( type(self).__name__, <uintptr_t><void *>self, f, k, - tf, tk, self.thumb_exists(), - w, h, format, aspect, self.quality, self.compress, - self.directory, self.category + tf, tk, w, h, format, aspect, self.quality, self.compress, + self.dir_path, self.category ) def on_server_die_callback_set(self, func, *args, **kargs): @@ -205,72 +306,221 @@ cdef class Client: else: raise TypeError("Parameter 'func' must be callable or None") - def fdo_set(self, int s): - """Configure future requests to use FreeDesktop.Org preset. + def thumb_exists(self, callback=None, *args, **kwargs): + """Checks if thumbnail already exists. - This is a preset to provide freedesktop.org (fdo) standard - compliant thumbnails. That is, files are stored as JPEG under - ~/.thumbnails/SIZE, with size being either normal (128x128) or - large (256x256). + If you want to avoid regenerating thumbnails, check if they + already exist with this function. - :param s: size identifier, either ETHUMB_THUMB_NORMAL (0) or - ETHUMB_THUMB_LARGE. + """ + cdef Ethumb_Client_Thumb_Exists_Cb cb = NULL + cdef Ethumb_Exists *res - .. seealso:: :py:func:`size_set`, :py:func:`format_set`, :py:func:`aspect_set`, :py:func:`crop_set`, - :py:func:`category_set`, :py:func:`directory_set`. + if callback: + if not callable(callback): + raise TypeError("callback is not callable") + cb = _thumb_exists_cb + + data = (args, kwargs) + res = ethumb_client_thumb_exists(self.obj, cb, <void *>data) + + return False + #TODO: handle return value + + def generate(self, func, *args, **kargs): + """Ask EThumb server to generate the specified thumbnail. + + Thumbnail generation is asynchronous and depend on ecore main + loop running. Given function will be called back with + generation status if True is returned by this call. If False + is returned, given function will not be called. + + Existing thumbnails will be overwritten with this call. Check + if they already exist with :py:func:`exists` before calling. + + :param func: function to call on generation completion, even + if failed or succeeded. Signature is:: + + func(self, id, file, key, thumb_path, thumb_key, status, *args, **kargs) + + with status being True for successful generation or + False on failure. + + :return: request identifier. Request can be canceled calling + :py:func:`cancel` with given id. If an identifier is returned (>= + 0), then func is guaranteed to be called unless it is + explicitly canceled. + + :raise TypeError: if **func** is not callable. + :raise SystemError: if could not generate thumbnail, probably + no :py:func:`file_set`. + + .. seealso:: :py:func:`cancel`, :py:func:`clear`, :py:func:`exists` """ - ethumb_client_fdo_set(self.obj, s) + if not callable(func): + raise TypeError("func must be callable") + + targs = (self, func, args, kargs) + r = ethumb_client_generate(self.obj, _generated_cb, <void*>targs, + _generated_cb_free_data) + if r >= 0: + Py_INCREF(targs) + return r + else: + raise SystemError("could not generate thumbnail. " + "Did you set the file?") - def size_set(self, int w, int h): - """Configure future request to use custom size. + def generate_cancel(self, int id): + """Cancel thumbnail request given its id. - :param w: width, default is 128. - :param h: height, default is 128. + Calling this function aborts thumbnail generation and **func** + given to :py:func:`generate` will not be called! + + :param id: identifier returned by :py:func:`generate` """ - ethumb_client_size_set(self.obj, w, h) + ethumb_client_generate_cancel(self.obj, id, NULL, NULL, NULL) - def size_get(self): - """Get current size being used by requests. + def generate_cancel_all(self): + """Clear request queue, canceling all generation requests. + + This will abort all existing requests, no **func** given to + :py:func:`generate` will be called. - :rtype: tuple of int. + Same as calling :py:func:`cancel` in all exising requests. """ - cdef int w, h - ethumb_client_size_get(self.obj, &w, &h) - return (w, h) + ethumb_client_generate_cancel_all(self.obj) - property size: + ## source file setup + property file: + """ The file to thumbnail. + + This is a tuple of 2 strings: ``path`` and ``key``. + + For convenience you can also assign a single string value (``path``), + ignoring the key. + + :type: **str** or (**str**, **str**) + + :param path: path to thumbnail subject. + :param key: path to key inside **path**, this is used to + generate thumbnail of edje groups or images inside EET. + + :raise RuntimeError: on failure setting the property + + .. note:: setting this property will reset other thumbnail + specifications. This is done to avoid one using the last thumb + path for new images. + + """ def __set__(self, value): - cdef int w, h - w, h = value - self.size_set(w, h) + if isinstance(value, tuple): + path, key = value + else: + path, key = value, None + if isinstance(path, unicode): path = PyUnicode_AsUTF8String(path) + if isinstance(key, unicode): key = PyUnicode_AsUTF8String(key) + if ethumb_client_file_set(self.obj, + <const char *>path if path is not None else NULL, + <const char *>key if key is not None else NULL) == 0: + raise RuntimeError("Cannot set file") def __get__(self): - return self.size_get() + cdef: + const char *path + const char *key + ethumb_client_file_get(self.obj, &path, &key) + return (_ctouni(path), _ctouni(key)) + + def file_free(self): + """Zero/Reset file parameters. + + This call will reset file and thumb specifications. + + .. seealso:: :py:func:`file_set` and :py:func:`thumb_set` + """ + ethumb_client_file_free(self.obj) + + property frame: + """ The optional edje file used to generate a frame around the thumbnail + + This will create an edje object that will have image swallowed + in. This can be used to simulate Polaroid or wood frames in + the generated image. Remember it is bad to modify the original + contents of thumbnails, but sometimes it's useful to have it + composited and avoid runtime overhead. - def format_set(self, int f): - """Configure format to use for future requests. + :type: (**str**, **str**, **str**) **writeonly** + + :param file: file path to edje. + :param group: group inside edje to use. + :param swallow: name of swallow part. + + :raise RuntimeError: on failure setting the property - :param f: format identifier to use, either ETHUMB_THUMB_FDO (0), - ETHUMB_THUMB_JPEG (1) or ETHUMB_THUMB_EET (2). Default is FDO. """ - ethumb_client_format_set(self.obj, f) + def __set__(self, tuple value): + theme, group, swallow = value + if isinstance(theme, unicode): theme = PyUnicode_AsUTF8String(theme) + if isinstance(group, unicode): group = PyUnicode_AsUTF8String(group) + if isinstance(swallow, unicode): swallow = PyUnicode_AsUTF8String(swallow) + if ethumb_client_frame_set(self.obj, + <const char *>theme if theme is not None else NULL, + <const char *>group if group is not None else NULL, + <const char *>swallow if swallow is not None else NULL) == 0: + raise RuntimeError("Cannot set frame") + + ## fine tune setup + property fdo: + """ Configure future requests to use FreeDesktop.Org preset. + + This is a preset to provide freedesktop.org (fdo) standard + compliant thumbnails. That is, files are stored as JPEG under + ~/.thumbnails/SIZE, with size being either normal (128x128) or + large (256x256). + + :type: :ref:`Ethumb_Client_Thumb_FDO_Size` **writeonly** - def format_get(self): - """Get current format in use for requests. + .. seealso:: :attr:`size`, :attr:`format`, :attr:`aspect`, + :attr:`crop_align`, :attr:`category`, :attr:`dir_path`. - :rtype: int """ - return ethumb_client_format_get(self.obj) + def __set__(self, Ethumb_Thumb_FDO_Size value): + ethumb_client_fdo_set(self.obj, value) + + property size: + """ The (custom) size of thumbnails. + + :type: (int **w**, int **w**) + + :param w: width, default is 128. + :param h: height, default is 128. + + """ + def __set__(self, tuple value): + w, h = value + ethumb_client_size_set(self.obj, w, h) + + def __get__(self): + cdef int w, h + ethumb_client_size_get(self.obj, &w, &h) + return w, h property format: - def __set__(self, value): - self.format_set(value) + """ The fileformat for the thumbnails. + + Thumbnails are compressed; possible formats are PNG, JPEG and EET. + + :type: :ref:`Ethumb_Client_Thumb_Format` + + """ + def __set__(self, Ethumb_Thumb_Format value): + ethumb_client_format_set(self.obj, value) def __get__(self): - return self.format_get() + return ethumb_client_format_get(self.obj) - def aspect_set(self, int a): - """Configure aspect mode to use. + property aspect: + """ The aspect ratio policy. If aspect is kept (ETHUMB_THUMB_KEEP_ASPECT), then image will be rescaled so the largest dimension is not bigger than it's @@ -294,361 +544,216 @@ cdef class Client: just the 500x500 central pixels of image will be considered for scaling. - :param a: aspect mode identifier, either ETHUMB_THUMB_KEEP_ASPECT (0), - ETHUMB_THUMB_IGNORE_ASPECT (1) or ETHUMB_THUMB_CROP (2). + :type: :ref:`Ethumb_Client_Thumb_Aspect` """ - ethumb_client_aspect_set(self.obj, a) + def __set__(self, Ethumb_Thumb_Aspect value): + ethumb_client_aspect_set(self.obj, value) - def aspect_get(self): - """Get current aspect in use for requests. + def __get__(self): + return ethumb_client_aspect_get(self.obj) - :rtype: int - """ - return ethumb_client_aspect_get(self.obj) + property orientation: + """ The thumbnail rotation or flip. - property aspect: - def __set__(self, value): - self.aspect_set(value) + :type: :ref:`Ethumb_Client_Thumb_Orientation` + + """ + def __set__(self, Ethumb_Thumb_Orientation value): + ethumb_client_orientation_set(self.obj, value) def __get__(self): - return self.aspect_get() + return ethumb_client_orientation_get(self.obj) - def crop_set(self, float x, float y): - """Configure crop alignment in use for future requests. + property crop_align: + """ Crop alignment in use. :param x: horizontal alignment. 0.0 means left side will be - visible or right side is being lost. 1.0 means right - side will be visible or left side is being lost. 0.5 - means just center is visible, both sides will be lost. - Default is 0.5. + visible or right side is being lost. 1.0 means right side + will be visible or left side is being lost. 0.5 means just + center is visible, both sides will be lost. Default is 0.5. :param y: vertical alignment. 0.0 is top visible, 1.0 is - bottom visible, 0.5 is center visible. Default is 0.5 - """ - ethumb_client_crop_align_set(self.obj, x, y) + bottom visible, 0.5 is center visible. Default is 0.5 - def crop_get(self): - """Get current crop alignment in use for requests. + :type: (float **x**, float **y**) - :rtype: tuple of float """ - cdef float x, y - ethumb_client_crop_align_get(self.obj, &x, &y) - return (x, y) - - property crop: - def __set__(self, value): - cdef float x, y + def __set__(self, tuple value): x, y = value - self.crop_set(x, y) + ethumb_client_crop_align_set(self.obj, x, y) def __get__(self): - return self.crop_get() - - def quality_set(self, int quality): - """Configure quality to be used in thumbnails. - - :param quality: value from 0 to 100, default is 80. The - effect depends on the format being used, PNG will not - use it. - """ - ethumb_client_quality_set(self.obj, quality) - - def quality_get(self): - """Get current quality in use for requests. - - :rtype: int - """ - return ethumb_client_quality_get(self.obj) + cdef float x, y + ethumb_client_crop_align_get(self.obj, &x, &y) + return x, y property quality: - def __set__(self, value): - self.quality_set(value) + """ The thumbnail compression quality. - def __get__(self): - return self.quality_get() + Value from 0 to 100, default is 80. The effect depends on the format + being used, PNG will not use it. - def compress_set(self, int compress): - """Configure compression level used in requests. + :type: int - :param compress: value from 0 to 9, default is 9. The effect - depends on the format being used, JPEG will not use it. """ - ethumb_client_compress_set(self.obj, compress) - - def compress_get(self): - """Get current compression level in use for requests. - - :rtype: int - """ - return ethumb_client_compress_get(self.obj) - - property compress: - def __set__(self, value): - self.compress_set(value) + def __set__(self, int value): + ethumb_client_quality_set(self.obj, value) def __get__(self): - return self.compress_get() - - def directory_set(self, path): - """Configure where to store thumbnails in future requests. + return ethumb_client_quality_get(self.obj) - Note that this is the base, a category is added to this path - as a sub directory. + property compress: + """ The thumbnail compression rate. - :param path: base directory where to store - thumbnails. Default is ~/.thumbnails - """ - ethumb_client_dir_path_set(self.obj, str_to_c(path)) + Value from 0 to 9, default is 9. The effect depends on the format being + used, JPEG will not use it. - def directory_get(self): - """Get current base directory to store thumbnails. + :type: int - :rtype: str or None """ - return str_from_c(ethumb_client_dir_path_get(self.obj)) - - property directory: - def __set__(self, value): - self.directory_set(value) + def __set__(self, int value): + ethumb_client_compress_set(self.obj, value) def __get__(self): - return self.directory_get() + return ethumb_client_compress_get(self.obj) - def category_set(self, category): - """Category directory to store thumbnails. + property dir_path: + """ Configure where to store thumbnails in future requests. - :param category: category sub directory to store - thumbnail. Default is either "normal" or "large" for FDO - compliant thumbnails or - WIDTHxHEIGHT-ASPECT[-FRAMED]-FORMAT. It can be a string or - None to use auto generated names. - """ - ethumb_client_category_set(self.obj, str_to_c(category)) + This is the base folder, a category folder is added to this path + as a sub directory. Default is ``~/.thumbnails`` - def category_get(self): - """Get current category sub directory to store thumbnails. + :type: **str** - :rtype: str or None """ - return str_from_c(ethumb_client_category_get(self.obj)) - - property category: - def __set__(self, value): - self.category_set(value) + def __set__(self, path): + if isinstance(path, unicode): path = PyUnicode_AsUTF8String(path) + ethumb_client_dir_path_set(self.obj, + <const char *>path if path is not None else NULL) def __get__(self): - return self.category_get() + cdef const char *path + path = ethumb_client_dir_path_get(self.obj) + return _ctouni(path) - def frame_set(self, file, group, swallow): - """Set frame to apply to future thumbnails. + property category: + """ Category directory to store thumbnails. - This will create an edje object that will have image swallowed - in. This can be used to simulate Polaroid or wood frames in - the generated image. Remember it is bad to modify the original - contents of thumbnails, but sometimes it's useful to have it - composited and avoid runtime overhead. + Category sub directory to store thumbnail. Default is either "normal" + or "large" for FDO compliant thumbnails or + ``WIDTHxHEIGHT-ASPECT[-FRAMED]-FORMAT``. It can be a string or None to + use auto generated names. - :param file: file path to edje. - :param group: group inside edje to use. - :param swallow: name of swallow part. - """ - cdef: - char *f - char *g - char *s - f = str_to_c(file) - g = str_to_c(group) - s = str_to_c(swallow) - return ethumb_client_frame_set(self.obj, f, g, s) - - def file_set(self, path, key=None): - """Set file to thumbnail. - - Calling this function will zero :py:func:`thumb_set` - specifications. This is done to avoid one using the last thumb - path for new images. + :type: **str** - :param path: path to thumbnail subject. - :param key: path to key inside **path**, this is used to - generate thumbnail of edje groups or images inside EET. """ - cdef: - char *p - char *k - p = str_to_c(path) - k = str_to_c(key) - ethumb_client_file_set(self.obj, p, k) - - def file_get(self): - """Get current file to thumbnail. - - :rtype: tuple of str - """ - cdef: - const char *p - const char *k - ethumb_client_file_get(self.obj, &p, &k) - return (str_from_c(p), str_from_c(k)) - - property file: - def __set__(self, value): - p, k = value - self.file_set(p, k) + def __set__(self, cat): + if isinstance(cat, unicode): cat = PyUnicode_AsUTF8String(cat) + ethumb_client_category_set(self.obj, + <const char *>cat if cat is not None else NULL) def __get__(self): - return self.file_get() + return _ctouni(ethumb_client_category_get(self.obj)) - def file_free(self): - """Zero/Reset file parameters. - - This call will reset file and thumb specifications. + property thumb_path: + """ The complete path of the generated thumbnail. - .. seealso:: :py:func:`file_set` and :py:func:`thumb_set` - """ - ethumb_client_file_free(self.obj) + This is a tuple of 2 strings: ``path`` and ``key``. - def thumb_set(self, path, key=None): - """Set thumbnail path and key. + For convenience you can also assign a single string value (``path``), + ignoring the key. - Note that these parameters are forgotten (reset) after - :py:func:`file_set`. + :type: **str** or (**str**, **str**) :param path: path to generated thumbnail to use, this is an absolute path to file, overriding directory and category. :param key: path to key inside **path**, this is used to generate thumbnail inside EET files. - """ - cdef: - const char *p - const char *k - p = str_to_c(path) - k = str_to_c(key) - ethumb_client_thumb_path_set(self.obj, p, k) - def thumb_get(self): - """Get current path and key of thumbnail. - - Note that if no explicit :py:func:`thumb_set` was called, it will - auto generate path based on existing parameters such as - directory, category and others. - - :rtype: tuple of str """ - cdef: - const char *p - const char *k - ethumb_client_thumb_path_get(self.obj, &p, &k) - return (str_from_c(p), str_from_c(k)) - - property thumb_path: def __set__(self, value): - p, k = value - self.thumb_set(p, k) - def __get__(self): - return self.thumb_get() - - def video_time_set(self, float time): - ethumb_client_video_time_set(self.obj, time) - - def video_start_set(self, float start): - ethumb_client_video_start_set(self.obj, start) - - def video_interval_set(self, float interval): - ethumb_client_video_interval_set(self.obj, interval) - - def video_ntimes_set(self, int ntimes): - ethumb_client_video_ntimes_set(self.obj, ntimes) + if isinstance(value, tuple): + path, key = value + else: + path, key = value, None + if isinstance(path, unicode): path = PyUnicode_AsUTF8String(path) + if isinstance(key, unicode): key = PyUnicode_AsUTF8String(key) + ethumb_client_thumb_path_set(self.obj, + <const char *>path if path is not None else NULL, + <const char *>key if key is not None else NULL) - def video_fps_set(self, int fps): - ethumb_client_video_fps_set(self.obj, fps) + def __get__(self): + cdef: + const char *path + const char *key + ethumb_client_thumb_path_get(self.obj, &path, &key) + return (_ctouni(path), _ctouni(key)) - # document_page - def document_page_set(self, int page): - ethumb_client_document_page_set(self.obj, page) + ## video setup + property video_time: + """ The video time (duration) in seconds. - def thumb_exists(self, callback = None, *args, **kwargs): - """Checks if thumbnail already exists. - - If you want to avoid regenerating thumbnails, check if they - already exist with this function. + :type: float (**readonly**) """ - cdef Ethumb_Client_Thumb_Exists_Cb cb = NULL - cdef Ethumb_Exists *res - - if callback: - if not callable(callback): - raise TypeError("callback is not callable") - cb = _thumb_exists_cb + def __set__(self, float value): + ethumb_client_video_time_set(self.obj, value) - data = (args, kwargs) - res = ethumb_client_thumb_exists(self.obj, cb, <void *>data) + property video_start: + """ The start point for video thumbnails. - return False - #TODO: handle return value + :type: float (from 0.0 to 1.0) (**readonly**) - def generate(self, func, *args, **kargs): - """Ask EThumb server to generate the specified thumbnail. - - Thumbnail generation is asynchronous and depend on ecore main - loop running. Given function will be called back with - generation status if True is returned by this call. If False - is returned, given function will not be called. + """ + def __set__(self, float value): + ethumb_client_video_start_set(self.obj, value) - Existing thumbnails will be overwritten with this call. Check - if they already exist with :py:func:`exists` before calling. + property video_interval: + """ The video frame interval, in seconds. - :param func: function to call on generation completion, even - if failed or succeeded. Signature is:: + This is useful for animated thumbnail and will define skip time before + going to the next frame. - func(self, id, file, key, thumb_path, thumb_key, status, *args, **kargs) + .. note:: that video backends might not be able to + precisely skip that amount as it will depend on various + factors, including video encoding. + + :type: float (**readonly**) - with status being True for successful generation or - False on failure. + """ + def __set__(self, float value): + ethumb_client_video_interval_set(self.obj, value) - :return: request identifier. Request can be canceled calling - :py:func:`cancel` with given id. If an identifier is returned (>= - 0), then func is guaranteed to be called unless it is - explicitly canceled. + property video_ntimes: + """ The number of times the video loops (if applicable). - :raise TypeError: if **func** is not callable. - :raise SystemError: if could not generate thumbnail, probably - no :py:func:`file_set`. + :type: int (**readonly**) - .. seealso:: :py:func:`cancel`, :py:func:`clear`, :py:func:`exists` """ - if not callable(func): - raise TypeError("func must be callable") + def __set__(self, int value): + ethumb_client_video_ntimes_set(self.obj, value) - targs = (self, func, args, kargs) - r = ethumb_client_generate(self.obj, _generated_cb, <void*>targs, - _generated_cb_free_data) - if r >= 0: - Py_INCREF(targs) - return r - else: - raise SystemError("could not generate thumbnail. " - "Did you set the file?") + property video_fps: + """ The thumbnail framerate. - def generate_cancel(self, int id): - """Cancel thumbnail request given its id. + Default to 10. - Calling this function aborts thumbnail generation and **func** - given to :py:func:`generate` will not be called! + :type: int (**readonly**) - :param id: identifier returned by :py:func:`generate` """ - ethumb_client_generate_cancel(self.obj, id, NULL, NULL, NULL) + def __set__(self, int value): + ethumb_client_video_fps_set(self.obj, value) - def generate_cancel_all(self): - """Clear request queue, canceling all generation requests. + ## document setup + property document_page: + """ The page number to thumbnail in paged documents. - This will abort all existing requests, no **func** given to - :py:func:`generate` will be called. + :type: int - Same as calling :py:func:`cancel` in all exising requests. """ - ethumb_client_generate_cancel_all(self.obj) + def __set__(self, int value): + ethumb_client_document_page_set(self.obj, value) + init() +atexit.register(shutdown) diff --git a/examples/ethumb/ethumb_client.py b/examples/ethumb/ethumb_client.py new file mode 100755 index 0000000..59b788a --- /dev/null +++ b/examples/ethumb/ethumb_client.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# encoding: utf-8 + +import os +import sys +import logging + +from efl import ecore +from efl import ethumb_client as ethumb + +script_path = os.path.dirname(os.path.abspath(__file__)) + + +# parse command line arguments +if len(sys.argv) != 2: + print("Python EthumbClient test application.\n\n" \ + "usage: ethumb_client.py filename\n") + exit(1) +filename = sys.argv[1] +print("Original file: %s\n" % filename) + + +# setup efl logging (you also need to set EINA_LOG_LEVEL=X) +l = logging.getLogger("efl") +h = logging.StreamHandler() +h.setFormatter(logging.Formatter("EFL %(levelname)s %(message)s")) +l.addHandler(h) +l.setLevel(logging.DEBUG) + +complete_count = 0 + +def generate_cb(client, id, file, key, thumb_path, thumb_key, success, num): + global complete_count + + # thumbnail completed + if success is True: + print("Thumb #%d completed: '%s'" % (num, thumb_path)) + complete_count += 1 + if complete_count >= 6: + print("\nTest Complete!") + ecore.main_loop_quit() + else: + print(" ERROR! aborting.") + ecore.main_loop_quit() + +def connect_cb(client, success): + if success is False: + print("Connection Failed") + ecore.main_loop_quit() + return + else: + print("Connection Successfull") + + # request some thumbnails + print("1. Request a standard FDO thumbnail (default)") + et.file = filename + et.generate(generate_cb, 1) + + print("2. Request a large FDO thumbnail...") + et.file = filename + et.fdo = ethumb.ETHUMB_THUMB_LARGE + et.generate(generate_cb, 2) + + print("3. Request a very large JPEG thumbnail...") + et.file = filename + et.format = ethumb.ETHUMB_THUMB_JPEG + et.size = 512, 512 + et.generate(generate_cb, 3) + + print("4. Request a cropped thumbnail...") + et.file = filename + et.aspect = ethumb.ETHUMB_THUMB_CROP + et.generate(generate_cb, 4) + + print("5. Request a rotated thumbnail") + et.file = filename + et.orientation = ethumb.ETHUMB_THUMB_ROTATE_180 + et.size = 256, 256 + et.aspect = ethumb.ETHUMB_THUMB_KEEP_ASPECT + et.generate(generate_cb, 5) + + print("6. Request a poor quality thumbnail in this folder\n") + et.file = filename + et.orientation = ethumb.ETHUMB_THUMB_ORIENT_NONE + et.thumb_path = os.path.join(script_path, 'big_poor2.jpg') + et.format = ethumb.ETHUMB_THUMB_JPEG + et.size = 512, 512 + et.quality = 10 + et.generate(generate_cb, 6) + + # ...and now wait for the responses + + +def server_die_cb(client): + print("Server die!") + ecore.main_loop_quit() + + +# create a single Ethumb instance +et = ethumb.EthumbClient(connect_cb) +et.on_server_die_callback_set(server_die_cb) + + + +# enter the ecore the main loop +ecore.main_loop_begin() + +# free used resource +et.disconnect() + diff --git a/include/efl.ethumb_client.pxd b/include/efl.ethumb_client.pxd index 70c7ec3..7f99043 100644 --- a/include/efl.ethumb_client.pxd +++ b/include/efl.ethumb_client.pxd @@ -16,17 +16,64 @@ # along with this Python-EFL. If not, see <http://www.gnu.org/licenses/>. from efl.eina cimport Eina_Bool, Eina_Free_Cb -from efl.ethumb cimport Ethumb_Thumb_Orientation + cdef extern from "Ethumb_Client.h": + + #################################################################### + # Enums + # + cpdef enum Ethumb_Thumb_Orientation: + ETHUMB_THUMB_ORIENT_NONE + ETHUMB_THUMB_ROTATE_90_CW + ETHUMB_THUMB_ROTATE_180 + ETHUMB_THUMB_ROTATE_90_CCW + ETHUMB_THUMB_FLIP_HORIZONTAL + ETHUMB_THUMB_FLIP_VERTICAL + ETHUMB_THUMB_FLIP_TRANSPOSE + ETHUMB_THUMB_FLIP_TRANSVERSE + ETHUMB_THUMB_ORIENT_ORIGINAL + ctypedef enum Ethumb_Thumb_Orientation: + pass + + cpdef enum Ethumb_Thumb_FDO_Size: + ETHUMB_THUMB_NORMAL + ETHUMB_THUMB_LARGE + ctypedef enum Ethumb_Thumb_FDO_Size: + pass + + cpdef enum Ethumb_Thumb_Format: + ETHUMB_THUMB_FDO + ETHUMB_THUMB_JPEG + ETHUMB_THUMB_EET + ctypedef enum Ethumb_Thumb_Format: + pass + + cpdef enum Ethumb_Thumb_Aspect: + ETHUMB_THUMB_KEEP_ASPECT + ETHUMB_THUMB_IGNORE_ASPECT + ETHUMB_THUMB_CROP + ctypedef enum Ethumb_Thumb_Aspect: + pass + + #################################################################### + # Structs + # ctypedef struct Ethumb_Client ctypedef struct Ethumb_Exists + + #################################################################### + # Other typedefs + # ctypedef void (*Ethumb_Client_Connect_Cb)(void *data, Ethumb_Client *client, Eina_Bool success) ctypedef void (*Ethumb_Client_Die_Cb)(void *data, Ethumb_Client *client) ctypedef void (*Ethumb_Client_Generate_Cb)(void *data, Ethumb_Client *client, int id, const char *file, const char *key, const char *thumb_path, const char *thumb_key, Eina_Bool success) ctypedef void (*Ethumb_Client_Thumb_Exists_Cb)(void *data, Ethumb_Client *client, Ethumb_Exists *thread, Eina_Bool exists) ctypedef void (*Ethumb_Client_Generate_Cancel_Cb)(void *data, Eina_Bool success) + #################################################################### + # Functions + # int ethumb_client_init() int ethumb_client_shutdown() @@ -90,7 +137,8 @@ cdef extern from "Ethumb_Client.h": void ethumb_client_thumb_async_cancel(Ethumb_Client *client, Ethumb_Client_Async *request) -cdef class Client: + +cdef class EthumbClient: cdef Ethumb_Client *obj cdef object _on_connect_callback cdef object _on_server_die_callback diff --git a/setup.py b/setup.py index 36a50ee..f29884e 100755 --- a/setup.py +++ b/setup.py @@ -351,8 +351,9 @@ if set(("build", "build_ext", "install", "bdist", "sdist")) & set(sys.argv): extra_link_args=ethumb_libs + eina_libs) ext_modules.append(ethumb_ext) - ethumb_client_cflags, ethumb_client_libs = pkg_config( - 'Ethumb_Client', 'ethumb_client', EFL_MIN_VER) + # === Ethumb Client === + ethumb_client_cflags, ethumb_client_libs = pkg_config('Ethumb_Client', + 'ethumb_client', EFL_MIN_VER) ethumb_client_ext = Extension("ethumb_client", ["efl/ethumb/efl.ethumb_client" + module_suffix], include_dirs=['include/'], --