summaryrefslogtreecommitdiff
path: root/ethumb/ethumb.client.pyx
blob: 3c0a6c05ffca6c9d03b99c1b13740c5274a20f8b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# Copyright (C) 2009 by ProFUSION embedded systems
#
# This file is part of Python-Ethumb.
#
# Python-Ethumb is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# Python-Ethumb is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this Python-Ethumb.  If not, see <http://www.gnu.org/licenses/>.

cimport ethumb.python as python
import traceback

__extra_epydoc_fields__ = (
    ("parm", "Parameter", "Parameters"), # epydoc don't support pyrex properly
    )

def shutdown():
    ethumb_client_shutdown()

def init():
    return ethumb_client_init()

cdef void _connect_cb(Ethumb_Client *client, Eina_Bool success, void *data) with gil:
    cdef Client self = <Client>data
    s = bool(<unsigned int>success)
    try:
        func, args, kargs = self._on_connect_callback
        func(self, s, *args, **kargs)
    except Exception, e:
        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(Ethumb_Client *client, void *data) with gil:
    cdef Client self = <Client>data
    if self._on_server_die_callback is not None:
        try:
            func, args, kargs = self._on_server_die_callback
            func(self, *args, **kargs)
        except Exception, e:
            traceback.print_exc()

    if self.obj != NULL:
        ethumb_client_disconnect(self.obj)
        self.obj = NULL
    self._on_server_die_callback = None


cdef void _generated_cb(int id, char *file, char *key, char *thumb_path, char *thumb_key, Eina_Bool success, void *data) 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 = success != 0
    if success:
        s = True
    else:
        s = False
    try:
        func(id, f, k, tp, tk, s, *args, **kargs)
    except Exception, e:
        traceback.print_exc()

cdef void _generated_cb_free_data(void *data) with gil:
    obj = <object>data
    python.Py_DECREF(obj)

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(char *mystr):
    if mystr != NULL:
        return mystr

