From 1590d37de04b3bae89e3b39308f0f1c502e6bbaa Mon Sep 17 00:00:00 2001 From: nojhan Date: Mon, 25 Feb 2008 21:16:17 +0000 Subject: [PATCH] script export/upload automatique --- stripit.py | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100755 stripit.py diff --git a/stripit.py b/stripit.py new file mode 100755 index 0000000..c97a564 --- /dev/null +++ b/stripit.py @@ -0,0 +1,343 @@ +#!/bin/env python +# -*- coding: utf-8 -*- + +import sys,os +from PIL import Image +import ftplib +import getopt +from elementtree import ElementTree +import unicodedata + +class Options: + + def __init__(self, argv, usage = "" ): + + # Message à afficher en cas de mauvaise utilisation des options + self.usage = usage + + self.options = {} + self.argv = argv + + def parse(self): + + # construction de la chaine argument pour getopt + gos_short = '' + gos_long = '' + + for o in self.options: + opt = self.options[o] + + # getopt demande deux chaines pour les typographies longues et courtes + gos_short += opt['short'] + gos_long += opt['long'] + + # si l'option prend un paramètre + if not opt['flag']: + gos_short += ':' + gos_long += '=' + + # parse la ligne de commande + try: + opts, args = getopt.getopt( self.argv[1:], gos_short, gos_long ) + except getopt.GetoptError: + print 'Unkown option' + self.print_usage() + sys.exit(2) + + # création des dicionnaires pour les valeurs + self.param = {} + self.is_flag = {} + s2l = {} # associations court:long + for o in self.options: + s2l[ self.options[o]['short'] ] = self.options[o]['long'] + + + # prend les valeurs indiquées sur la ligne de commande + for o,a in opts: + # court => on enlève un tiret + os = o[1:] + # long => on enlève deux tirets + ol = o[2:] + + # si c'est une option courte + if os in s2l: + # si c'est un flag + if self.options[ s2l[os] ]['flag']: + # demandé => vrai + self.options[ s2l[os] ]['value'] = True + else: + # prend la valeur indiquée + self.options[ s2l[os] ]['value'] = a + # si c'est une option longue + elif ol in self.options: + if self.options[ol]['flag']: + self.options[ ol ]['value'] = True + else: + self.options[ol]['value'] = a + + # retourne tout ce qui n'a pas été parsé + return args + + + def add( self, short, long, description, default='' ): + flag = False + if default==True or default==False: + flag = True + self.options [ long ] = { 'short':short, 'long':long, 'description':description, 'default':default, 'flag':flag, 'value':default } + + + def print_usage(self): + print self.usage + + for o in self.options: + print "\t-%s, --%s\t%s." % ( self.options[o]['short'], self.options[o]['long'], self.options[o]['description'] ) + + def print_state(self): + print "Options settings:" + for o in self.options: + print "\t%s=%s (%s)" % ( self.options[o]['long'], self.options[o]['value'], self.options[o]['default'] ) + + def get( self, long ): + return self.options[long]['value'] + +# +# XPath-friendlier ElementTree namespace helper +# http://infix.se/2007/02/21/xpath-friendlier-elementtree-namespace-helper +# +class NS: + def __init__(self, uri): + self.uri = '{'+uri+'}' + def __getattr__(self, tag): + return self.uri + tag + def __call__(self, path): + return "/".join((tag not in ("", ".", "*")) + and getattr(self, tag) + or tag + for tag in path.split("/")) + + +class Stripit: + def __init__( self, verbose=False ): + self.verbose = verbose + + # + # wrapper around PIL 1.1.6 Image.save to preserve PNG metadata + # + # public domain, Nick Galbreath + # http://blog.modp.com/2007/08/python-pil-and-png-metadata-take-2.html + # + def pngsave(self, im, file): + # these can be automatically added to Image.info dict + # they are not user-added metadata + reserved = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect') + + # undocumented class + from PIL import PngImagePlugin + meta = PngImagePlugin.PngInfo() + + # copy metadata into new object + for k,v in im.info.iteritems(): + if k in reserved: continue + meta.add_text(k, v, 0) + + # and save + im.save(file, "PNG", pnginfo=meta) + + + def export( self, file_we, max_size=None, options='' ): + """file name only, without extension""" + + if self.verbose: + print '\tCreating the PNG from the SVG...', + sys.stdout.flush() + + size_arg='' + if max_size: + w = os.popen( 'inkscape --query-width %s.svg' % (file_we) ) + h = os.popen( 'inkscape --query-height %s.svg' % (file_we) ) + + width = float(w.read()) + height = float(h.read()) + ratio = height/width + max_size = float( max_size ) + + w.close() + h.close() + + if height > width: + size_arg += ' --export-height=%f' % (max_size) + size_arg += ' --export-width=%f' % (max_size / ratio) + else: + size_arg += ' --export-height=%f' % (max_size * ratio) + size_arg += ' --export-width=%f' % (max_size) + + + cmd = 'inkscape -z %s --export-png %s.png %s.svg ' % (size_arg, file_we,file_we) + cmd += options + + #if self.verbose: + # print ' " %s " ' % (cmd) + # sys.stdout.flush() + + os.popen( cmd ) + + if self.verbose: + print '\tok' + + + def xfind( self, tree, ns ): + # on ne veut passer qu'un liste d'élements + q = '//' + '/'.join( ns ) + + # requête sur le xml + res = unicode( tree.findtext( q ) ) + + # la norme PNG demande de l'ASCII, on essaye de convertir au mieux + return unicodedata.normalize('NFKD', res ).encode('ASCII', 'ignore') + + + def get_svg_metadata( self, file_we ): + + if self.verbose: + print '\tGet SVG metadata...', + sys.stdout.flush() + + # raccourcis pour les namespaces + DC = NS('http://purl.org/dc/elements/1.1/') + CC = NS('http://web.resource.org/cc/') + RDF = NS('http://www.w3.org/1999/02/22-rdf-syntax-ns#') + SVG = NS('http://www.w3.org/2000/svg') + + tree = ElementTree.parse( file_we + '.svg') + + # PNG metadata textual informations : + # (from http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.Anc-text ) + # + # Title Short (one line) title or caption for image + # Author Name of image's creator + # Description Description of image (possibly long) + # Copyright Copyright notice + # Creation Time Time of original image creation + # Software Software used to create the image + # Disclaimer Legal disclaimer + # Warning Warning of nature of content + # Source Device used to create the image + # Comment Miscellaneous comment; conversion from + # GIF comment + + metadata = {} + metadata['Author'] = self.xfind( tree, [ CC('Agent'), DC('title') ] ) + metadata['Description'] = self.xfind( tree, [ CC('Work'), DC('description') ]) + metadata['Copyright'] = self.xfind( tree, [ CC('Work'), CC('license') ] ) # FIXME ne marche pas + metadata['Creation Time'] = self.xfind( tree, [ CC('Work'), DC('date') ] ) + metadata['Software'] = 'www.inkscape.org / stripit.sourceforge.net' # FIXME pas très élégant + metadata['Disclaimer'] ='' + metadata['Warning'] = '' + metadata['Source'] = self.xfind( tree, [ CC('Work'), DC('source') ]) + lang = self.xfind( tree, [ CC('Work'), DC('language') ]) + metadata['Comment'] = 'Language: %s' % lang + + if self.verbose: + print '\t\t\tok' + return metadata + + + def save_png_with_metadata( self, file_we, metadata ): + if self.verbose: + print '\tWrite metadata in the PNG...\t', + sys.stdout.flush() + + Image.init() + im = Image.open( file_we+'.png' ) + im.info = metadata + #print im.info + self.pngsave( im, file_we+'.png' ) + if self.verbose: + print '\tok' + + + def upload( self, file_we, host, user, dir, password ): + if self.verbose: + print '\tUpload of %s on %s.\t' % (file_we, host), + sys.stdout.flush() + ftp = ftplib.FTP( host, user, password ) + ftp.cwd( dir ) + + if self.verbose: + print '.', + sys.stdout.flush() + + f_png = open( '%s.png' % file_we ) + ftp.storbinary( 'STOR %s.png' % file_we, f_png ) + f_png.close() + + if self.verbose: + print '.', + sys.stdout.flush() + + f_svg = open( '%s.svg' % file_we ) + ftp.storbinary( 'STOR %s.svg' % file_we, f_svg ) + f_svg.close() + + ftp.quit() + + if self.verbose: + print '\ŧok' + + + +if __name__=="__main__": + + usage = """Script d'aide à l'export et au téléchargement pour StripIt. +\tCe script va exporter un fichier SVG au format PNG, puis télécharger le tout sur un serveur FTP. +Usage: stripit.py [OPTIONS] fichier""" + + oo = Options( sys.argv, usage ) + + oo.add( 'H', 'host', 'Serveur FTP', '' ) + oo.add( 'u', 'user', 'Login FTP', 'anonymous' ) + oo.add( 'd', 'dir', 'Dossier FTP', 'strips' ) + oo.add( 'p', 'port', 'Port FTP', '21' ) + oo.add( 'P', 'pass', 'Mot de passe FTP', '' ) + oo.add( 'x', 'no-export', "Pas d'export PNG", False ) + oo.add( 'n', 'no-upload', 'Pas de téléchargement FTP', False ) + oo.add( 'v', 'verbose', "Afficher plus d'informations", False ) + oo.add( 's', 'supp', 'Options supplémentaires pour inkscape', '' ) + oo.add( 'h', 'help', "Ce message d'aide", False ) + oo.add( 'm', 'max-size', 'Taille maximale en hauteur ou en largeur, en pixels', '800' ) + + args = oo.parse() + #oo.print_state() + + if oo.get('help'): + oo.print_usage() + sys.exit() + + while args: + + f = args.pop() + + # supprime l'extension + f = os.path.splitext( f ) [0] + + if oo.get('verbose'): + print "Processing %s:" % os.path.basename( f ) + + si = Stripit( oo.get('verbose') ) + + if not oo.get('no-export'): + si.export( f, oo.get('max-size'), oo.get('supp') ) + + md = si.get_svg_metadata( f ) + + si.save_png_with_metadata( f, md ) + + if not oo.get('no-upload'): + si.upload( + f, + oo.get('host'), + oo.get('user'), + oo.get('dir'), + oo.get('pass') + ) # FIXME options ou .conf ? +