Catégorie : Audio-vidéo     Tags :      0 Commentaire

    Retrouvez cet article dans : Linux Magazine 85

    Freevo est avant tout un logiciel multimédia permettant aisément de visualiser des films, des photos ou d’écouter de la musique. Mais on peut lui adjoindre toutes sortes de greffons pour divers usages. Dans cette optique, on peut programmer ses propres greffons, afin d’étendre ses capacités à d’autres domaines.

    reevo est programmé en Python, ses greffons le sont également. Pour aborder cet article, il faut avoir des connaissances, au moins basiques, en Python. Elles sont considérées comme acquises.
    Nous verrons comment programmer un greffon simple, en utilisant les fonctionnalités intégrées dans la version par défaut de Freevo. Nous prendrons pour exemple l’implémentation d’une fonctionnalité de photocopie, au moyen d’un scanner et d’une imprimante. Il nous faudra afficher un aperçu de l’image à photocopier, afin de vérifier qu’elle est conforme à nos désirs, pour choisir ensuite le type de photocopie à exécuter : couleur, niveaux de gris ou noir et blanc.

    /img-articles/lm/85/art-2/fig-1.jpg

    Fig. 1 : Principe de fonctionnement d’un système de gestion de version

    Où placer le greffon ?

    Étant donné que Freevo est un module du langage Python, ses fichiers sources peuvent être trouvés dans le répertoire Site Packages de Python (sous Debian GNU/Linux, dans le répertoire /usr/lib/python2.3/site-packages/freevo/). C’est là que se trouve le répertoire plugins/, contenant tous les greffons disponibles pour Freevo.
    Le fonctionnement de Freevo nous oblige à placer le greffon dans ce répertoire. Cependant, celui-ci appartenant à l’utilisateur root, nous pourrions être tentés de programmer directement en super-utilisateur ; comme chacun le sait, c’est une très mauvaise habitude (bien que, dans ce cas, seule l’écriture du fichier se ferait en root ; son exécution se faisant toujours avec l’utilisateur qui lance Freevo).
    Pour contourner cette limitation, nous allons créer le greffon dans un répertoire utilisateur, pour ensuite créer un lien symbolique dans le répertoire de Freevo. Nous créons donc le fichier ~/photocopie.py, avec un lien de /usr/lib/python2.3/site-packages/freevo/photocopie.py vers ce fichier.

    Ajouter une entrée au menu principal

    Pour accéder à notre photocopieuse sous Linux, il faut d’abord ajouter une entrée dans le menu principal de Freevo. Profitons-en pour créer un squelette de greffon.

    # -!- coding: utf-8 -!-
    
    import plugin
    from menu import Menu
    
    class PluginInterface(plugin.MainMenuPlugin):
        def __init__(self):
            plugin.MainMenuPlugin.__init__(self)
    
        def items(self, parent):
            return [ SaneMainMenu(parent) ]
    
    class SaneMainMenu(Item):
        def __init__(self, parent):
            Item.__init__(self, parent, skin_type=’image’)
            self.name   = _(‘Photocopier un document’)
            self.menus = [ ]
    
        def actions(self):
            items = [ ( self.create_mainmenu , _(‘Photocopier un document’) ) ]
            return items
    
        def create_mainmenu(self, arg=None, menuw=None):
            sane_menu = Menu(_(‘Photocopieuse’), self.menus)
            menuw.pushmenu(sane_menu)
            menuw.refresh()

    La classe SaneMainMenu est la pièce maîtresse de notre greffon. Elle hérite de Item pour être représentée comme une entrée du menu principal. Pour qu’elle fonctionne, il faut que la fonction actions() existe et retourne une liste d’actions. En ce qui nous concerne, cette liste ne contient qu’une action, qui affiche le menu principal de notre greffon.

    Ce menu principal est une instance de Menu, classe de base dans Freevo. Cette classe prend en argument un titre et une liste d’éléments qui constituent le menu. Pour l’instant, cette liste (self.menus) est vide.
    Lorsque l’entrée du menu principal Photocopier un document est sélectionnée, la première fonction, dans la liste retournée par actions(), est exécutée. Dans notre cas, c’est l’affichage du menu principal (create_mainmenu()).

    Le menu de notre photocopieuse

    La liste self.menus doit contenir des instances de la classe MenuItem. En l’occurrence, voici le code dont nous avons besoin :

    import plugin
    from item import Item
    from menu import Menu, MenuItem
    
    [...]
    
    class SaneMainMenu(Item):
        def __init__(self, parent):
            Item.__init__(self, parent, skin_type=’image’)
            self.name   = _(‘Photocopier un document’)
            self.menus = [
                MenuItem(_(‘Apercu’), self.preview, skin_type=’image’),
                MenuItem(_(‘Photocopier (couleur)’), self.print_color),
                MenuItem(_(‘Photocopier (niveaux de gris)’), self.print_gray),
                MenuItem(_(‘Photocopier (noir et blanc)’), self.print_bw)
            ]
    
        [...]
    
        def preview(self, arg=None, menuw=None):
            pass
    
        def print_color(self, arg=None, menuw=None):
            pass
    
        def print_gray(self, arg=None, menuw=None):
            pass
    
        def print_bw(self, arg=None, menuw=None):
            pass

    Chacune des instances de MenuItem permet d’accéder à une fonction différente de la classe SaneMainMenu. Lorsqu’une entrée est sélectionnée dans le menu, la fonction correspondante est exécutée, avec d’éventuels arguments (non utilisés ici), ainsi que la variable menuw, correspondant au menu à partir duquel la fonction est appelée.

    Initialisation

    Nous avons besoin d’un répertoire temporaire dans lequel stocker des informations ; il faut donc l’initialiser. Par ailleurs, nous allons mettre en place une image par défaut lorsque aucun aperçu n’est disponible.

    [...]
    import base64, os, tempfile
    [...]
    
    class SaneMainMenu(Item):
        def __init__(self, parent):
            Item.__init__(self, parent, skin_type=’image’)
            self.name   = _(‘Photocopier un document’)
            self.menus = [
                MenuItem(_(‘Apercu’), self.preview, skin_type=’image’),
                MenuItem(_(‘Photocopier (couleur)’), self.print_color),
                MenuItem(_(‘Photocopier (niveaux de gris)’), self.print_gray),
                MenuItem(_(‘Photocopier (noir et blanc)’), self.print_bw),
            ]
            self.tmp_dir = None
            self.reinit()
    
        def reinit(self):
            if self.tmp_dir:
                os.system(‘rm -fr %s’ % self.tmp_dir)
            self.tmp_dir = tempfile.mkdtemp(‘’, ‘tmp-freevo-sane-’)
            self.set_preview(None, menuw=menuw)
    
        def set_preview(self, file_=’preview’, menuw=None):
            """
            file_ = None if you want to remove the preview
            """
            if not file_:
                phold = file(os.path.join(self.tmp_dir, ‘placeholder.png’), ‘w’)
                phold.write(base64.decodestring(_placeholder_data))
                phold.close()
                file_ = ‘placeholder’
            image = os.path.join(self.tmp_dir, ‘%s.png’ % file_)
            for menu in self.menus:
                menu.image = image
            if menuw:
                menuw.refresh()
    
        [...]
    
    _placeholder_data = \
    """[...]
    [...]"""

    La fonction d’initialisation appelle reinit(), qui crée le répertoire temporaire (en effaçant l’ancien répertoire, s’il existe), et qui appelle set_preview() pour afficher l’image par défaut.
    Pour simplifier la distribution de ce greffon, nous choisissons d’embarquer cette image dans le code Python : il suffit d’encoder l’image voulue en base64 avec la fonction base64.encodestring(file(‘<image>.png’).read()) et de placer le résultat dans _placeholder_data.
    Lorsque la fonction set_preview() est exécutée, elle remplace l’image du menu de notre photocopieuse. Si la variable file_ est None, l’image par défaut est affichée.
    Dans les menus habituels de Freevo, cette image est différente pour chacune des entrées ; c’est pratique, par exemple, pour une  liste de musiques, où chacune a sa pochette. Dans notre cas, nous attribuons la même image à toutes les entrées du menu.
    En changeant la valeur de la variable image de chacune des entrées du menu (for menu in self.menus), nous nous assurons que la nouvelle image sera chargée.
    Si nous nous étions contentés d’écrire le contenu de l’image dans un fichier existant, l’affichage ne se mettrait pas à jour :  les images sont en cache, les modifications sur disque ne sont donc pas prises en compte.
    Une fois que l’image est modifiée, le menu est rafraîchi avec la fonction refresh().

    Traitement des images

    SANE est l’infrastructure de numérisation sous GNU/Linux. Il offre, entre autres, la commande scanimage, qui permet de numériser un document en ligne de commande.
    C’est ce programme qui sera appelé par l’intermédiaire de la fonction os.system(). Pour le traitement des images, la commande convert de la suite logicielle ImageMagick sera utilisée.
    Les commandes suivantes nous seront utiles :

    Scanimage ne représente qu’un élément de la gamme d’outils SANE, des interfaces graphiques et greffons pour The Gimp sont également disponibles.

    • scanimage --resolution 75 preview.pnm puis convert preview.pnm preview.png : acquisition d’un aperçu (75 DPI) ;
    • scanimage --resolution --mode Gray 300 final.pnm puis convert final.pnm -density 300 -units PixelsPerInch final.ps : acquisition du document en niveaux de gris et conversion en PostScript ;
    • lp : impression du document PostScript.

    Aperçu

    Maintenant que nous avons réalisé un menu, les squelettes des fonctions et de quoi afficher une image, nous pouvons nous intéresser à l’acquisition d’un aperçu...
    Nous allons donc compléter la fonction preview() :

    [...]
    import config
    from gui import PopupBox
    [...]
    
    def preview(self, arg=None, menuw=None):
        self.reinit(menuw=menuw)
        pnmfile = os.path.join(self.tmp_dir, ‘preview.pnm’)
        pngfile = os.path.join(self.tmp_dir, ‘preview.png’)
        popup = PopupBox(text=_(‘Acquisition de l apercu’))
        popup.show()
        os.system(‘%s --resolution %d > %s’ % (config.SANE_SCANIMAGE_PATH, config.SANE_PREVIEW_RESOLUTION, pnmfile))
        os.system(‘%s %s %s’ % (config.SANE_CONVERT_PATH, pnmfile, pngfile))
        self.set_preview(menuw=menuw)
        popup.destroy()

    Tout d’abord, avant d’acquérir l’aperçu, le répertoire temporaire et l’affichage sont réinitialisés (self.reinit()). Un message surgissant (popup) est présenté, gelant ainsi l’interface et nous informant que l’aperçu est en cours d’acquisition ; c’est le rôle de la fonction show().
    La fonction os.system() est ensuite utilisée pour appeler scanimage (l’aperçu est tout simplement une image en basse résolution). Une fois que ce dernier est enregistré, il est converti au format PNG, puis affiché dans le cadre prévu à cet effet, avec la fonction set_preview(). Le message d’attente est ensuite supprimé, l’utilisateur a de nouveau le contrôle sur son système.
    Dans cette fonction, nous accédons à des variables de la configuration. Ces variables sont spécifiées dans le fichier local_conf.py. Nous verrons cela en détail un peu plus loin.

    Photocopie

    La fonction print_(), dont le but est de photocopier le document qui est dans le scanner, est créée. Les trois fonctions que nous avons définies plus haut appellent celle-ci, avec des arguments différents.

    def print_color(self, arg=None, menuw=None):
        self.print_(mode=’Color’)
    
    def print_gray(self, arg=None, menuw=None):
        self.print_(mode=’Gray’)
    
    def print_bw(self, arg=None, menuw=None):
        self.print_(mode=’Lineart’)
    
    def print_(self, mode=’Color’):
        pnmfile = os.path.join(self.tmp_dir, ‘final.pnm’)
        psfile = os.path.join(self.tmp_dir, ‘final.ps’)
        popup = PopupBox(text=_(‘[1/3] Acquisition de l image’))
        popup.show()
        os.system(‘%s --resolution %d --mode %s > %s’ % (config.SANE_SCANIMAGE_PATH, config.SANE_ACQUIRE_RESOLUTION, mode, pnmfile))
        popup.destroy()
        popup = PopupBox(text=_(‘[2/3] Conversion’))
        popup.show()
        os.system(‘%s %s -density %d -units PixelsPerInch %s’ % (config.SANE_CONVERT_PATH, pnmfile, config.SANE_ACQUIRE_RESOLUTION, psfile))
        popup.destroy()
        popup = PopupBox(text=_(‘[3/3] Impression’))
        popup.show()
        os.system(‘%s %s’ % (config.SANE_PRINT_COMMAND, psfile))
        popup.destroy()

    La procédure est la même qu’avec l’acquisition de l’aperçu : affichage de popups, exécution de commandes...

    Configuration

    Freevo, lors de son démarrage, lit le fichier local_conf.py. Toutes les variables de ce fichier sont intégrées dans le module config. Par conséquent, toute configuration effectuée dans ce fichier se trouve dans ce module. Les éléments que nous utilisons ici sont :

    • SANE_SCANIMAGE_PATH : chemin vers la commande scanimage ;
    • SANE_CONVERT_PATH : chemin vers la commande convert ;
    • SANE_PREVIEW_RESOLUTION : résolution pour l’aperçu ;
    • SANE_ACQUIRE_RESOLUTION : résolution pour la photocopie ;
    • SANE_PRINT_COMMAND : commande d’impression.

    Finalement...

    Finalement, le code complet de ce petit greffon est le suivant :

    # -!- coding: utf-8 -!-
    
    import base64, os, tempfile
    import config, plugin
    from gui import PopupBox
    from item import Item
    from menu import Menu, MenuItem
    
    class PluginInterface(plugin.MainMenuPlugin):
        def __init__(self):
            plugin.MainMenuPlugin.__init__(self)
    
        def items(self, parent):
            return [ SaneMainMenu(parent) ]
    
    class SaneMainMenu(Item):
        def __init__(self, parent):
            Item.__init__(self, parent, skin_type=’image’)
            self.name   = _(‘Photocopier un document’)
            self.menus = [
                MenuItem(_(‘Apercu’), self.preview, skin_type=’image’),
                MenuItem(_(‘Photocopier (couleur)’), self.print_color),
                MenuItem(_(‘Photocopier (niveaux de gris)’), self.print_gray),
                MenuItem(_(‘Photocopier (noir et blanc)’), self.print_bw)
            ]
            self.tmp_dir = None
            self.reinit()
    
        def actions(self):
            items = [ ( self.create_mainmenu , _(‘Photocopier un document’) ) ]
            return items
    
        def set_preview(self, file_=’preview’, menuw=None):
            """
            file_ = None if you want to remove the preview
            """
            if not file_:
                phold = file(os.path.join(self.tmp_dir, ‘placeholder.png’), ‘w’)
                phold.write(base64.decodestring(_placeholder_data))
                phold.close()
                file_ = ‘placeholder’
            image = os.path.join(self.tmp_dir, ‘%s.png’ % file_)
            for menu in self.menus:
                menu.image = image
            if menuw:
                menuw.refresh()
    
        def create_mainmenu(self, arg=None, menuw=None):
            sane_menu = Menu(_(‘Photocopieuse’), self.menus)
            menuw.pushmenu(sane_menu)
            menuw.refresh()
    
        def preview(self, arg=None, menuw=None):
            self.reinit(menuw=menuw)
            pnmfile = os.path.join(self.tmp_dir, ‘preview.pnm’)
            pngfile = os.path.join(self.tmp_dir, ‘preview.png’)
            popup = PopupBox(text=_(‘Acquisition de l apercu’))
            popup.show()
            os.system(‘%s --resolution %d > %s’ % (config.SANE_SCANIMAGE_PATH, config.SANE_PREVIEW_RESOLUTION, pnmfile))
            os.system(‘%s %s %s’ % (config.SANE_CONVERT_PATH, pnmfile, pngfile))
            self.set_preview(menuw=menuw)
            popup.destroy()
    
        def print_color(self, arg=None, menuw=None):
            self.print_(mode=’Color’)
    
        def print_gray(self, arg=None, menuw=None):
            self.print_(mode=’Gray’)
    
        def print_bw(self, arg=None, menuw=None):
            self.print_(mode=’Lineart’)
    
        def print_(self, mode=’Color’):
            pnmfile = os.path.join(self.tmp_dir, ‘final.pnm’)
            psfile = os.path.join(self.tmp_dir, ‘final.ps’)
            popup = PopupBox(text=_(‘[1/3] Acquisition de l image’))
            popup.show()
            os.system(‘%s --resolution %d --mode %s > %s’ % (config.SANE_SCANIMAGE_PATH, config.SANE_ACQUIRE_RESOLUTION, mode, pnmfile))
            popup.destroy()
            popup = PopupBox(text=_(‘[2/3] Conversion’))
            popup.show()
            os.system(‘%s %s -density %d -units PixelsPerInch %s’ % (config.SANE_CONVERT_PATH, pnmfile, config.SANE_ACQUIRE_RESOLUTION, psfile))
            popup.destroy()
            popup = PopupBox(text=_(‘[3/3] Impression’))
            popup.show()
            os.system(‘%s %s’ % (config.SANE_PRINT_COMMAND, psfile))
            popup.destroy()
    
        def reinit(self, arg=None, menuw=None):
            if self.tmp_dir:
                os.system(‘rm -fr %s’ % self.tmp_dir)
            self.tmp_dir = tempfile.mkdtemp(‘’, ‘tmp-freevo-sane-’)
            self.set_preview(None, menuw=menuw)
    
    _placeholder_data = \
    """[...]
    [...]"""

    Pour activer ce greffon, les lignes suivantes sont ajoutées à local_conf.py :

    plugin.activate(‘photocopie’, level = 60)
    SANE_SCANIMAGE_PATH=’/usr/bin/scanimage’
    SANE_CONVERT_PATH=’/usr/bin/convert’
    SANE_PREVIEW_RESOLUTION=75 # résolution minimale de mon scanner
    SANE_ACQUIRE_RESOLUTION=300 # résolution d’impression
    SANE_PRINT_COMMAND=’lp -d hl2030’ # impression sur une imprimante precise

    Améliorations

    A partir de cette base, beaucoup d’améliorations sont envisageables :

    • Il est possible de s’affranchir des menus habituels pour composer quelque chose de spécifique à la photocopie (cf. le greffon weather).
    • Différentes fonctionnalités pourraient être ajoutées, telles que le réglage de la luminosité et du contraste, le nombre de copies, etc.
    • On peut également envisager une interface pour " découper " l’image à numériser et n’en imprimer qu’une partie, en l’agrandissant par exemple.

    Nous constatons donc que Freevo ne pose pas de limite à l’imagination. Il est assez flexible pour s’adapter à nos besoins et non le contraire.

    Retrouvez cet article dans : Linux Magazine 85

    Posté par (La rédaction) | Signature : Sébastien Munch | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.