cdef class Client:
    """Client for Ethumbd server.

    This client is the recommended way to generate thumbnails with
    Ethumb. All you have to do is create a client instance, wait it to
    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.
     - set various parameters, like format and size.
     - loop on original files:
       - C{c.file_set(file)}
       - C{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
    L{disconnect()} function.
    """

    def __init__(self, func, *args, **kargs):
        """Ethumb Client constructor.

        Server is ready to receive requests just after B{func} is
        called back with C{status == True}.

        @parm: B{func} function to call when connection with server is
           established. Function signature is:
              C{func(client, status, *args, **kargs)}
           with status being True for successful connection or False
           on error.

        @raise TypeError: if B{func} is not callable.
        @raise SystemError: if it was not possible to connect to
            server, allocate memory or use DBus.
        """
        if not callable(func):
            raise TypeError("Parameter 'func' must be callable")
        if self.obj == NULL:
            self._on_connect_callback = (func, args, kargs)
            self._on_server_die_callback = None
            self.obj = ethumb_client_connect(_connect_cb, <void*>self, NULL)
            if self.obj == NULL:
                raise SystemError("Error connecting to server.")
            else:
                ethumb_client_on_server_die_callback_set(
                    self.obj, _on_server_die_cb, <void*>self)

    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)
            self.obj = NULL
        self._on_connect_callback = None
        self._on_server_die_callback = None

    def __str__(self):
        f, k = self.file
        tf, tk = self.thumb_path
        w, h = self.size

        format = ("FDO", "JPEG", "EET")[self.format]
        aspect = ("KEEP", "IGNORE", "CROP")[self.aspect]
        if self.aspect == 2:
            aspect = "CROP[%f, %f]" % self.crop
        return ("%s(file=(%r, %r), thumb=(%r, %r), exists=%s, size=%dx%d, "
                "format=%s, aspect=%s, quality=%d, compress=%d, "
                "directory=%r, category=%r)") % \
                (self.__class__.__name__, f, k, tf, tk, self.exists(),
                 w, h, format, aspect, self.quality, self.compress,
                 self.directory, self.category)

    def __repr__(self):
        f, k = self.file
        tf, tk = self.thumb_path
        return "%s(obj=%#x, file=(%r, %r), thumb=(%r, %r), exists=%s)" % \
            (self.__class__.__name__, f, k, tf, tk, self.exists())

    def on_server_die_callback_set(self, func, *args, **kargs):
        """Function to call when server dies.

        When server is dead there is nothing to do with this client
        anymore, just create a new one and start over, hope that
        server could be started and you could generate more
        thumbnails.

        @parm: B{func} function to call when server dies. Signature:
           C{func(client, *args, **kargs)

        @raise TypeError: if B{func} is not callable or None.
        """
        if func is None:
            self._on_server_die_callback = None
        elif callable(func):
            self._on_server_die_callback = (func, args, kargs)
        else:
            raise TypeError("Parameter 'func' must be callable or None")

    def fdo_set(self, int s):
        """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).

        @parm: B{s} size identifier, either ETHUMB_THUMB_NORMAL (0) or
           ETHUMB_THUMB_LARGE.

        @see: L{size_set()}, L{format_set()}, L{aspect_set()}, L{crop_set()},
           L{category_set()}, L{directory_set()}.
        """
        ethumb_client_fdo_set(self.obj, s)

    def size_set(self, int w, int h):
        """Configure future request to use custom size.

        @parm: B{w} width, default is 128.
        @parm: B{h} height, default is 128.
        """
        ethumb_client_size_set(self.obj, w, h)

    def size_get(self):
        """Get current size being used by requests.

        @rtype: tuple of int.
        """
        cdef int w, h
        ethumb_client_size_get(self.obj, &w, &h)
        return (w, h)

    property size:
        def __set__(self, value):
            cdef int w, h
            w, h = value
            self.size_set(w, h)

        def __get__(self):
            return self.size_get()

    def format_set(self, int f):
        """Configure format to use for future requests.

        @parm: B{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 format_get(self):
        """Get current format in use for requests.

        @rtype: int
        """
        return ethumb_client_format_get(self.obj)

    property format:
        def __set__(self, value):
            self.format_set(value)

        def __get__(self):
            return self.format_get()

    def aspect_set(self, int a):
        """Configure aspect mode to use.

        If aspect is kept (ETHUMB_THUMB_KEEP_ASPECT), then image will
        be rescaled so the largest dimension is not bigger than it's
        specified size (see L{size_get()}) and the other dimension is
        resized in the same proportion. Example: size is 256x256,
        image is 1000x500, resulting thumbnail is 256x128.

        If aspect is ignored (ETHUMB_THUMB_IGNORE_ASPECT), then image
        will be distorted to match required thumbnail size. Example:
        size is 256x256, image is 1000x500, resulting thumbnail is
        256x256.

        If crop is required (ETHUMB_THUMB_CROP), then image will be
        cropped so the smallest dimension is not bigger than its
        specified size (see L{size_get()}) and the other dimension
        will overflow, not being visible in the final image. How it
        will overflow is speficied by L{crop_set()}
        alignment. Example: size is 256x256, image is 1000x500, crop
        alignment is 0.5, 0.5, resulting thumbnail is 256x256 with 250
        pixels from left and 250 pixels from right being lost, that is
        just the 500x500 central pixels of image will be considered
        for scaling.

        @parm: B{a} aspect mode identifier, either ETHUMB_THUMB_KEEP_ASPECT (0),
           ETHUMB_THUMB_IGNORE_ASPECT (1) or ETHUMB_THUMB_CROP (2).

        """
        ethumb_client_aspect_set(self.obj, a)

    def aspect_get(self):
        """Get current aspect in use for requests.

        @rtype: int
        """
        return ethumb_client_aspect_get(self.obj)

    property aspect:
        def __set__(self, value):
            self.aspect_set(value)

        def __get__(self):
            return self.aspect_get()

    def crop_set(self, float x, float y):
        """Configure crop alignment in use for future requests.

        @parm: B{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.
        @parm: B{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)

    def crop_get(self):
        """Get current crop alignment in use for requests.

        @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
            x, y = value
            self.crop_set(x, y)

        def __get__(self):
            return self.crop_get()

    def quality_set(self, int quality):
        """Configure quality to be used in thumbnails.

        @parm: B{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)

    property quality:
        def __set__(self, value):
            self.quality_set(value)

        def __get__(self):
            return self.quality_get()

    def compress_set(self, int compress):
        """Configure compression level used in requests.

        @parm: B{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 __get__(self):
            return self.compress_get()

    def directory_set(self, path):
        """Configure where to store thumbnails in future requests.

        Note that this is the base, a category is added to this path
        as a sub directory.

        @parm: B{path} base directory where to store
           thumbnails. Default is ~/.thumbnails
        """
        ethumb_client_dir_path_set(self.obj, str_to_c(path))

    def directory_get(self):
        """Get current base directory to store thumbnails.

        @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 __get__(self):
            return self.directory_get()

    def category_set(self, category):
        """Category directory to store thumbnails.

        @parm: B{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))

    def category_get(self):
        """Get current category sub directory to store thumbnails.

        @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 __get__(self):
            return self.category_get()

    def frame_set(self, file, group, swallow):
        """Set frame to apply to future 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. Remeber it is bad to modify the original
        contents of thumbnails, but sometimes it's useful to have it
        composited and avoid runtime overhead.

        @parm: B{file} file path to edje.
        @parm: B{group} group inside edje to use.
        @parm: B{swallow} name of swallow part.
        """
        cdef char *f, *g, *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 L{thumb_set()}
        specifications. This is done to avoid one using the last thumb
        path for new images.

        @parm: B{path} path to thumbnail subject.
        @parm: B{key} path to key inside B{path}, this is used to
           generate thumbnail of edje groups or images inside EET.
        """
        cdef char *p, *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 char *p, *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 __get__(self):
            return self.file_get()

    def file_free(self):
        """Zero/Reset file parameters.

        This call will reset file and thumb specifications.

        @see: L{file_set()} and L{thumb_set()}
        """
        ethumb_client_file_free(self.obj)

    def thumb_set(self, path, key=None):
        """Set thumbnail path and key.

        Note that these parameters are forgotten (reset) after
        L{file_set()}.

        @parm: B{path} path to generated thumbnail to use, this is an
           absolute path to file, overriding directory and category.
        @parm: B{key} path to key inside B{path}, this is used to
           generate thumbnail inside EET files.
        """
        cdef char *p, *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 L{thumb_set()} was called, it will
        auto generate path based on existing parameters such as
        directory, category and others.

        @rtype: tuple of str
        """
        cdef char *p, *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)

    def video_fps_set(self, int fps):
        ethumb_client_video_fps_set(self.obj, fps)

    # document_page
    def document_page_set(self, int page):
        ethumb_client_document_page_set(self.obj, page)

    def exists(self):
        """Checks if thumbnail already exists.

        If you want to avoid regenerating thumbnails, check if they
        already exist with this function.
        """
        return bool(ethumb_client_thumb_exists(self.obj))

    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 L{exists()} before calling.

        @parm: B{func} function to call on generation completion, even
           if failed or succeeded. Signature is:
              C{func(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
           L{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 B{func} is not callable.
        @raise SystemError: if could not generate thumbnail, probably
           no L{file_set()}.

        @see: L{cancel()}, L{clear()}, L{exists()}
        """
        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:
            python.Py_INCREF(targs)
            return r
        else:
            raise SystemError("could not generate thumbnail. "
                              "Did you set the file?")

    def cancel(self, int id):
        """Cancel thumbnail request.

        Calling this function aborts thumbnail generation and B{func}
        given to L{generate()} will not be called!

        @parm: B{id} identifier returned by L{generate()}
        """
        ethumb_client_queue_remove(self.obj, id, NULL, NULL)

    def clear(self):
        """Clear request queue.

        This will abort all existing requests, no B{func} given to
        L{generate()} will be called.

        Same as calling L{cancel()} in all exising requests.
        """
        ethumb_client_queue_clear(self.obj)

init()