Package imagizer :: Module imagizer |
[hide private]
[frames] | no frames]

# Source Code for Module imagizer.imagizer

       1  #!/usr/bin/env python
       2  # -*- coding: UTF8 -*-
       3  #******************************************************************************\
       4  #* $Source: /srv/cvs/imagizer/doc/html/imagizer.imagizer-pysrc.html,v $
       5  #* $Id: imagizer.imagizer-pysrc.html,v 1.1 2010-04-28 19:44:10 jerome Exp $
       6  #*
       7  #* Copyright (C) 2006 - 2010,  Jérôme Kieffer <kieffer@terre-adelie.org>
       8  #* Conception : Jérôme KIEFFER, Mickael Profeta & Isabelle Letard
       9  #* Licence GPL v2
      10  #*
      11  #* This program is free software; you can redistribute it and/or modify
      12  #* it under the terms of the GNU General Public License as published by
      13  #* the Free Software Foundation; either version 2 of the License, or
      14  #* (at your option) any later version.
      15  #*
      16  #* This program is distributed in the hope that it will be useful,
      17  #* but WITHOUT ANY WARRANTY; without even the implied warranty of
      18  #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      19  #* GNU General Public License for more details.
      20  #*
      21  #* You should have received a copy of the GNU General Public License
      22  #* along with this program; if not, write to the Free Software
      23  #* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
      24  #*
      25  #*****************************************************************************/
      26
      27  """
      28  General library used by selector and generator.
      29  It handles images, progress bars and configuration file.
      30  """
      31
      32  import os, sys, shutil, time, re, gc, distutils.sysconfig
      33
      34  try:
      35      import Image, ImageStat, ImageChops, ImageFile
      36  except:
      37      raise ImportError("Selector needs PIL: Python Imaging Library\n PIL is available from http://www.pythonware.com/products/pil/")
      38  try:
      39      import pygtk ; pygtk.require('2.0')
      40      import gtk
      41      import gtk.glade as GTKglade
      42  except ImportError:
      43      raise ImportError("Selector needs pygtk and glade-2 available from http://www.pygtk.org/")
      44  #Variables globales qui sont des CONSTANTES !
      45  gtkInterpolation = [gtk.gdk.INTERP_NEAREST, gtk.gdk.INTERP_TILES, gtk.gdk.INTERP_BILINEAR, gtk.gdk.INTERP_HYPER]
      46  #gtk.gdk.INTERP_NEAREST    Nearest neighbor sampling; this is the fastest and lowest quality mode. Quality is normally unacceptable when scaling down, but may be OK when scaling up.
      47  #gtk.gdk.INTERP_TILES    This is an accurate simulation of the PostScript image operator without any interpolation enabled. Each pixel is rendered as a tiny parallelogram of solid color, the edges of which are implemented with antialiasing. It resembles nearest neighbor for enlargement, and bilinear for reduction.
      48  #gtk.gdk.INTERP_BILINEAR    Best quality/speed balance; use this mode by default. Bilinear interpolation. For enlargement, it is equivalent to point-sampling the ideal bilinear-interpolated image. For reduction, it is equivalent to laying down small tiles and integrating over the coverage area.
      49  #gtk.gdk.INTERP_HYPER    This is the slowest and highest quality reconstruction function. It is derived from the hyperbolic filters in Wolberg's "Digital Image Warping", and is formally defined as the hyperbolic-filter sampling the ideal hyperbolic-filter interpolated image (the filter is designed to be idempotent for 1:1 pixel mapping).
      50
      51
      52  #here we detect the OS runnng the program so that we can call exftran in the right way
      53  installdir = os.path.join(distutils.sysconfig.get_python_lib(), "imagizer")
      54  if os.name == 'nt': #sys.platform == 'win32':
      55      exiftran = os.path.join(installdir, "exiftran.exe ")
      56      gimpexe = "gimp-remote "
      57      ConfFile = [os.path.join(os.getenv("ALLUSERSPROFILE"), "imagizer.conf"), os.path.join(os.getenv("USERPROFILE"), "imagizer.conf"), "imagizer.conf"]
      58  elif os.name == 'posix':
      59      MaxJPEGMem = 100000 # OK up to 10 Mpix
      60      exiftran = "JPEGMEM=%i %s " % (MaxJPEGMem, os.path.join(installdir, "exiftran "))
      61      gimpexe = "gimp-remote "
      62      ConfFile = ["/etc/imagizer.conf", os.path.join(os.getenv("HOME"), ".imagizer"), ".imagizer"]
      63  else:
      64      raise OSError("Your platform does not seem to be an Unix nor a M$ Windows.\nI am sorry but the exiftran binary is necessary to run selector, and exiftran is probably not available for you plateform. If you have exiftran installed, please contact the developper to correct that bug, kieffer at terre-adelie dot org")
      65
      66  #sys.path.append(installdir)
      67  unifiedglade = os.path.join(installdir, "selector.glade")
      68  from signals import Signal
      69  from config import Config
      70  config = Config()
      71  config.load(ConfFile)
      72  if config.ImageCache > 1000:
      73      import imagecache
      74      imageCache = imagecache.ImageCache(maxSize=config.ImageCache)
      75  else:
      76      imageCache = None
      77  import pyexiv2
      78
      79
      80
      81  #class Model:
      82  #    """ Implémentation de l'applicatif
      83  #    """
      84  #    def __init__(self, label):
      85  #        """
      86  #        """
      87  #        self.__label = label
      88  #        self.startSignal = Signal()
      89  #        self.refreshSignal = Signal()
      90  #
      91  #    def start(self):
      92  #        """ Lance les calculs
      93  #        """
      94  #        self.startSignal.emit(self.__label, NBVALUES)
      95  #        for i in xrange(NBVALUES):
      96  #            time.sleep(0.5)
      97  #
      98  #            # On lève le signal de rafraichissement des vues éventuelles
      99  #            # Note qu'ici on ne sait absolument pas si quelque chose s'affiche ou non
     100  #            # ni de quelle façon c'est affiché.
     101  #            self.refreshSignal.emit(i)
     102
     103
     104
     105 -class ModelProcessSelected:
     106      """Implemantation MVC de la procedure ProcessSelected"""
     107 -    def __init__(self):
     108          """
     109          """
     110          self.__label = "Un moment..."
     111          self.startSignal = Signal()
     112          self.refreshSignal = Signal()
     113          self.finishSignal = Signal()
     114          self.NbrJobsSignal = Signal()
     115 -    def start(self, List):
     116          """ Lance les calculs
     117          """
     118
     119          def SplitIntoPages(pathday, GlobalCount):
     120              """Split a directory (pathday) into pages of 20 images"""
     121              files = []
     122              for  i in os.listdir(pathday):
     123                  if os.path.splitext(i)[1] in config.Extensions:files.append(i)
     124              files.sort()
     125              if  len(files) > config.NbrPerPage:
     126                  pages = 1 + (len(files) - 1) / config.NbrPerPage
     127                  for i in range(1, pages + 1):
     128                      folder = os.path.join(pathday, config.PagePrefix + str(i))
     129                      if not os.path.isdir(folder): mkdir(folder)
     130                  for j in range(len(files)):
     131                      i = 1 + (j) / config.NbrPerPage
     132                      filename = os.path.join(pathday, config.PagePrefix + str(i), files[j])
     133                      self.refreshSignal.emit(GlobalCount, files[j])
     134                      GlobalCount += 1
     135                      shutil.move(os.path.join(pathday, files[j]), filename)
     136                      ScaleImage(filename, filigrane)
     137              else:
     138                  for j in files:
     139                      self.refreshSignal.emit(GlobalCount, j)
     140                      GlobalCount += 1
     141                      ScaleImage(os.path.join(pathday, j), filigrane)
     142              return GlobalCount
     143          def ArrangeOneFile(dirname, filename):
     144              try:
     145                  timetuple = time.strptime(filename[:19], "%Y-%m-%d_%Hh%Mm%S")
     146                  suffix = filename[19:]
     147              except ValueError:
     148                  try:
     149                      timetuple = time.strptime(filename[:11], "%Y-%m-%d_")
     150                      suffix = filename[11:]
     151                  except ValueError:
     152                      print("Unable to handle such file: %s" % filename)
     153                      return
     154              daydir = os.path.join(SelectedDir, time.strftime("%Y-%m-%d", timetuple))
     155              if not os.path.isdir(daydir):
     156                  os.mkdir(daydir)
     157              shutil.move(os.path.join(dirname, filename), os.path.join(daydir, time.strftime("%Hh%Mm%S", timetuple) + suffix))
     158
     159          self.startSignal.emit(self.__label, max(1, len(List)))
     160          if config.Filigrane:
     161              filigrane = signature(config.FiligraneSource)
     162          else:
     163              filigrane = None
     164
     165          SelectedDir = os.path.join(config.DefaultRepository, config.SelectedDirectory)
     166          self.refreshSignal.emit(-1, "copie des fichiers existants")
     167          if not os.path.isdir(SelectedDir):     mkdir(SelectedDir)
     168  #####first of all : copy the subfolders into the day folder to help mixing the files
     169          AlsoProcess = 0
     170          for day in os.listdir(SelectedDir):
     171  #if SingleDir : revert to a foldered structure
     172              DayOrFile = os.path.join(SelectedDir, day)
     173              if os.path.isfile(DayOrFile):
     174                  ArrangeOneFile(SelectedDir, day)
     175                  AlsoProcess += 1
     176  #end SingleDir normalization
     177              elif os.path.isdir(DayOrFile):
     178                  if day in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]:
     179                      recursive_delete(DayOrFile)
     180                  elif day.find(config.PagePrefix) == 0: #subpages in SIngleDir mode that need to be flatten
     181                      for File in os.listdir(DayOrFile):
     182                          if     os.path.isfile(os.path.join(DayOrFile, File)):
     183                              ArrangeOneFile(DayOrFile, File)
     184                              AlsoProcess += 1
     185  #                        elif os.path.isdir(os.path.join(DayOrFile,File)) and File in [config.ScaledImages["Suffix"],config.Thumbnails["Suffix"]]:
     186  #                            recursive_delete(os.path.join(DayOrFile,File))
     187                      recursive_delete(DayOrFile)
     188                  else:
     189                      for File in os.listdir(DayOrFile):
     190                          if File.find(config.PagePrefix) == 0:
     191                              if os.path.isdir(os.path.join(SelectedDir, day, File)):
     192                                  for strImageFile in os.listdir(os.path.join(SelectedDir, day, File)):
     193                                      src = os.path.join(SelectedDir, day, File, strImageFile)
     194                                      dst = os.path.join(SelectedDir, day, strImageFile)
     195                                      if os.path.isfile(src) and not os.path.exists(dst):
     196                                          shutil.move(src, dst)
     197                                          AlsoProcess += 1
     198                                      if (os.path.isdir(src)) and (os.path.split(src)[1] in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]):
     199                                          shutil.rmtree(src)
     200                          else:
     201                              if os.path.splitext(File)[1] in config.Extensions:
     202                                  AlsoProcess += 1
     203
     204  #######then copy the selected files to their folders###########################
     205          for File in List:
     206              dest = os.path.join(SelectedDir, File)
     207              src = os.path.join(config.DefaultRepository, File)
     208              destdir = os.path.dirname(dest)
     209              if not os.path.isdir(destdir): makedir(destdir)
     210              if not os.path.exists(dest):
     211                  print "copie de %s " % (File)
     212                  shutil.copy(src, dest)
     213                  os.chmod(dest, config.DefaultFileMode)
     214                  AlsoProcess += 1
     215              else :
     216                  print "%s existe déja" % (dest)
     217          if AlsoProcess > 0:self.NbrJobsSignal.emit(AlsoProcess)
     218  ######copy the comments of the directory to the Selected directory
     219          AlreadyDone = []
     220          for File in List:
     221              directory = os.path.split(File)[0]
     222              if directory in AlreadyDone:
     223                  continue
     224              else:
     225                  AlreadyDone.append(directory)
     226                  dst = os.path.join(SelectedDir, directory, config.CommentFile)
     227                  src = os.path.join(config.DefaultRepository, directory, config.CommentFile)
     228                  if os.path.isfile(src):
     229                      shutil.copy(src, dst)
     230
     231  ########finaly recreate the structure with pages or make a single page ########################
     232          dirs = os.listdir(SelectedDir)
     233          dirs.sort()
     234  #        print "config.ExportSingleDir = "+str(config.ExportSingleDir)
     235          if config.ExportSingleDir: #SingleDir
     236              #first move all files to the root
     237              for day in dirs:
     238                  daydir = os.path.join(SelectedDir, day)
     239                  for filename in os.listdir(daydir):
     240                      try:
     241                          timetuple = time.strptime(day[:10] + "_" + filename[:8], "%Y-%m-%d_%Hh%Mm%S")
     242                          suffix = filename[8:]
     243                      except ValueError:
     244                          try:
     245                              timetuple = time.strptime(day[:10], "%Y-%m-%d")
     246                              suffix = filename
     247                          except ValueError:
     248                              print ("Unable to handle dir: %s\t file: %s" % (day, filename))
     249                              continue
     250                      src = os.path.join(daydir, filename)
     251                      dst = os.path.join(SelectedDir, time.strftime("%Y-%m-%d_%Hh%Mm%S", timetuple) + suffix)
     252                      shutil.move(src, dst)
     253                  recursive_delete(daydir)
     254              SplitIntoPages(SelectedDir, 0)
     255          else: #Multidir
     256              GlobalCount = 0
     257              for day in dirs:
     258                  GlobalCount = SplitIntoPages(os.path.join(SelectedDir, day), GlobalCount)
     259
     260          self.finishSignal.emit()
     261
     262
     263
     264 -class ModelCopySelected:
     265      """Implemantation MVC de la procedure CopySelected"""
     266 -    def __init__(self):
     267          """
     268          """
     269          self.__label = "Un moment..."
     270          self.startSignal = Signal()
     271          self.refreshSignal = Signal()
     272          self.finishSignal = Signal()
     273          self.NbrJobsSignal = Signal()
     274 -    def start(self, List):
     275          """ Lance les calculs
     276          """
     277          self.startSignal.emit(self.__label, max(1, len(List)))
     278          if config.Filigrane:
     279              filigrane = signature(config.FiligraneSource)
     280          else:
     281              filigrane = None
     282
     283          SelectedDir = os.path.join(config.DefaultRepository, config.SelectedDirectory)
     284          self.refreshSignal.emit(-1, "copie des fichiers existants")
     285          if not os.path.isdir(SelectedDir):     mkdir(SelectedDir)
     286  #####first of all : copy the subfolders into the day folder to help mixing the files
     287          for day in os.listdir(SelectedDir):
     288              for File in os.listdir(os.path.join(SelectedDir, day)):
     289                  if File.find(config.PagePrefix) == 0:
     290                      if os.path.isdir(os.path.join(SelectedDir, day, File)):
     291                          for strImageFile in os.listdir(os.path.join(SelectedDir, day, File)):
     292                              src = os.path.join(SelectedDir, day, File, strImageFile)
     293                              dst = os.path.join(SelectedDir, day, strImageFile)
     294                              if os.path.isfile(src) and not os.path.exists(dst):
     295                                  shutil.move(src, dst)
     296                              if (os.path.isdir(src)) and (os.path.split(src)[1] in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]):
     297                                  shutil.rmtree(src)
     298
     299  #######then copy the selected files to their folders###########################
     300          GlobalCount = 0
     301          for File in List:
     302              dest = os.path.join(SelectedDir, File)
     303              src = os.path.join(config.DefaultRepository, File)
     304              destdir = os.path.dirname(dest)
     305              self.refreshSignal.emit(GlobalCount, File)
     306              GlobalCount += 1
     307              if not os.path.isdir(destdir): makedir(destdir)
     308              if not os.path.exists(dest):
     309                  if filigrane:
     310                      Img = Image.open(src)
     311                      filigrane.substract(Img).save(dest, quality=config.FiligraneQuality, optimize=config.FiligraneOptimize, progressive=config.FiligraneOptimize)
     312                  else:
     313                      shutil.copy(src, dest)
     314                  os.chmod(dest, config.DefaultFileMode)
     315              else :
     316                  print "%s existe déja" % (dest)
     317  ######copy the comments of the directory to the Selected directory
     318          AlreadyDone = []
     319          for File in List:
     320              directory = os.path.split(File)[0]
     321              if directory in AlreadyDone:
     322                  continue
     323              else:
     324                  AlreadyDone.append(directory)
     325                  dst = os.path.join(SelectedDir, directory, config.CommentFile)
     326                  src = os.path.join(config.DefaultRepository, directory, config.CommentFile)
     327                  if os.path.isfile(src):
     328                      shutil.copy(src, dst)
     329          self.finishSignal.emit()
     330
     331
     332
     333
     334 -class ModelRangeTout:
     335      """Implemantation MVC de la procedure RangeTout
     336      moves all the JPEG files to a directory named from
     337      their day and with the name according to the time"""
     338
     339 -    def __init__(self):
     340          """
     341          """
     342          self.__label = "Initial renaming of new images .... "
     343          self.startSignal = Signal()
     344          self.refreshSignal = Signal()
     345          self.finishSignal = Signal()
     346          self.NbrJobsSignal = Signal()
     347
     348
     349 -    def start(self, RootDir):
     350          """ Lance les calculs
     351          """
     352          config.DefaultRepository = RootDir
     353          AllJpegs = FindFile(RootDir)
     354          AllFilesToProcess = []
     355          AllreadyDone = []
     356          NewFiles = []
     357          uid = os.getuid()
     358          gid = os.getgid()
     359          for i in AllJpegs:
     360              if i.find(config.TrashDirectory) == 0: continue
     361              if i.find(config.SelectedDirectory) == 0: continue
     362              try:
     363                  a = int(i[:4])
     364                  m = int(i[5:7])
     365                  j = int(i[8:10])
     366                  if (a >= 0000) and (m <= 12) and (j <= 31) and (i[4] in ["-", "_", "."]) and (i[7] in ["-", "_"]):
     367                      AllreadyDone.append(i)
     368                  else:
     369                      AllFilesToProcess.append(i)
     370              except ValueError:
     371                  AllFilesToProcess.append(i)
     372          AllFilesToProcess.sort()
     373          NumFiles = len(AllFilesToProcess)
     374          self.startSignal.emit(self.__label, NumFiles)
     375          for h in range(NumFiles):
     376              i = AllFilesToProcess[h]
     377              self.refreshSignal.emit(h, i)
     378              data = photo(i).exif()
     379              try:
     380                  datei, heurei = data["Heure"].split()
     381                  date = re.sub(":", "-", datei)
     382                  heurej = re.sub(":", "h", heurei, 1)
     383                  model = data["Modele"].split(",")[-1]
     384                  heure = latin1_to_ascii("%s-%s.jpg" % (re.sub(":", "m", heurej, 1), re.sub("/", "", re.sub(" ", "_", model))))
     385              except ValueError:
     386                  date = time.strftime("%Y-%m-%d", time.gmtime(os.path.getctime(os.path.join(RootDir, i))))
     387                  heure = latin1_to_ascii("%s-%s.jpg" % (time.strftime("%Hh%Mm%S", time.gmtime(os.path.getctime(os.path.join(RootDir, i)))), re.sub("/", "-", re.sub(" ", "_", os.path.splitext(i)[0]))))
     388              if not (os.path.isdir(os.path.join(RootDir, date))) : mkdir(os.path.join(RootDir, date))
     389              strImageFile = os.path.join(RootDir, date, heure)
     390              ToProcess = os.path.join(date, heure)
     391              if os.path.isfile(strImageFile):
     392                  print "Problème ... %s existe déja " % i
     393                  s = 0
     394                  for j in os.listdir(os.path.join(RootDir, date)):
     395                      if j.find(heure[:-4]) == 0:s += 1
     396                  ToProcess = os.path.join(date, heure[:-4] + "-%s.jpg" % s)
     397                  strImageFile = os.path.join(RootDir, ToProcess)
     398              shutil.move(os.path.join(RootDir, i), strImageFile)
     399              try:
     400                  os.chown(strImageFile, uid, gid)
     401                  os.chmod(strImageFile, config.DefaultFileMode)
     402              except OSError:
     403                  print "error in chown or chmod of %s" % strImageFile
     404              if config.AutoRotate and data["Orientation"] != "1":
     405                  photo(strImageFile).autorotate()
     406  #Set the new images in cache for further display
     407                  if imageCache is not None:
     408                      if config.ImageWidth and config.ImageHeight:
     409                          if imageCache.size + 3 * config.ImageWidth * config.ImageHeight < imageCache.maxSize:
     410                              print "put in cache file " + ToProcess
     411                              pixbuf = gtk.gdk.pixbuf_new_from_file(strImageFile)
     412                              Xsize = pixbuf.get_width()
     413                              Ysize = pixbuf.get_height()
     414                              R = min(float(config.ImageWidth) / float(Xsize), float(config.ImageHeight) / float(Ysize))
     415                              if R < 1:
     416                                  scaled_buf = pixbuf.scale_simple(config.ImageWidth, config.ImageHeight, gtkInterpolation[config.Interpolation])
     417                              else:
     418                                  scaled_buf = pixbuf
     419                              imageCache[ ToProcess ] = scaled_buf
     420  ##################################################
     421              AllreadyDone.append(ToProcess)
     422              NewFiles.append(ToProcess)
     423          AllreadyDone.sort()
     424          self.finishSignal.emit()
     425
     426          if len(NewFiles) > 0:
     427              FirstImage = min(NewFiles)
     428              return AllreadyDone, AllreadyDone.index(FirstImage)
     429          else:
     430              return AllreadyDone, 0
     431
     432 -class Controler:
     433      """ Implémentation du contrôleur de la vue utilisant la console"""
     434 -    def __init__(self, model, view):
     435  #        self.__model = model # Ne sert pas ici, car on ne fait que des actions modèle -> vue
     436          self.__view = view
     437
     438          # Connection des signaux
     439          model.startSignal.connect(self.__startCallback)
     440          model.refreshSignal.connect(self.__refreshCallback)
     441          model.finishSignal.connect(self.__stopCallback)
     442          model.NbrJobsSignal.connect(self.__NBJCallback)
     443 -    def __startCallback(self, label, nbVal):
     444          """ Callback pour le signal de début de progressbar."""
     445          self.__view.creatProgressBar(label, nbVal)
     446 -    def __refreshCallback(self, i, filename):
     447          """ Mise à jour de la progressbar."""
     448          self.__view.updateProgressBar(i, filename)
     449 -    def __stopCallback(self):
     450          """ Callback pour le signal de fin de splashscreen."""
     451          self.__view.finish()
     452 -    def __NBJCallback(self, NbrJobs):
     453          """ Callback pour redefinir le nombre de job totaux."""
     454          self.__view.ProgressBarMax(NbrJobs)
     455
     456
     457
     458 -class ControlerX:
     459      """ Implémentation du contrôleur. C'est lui qui lie les modèle et la(les) vue(s)."""
     460 -    def __init__(self, model, viewx):
     461  #        self.__model = model # Ne sert pas ici, car on ne fait que des actions modèle -> vue
     462          self.__viewx = viewx
     463          # Connection des signaux
     464          model.startSignal.connect(self.__startCallback)
     465          model.refreshSignal.connect(self.__refreshCallback)
     466          model.finishSignal.connect(self.__stopCallback)
     467          model.NbrJobsSignal.connect(self.__NBJCallback)
     468 -    def __startCallback(self, label, nbVal):
     469          """ Callback pour le signal de début de progressbar."""
     470          self.__viewx.creatProgressBar(label, nbVal)
     471 -    def __refreshCallback(self, i, filename):
     472          """ Mise à jour de la progressbar.    """
     473          self.__viewx.updateProgressBar(i, filename)
     474 -    def __stopCallback(self):
     475          """ ferme la fenetre. Callback pour le signal de fin de splashscreen."""
     476          self.__viewx.finish()
     477 -    def __NBJCallback(self, NbrJobs):
     478          """ Callback pour redefinir le nombre de job totaux."""
     479          self.__viewx.ProgressBarMax(NbrJobs)
     480
     481
     482
     483 -class View:
     484      """ Implémentation de la vue.
     485      Utilisation de la console.
     486      """
     487 -    def __init__(self):
     488          """ On initialise la vue."""
     489          self.__nbVal = None
     490 -    def creatProgressBar(self, label, nbVal):
     491          """ Création de la progressbar.        """
     492          self.__nbVal = nbVal
     493          print label
     494
     495 -    def ProgressBarMax(self, nbVal):
     496          """re-definit le nombre maximum de la progress-bar"""
     497          self.__nbVal = nbVal
     498  #        print "Modification du maximum : %i"%self.__nbVal
     499
     500 -    def updateProgressBar(self, h, filename):
     501          """ Mise à jour de la progressbar
     502          """
     503          print "%5.1f %% processing  ... %s" % (100.0 * (h + 1) / self.__nbVal, filename)
     504 -    def finish(self):
     505          """nothin in text mode"""
     506          pass
     507
     508 -class ViewX:
     509      """
     510      Implementation of the view as a splashscren
     511      """
     512 -    def __init__(self):
     513          """
     514          Initialization of the view in the constructor
     515
     516          Ici, on ne fait rien, car la progressbar sera créée au moment
     517          où on en aura besoin. Dans un cas réel, on initialise les widgets
     518          de l'interface graphique
     519          """
     520          self.__nbVal = None
     521          self.xml = None
     522          self.pb = None
     523
     524 -    def creatProgressBar(self, label, nbVal):
     525          """
     526          Creation of a progress bar.
     527          """
     528          self.xml = GTKglade.XML(unifiedglade, root="splash")
     529          self.xml.get_widget("image").set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(os.path.join(installdir, "Splash.png")))
     530          self.pb = self.xml.get_widget("progress")
     531          self.xml.get_widget("splash").set_title(label)
     532          self.xml.get_widget("splash").show()
     533          while gtk.events_pending():gtk.main_iteration()
     534          self.__nbVal = nbVal
     535 -    def ProgressBarMax(self, nbVal):
     536          """re-definit le nombre maximum de la progress-bar"""
     537          self.__nbVal = nbVal
     538
     539 -    def updateProgressBar(self, h, filename):
     540          """ Mise à jour de la progressbar
     541          Dans le cas d'un toolkit, c'est ici qu'il faudra appeler le traitement
     542          des évènements.
     543          set the progress-bar to the given value with the given name
     544          @param h: current number of the file
     545          @type val: integer or float
     546          @param name: name of the current element
     547          @type name: string
     548          @return: None"""
     549          if h < self.__nbVal:
     550              self.pb.set_fraction(float(h + 1) / self.__nbVal)
     551          else:
     552              self.pb.set_fraction(1.0)
     553          self.pb.set_text(filename)
     554          while gtk.events_pending():gtk.main_iteration()
     555 -    def finish(self):
     556          """destroys the interface of the splash screen"""
     557          self.xml.get_widget("splash").destroy()
     558          while gtk.events_pending():gtk.main_iteration()
     559          del self.xml
     560          gc.collect()
     561
     562
     563 -def RangeTout(repository):
     564      """moves all the JPEG files to a directory named from their day and with the
     565      name according to the time
     566      This is a MVC implementation"""
     567      model = ModelRangeTout()
     568      view = View()
     569      Controler(model, view)
     570      viewx = ViewX()
     571      ControlerX(model, viewx)
     572      return model.start(repository)
     573
     574 -def ProcessSelected(SelectedFiles):
     575      """This procedure uses the MVC implementation of processSelected
     576      It makes a copy of all selected photos and scales them
     577      copy all the selected files to "selected" subdirectory, 20 per page
     578      """
     579      print "execution %s" % SelectedFiles
     580      model = ModelProcessSelected()
     581      view = View()
     582      Controler(model, view)
     583      viewx = ViewX()
     584      ControlerX(model, viewx)
     585      model.start(SelectedFiles)
     586
     587 -def CopySelected(SelectedFiles):
     588      """This procedure makes a copy of all selected photos and scales them
     589      copy all the selected files to "selected" subdirectory
     590      """
     591      print "Copy %s" % SelectedFiles
     592      model = ModelCopySelected()
     593      view = View()
     594      Controler(model, view)
     595      viewx = ViewX()
     596      ControlerX(model, viewx)
     597      model.start(SelectedFiles)
     598
     599
     600
     601
     602  ##########################################################
     603  # # # # # # Début de la classe photo # # # # # # # # # # #
     604  ##########################################################
     605 -class photo:
     606      """class photo that does all the operations available on photos"""
     607 -    def __init__(self, filename):
     608          self.filename = filename
     609          self.fn = os.path.join(config.DefaultRepository, self.filename)
     610          self.data = None
     611          self.x = None
     612          self.y = None
     613          self.g = None
     614          self.f = None
     615          if not os.path.isfile(self.fn):
     616              print "Erreur, le fichier %s n'existe pas" % self.fn
     617          self.bImageCache = (imageCache is not None)
     618
     619 -    def LoadPIL(self):
     620          """Load the image"""
     621          self.f = Image.open(self.fn)
     622
     623 -    def larg(self):
     624          """width-height of a jpeg file"""
     625          self.taille()
     626          return self.x - self.y
     627
     628 -    def taille(self):
     629          """width and height of a jpeg file"""
     630          if self.x == None and self.y == None:
     631              self.LoadPIL()
     632              self.x, self.y = self.f.size
     633
     634 -    def SaveThumb(self, Thumbname, Size=160, Interpolation=1, Quality=75, Progressive=False, Optimize=False, ExifExtraction=False):
     635          """save a thumbnail of the given name, with the given size and the interpolation methode (quality)
     636          resampling filters :
     637          NONE = 0
     638          NEAREST = 0
     639          ANTIALIAS = 1 # 3-lobed lanczos
     640          LINEAR = BILINEAR = 2
     641          CUBIC = BICUBIC = 3
     642          """
     643          if  os.path.isfile(Thumbname):
     644              print "sorry, file %s exists" % Thumbname
     645          else:
     646              image_exif = pyexiv2.Image(self.fn)
     647              image_exif.readMetadata()
     648              #RawExif,comment=EXIF.process_file(open(self.fn,'rb'),0)
     649              extract = False
     650              print "process file %s exists" % Thumbname
     651              if ExifExtraction:
     652                  try:
     653                      image_exif.dumpThumbnailToFile(Thumbname[:-4])
     654                      extract = True
     655                  except OSError:
     656                      extract = False
     657              if not extract:
     658  #                print "on essaie avec PIL"
     659                  self.LoadPIL()
     660                  self.g = self.f.copy()
     661                  self.g.thumbnail((Size, Size), Interpolation)
     662                  self.g.save(Thumbname, quality=Quality, progressive=Progressive, optimize=Optimize)
     663              os.chmod(Thumbname, config.DefaultFileMode)
     664
     665
     666 -    def Rotate(self, angle=0):
     667          """does a looseless rotation of the given jpeg file"""
     668          if os.name == 'nt' and self.f != None: del self.f
     669          self.taille()
     670          x = self.x
     671          y = self.y
     672          if angle == 90:
     673              if imageCache is not None:
     674                  os.system('%s -ip -9 "%s" &' % (exiftran, self.fn))
     675                  imageCache[self.filename] = imageCache[self.filename].rotate_simple(gtk.gdk.PIXBUF_ROTATE_CLOCKWISE)
     676                  self.x = y
     677                  self.y = x
     678              else:
     679                  os.system('%s -ip -9 "%s" ' % (exiftran, self.fn))
     680                  self.x = None
     681                  self.y = None
     682          elif angle == 270:
     683              if imageCache is not None:
     684                  os.system('%s -ip -2 "%s" &' % (exiftran, self.fn))
     685                  imageCache[self.filename] = imageCache[self.filename].rotate_simple(gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
     686                  self.x = y
     687                  self.y = x
     688              else:
     689                  os.system('%s -ip -2 "%s" ' % (exiftran, self.fn))
     690                  self.x = None
     691                  self.y = None
     692          elif angle == 180:
     693              if imageCache is not None:
     694                  os.system('%s -ip -1 "%s" &' % (exiftran, self.fn))
     695                  imageCache[self.filename] = imageCache[self.filename].rotate_simple(gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN)
     696                  self.x = x
     697                  self.y = y
     698              else:
     699                  os.system('%s -ip -1 "%s" ' % (exiftran, self.fn))
     700                  self.x = None
     701                  self.y = None
     702          else:
     703              print "Erreur ! il n'est pas possible de faire une rotation de ce type sans perte de donnée."
     704
     705 -    def RemoveFromCache(self):
     706          """remove the curent image from the Cache .... for various reasons"""
     707          if imageCache is not None:
     708              if self.filename in imageCache.ordered:
     709                  pixBuf = imageCache.imageDict.pop(self.filename)
     710                  index = imageCache.ordered.index(self.filename)
     711                  imageCache.ordered.pop(index)
     712                  imageCache.size -= 3 * pixBuf.get_width() * pixBuf.get_height()
     713
     714 -    def Trash(self):
     715          """Send the file to the trash folder"""
     716          self.RemoveFromCache()
     717          Trashdir = os.path.join(config.DefaultRepository, config.TrashDirectory)
     718          td = os.path.dirname(os.path.join(Trashdir, self.filename))
     719          #tf = os.path.join(Trashdir, self.filename)
     720          if not os.path.isdir(td): makedir(td)
     721          shutil.move(self.fn, os.path.join(Trashdir, self.filename))
     722
     723 -    def exif(self):
     724          """return exif data + title from the photo"""
     725          clef = {'Exif.Image.Make':'Marque',
     726   'Exif.Image.Model':'Modele',
     727   'Exif.Photo.DateTimeOriginal':'Heure',
     728   'Exif.Photo.ExposureTime':'Vitesse',
     729   'Exif.Photo.FNumber':'Ouverture',
     730  # 'Exif.Photo.DateTimeOriginal':'Heure2',
     731   'Exif.Photo.ExposureBiasValue':'Bias',
     732   'Exif.Photo.Flash':'Flash',
     733   'Exif.Photo.FocalLength':'Focale',
     734   'Exif.Photo.ISOSpeedRatings':'Iso' ,
     735   'Exif.Image.Orientation':'Orientation'
     736  }
     737
     738          if self.data is None:
     739              self.data = {}
     740              self.data["Taille"] = "%.2f %s" % SmartSize(os.path.getsize(self.fn))
     741              image_exif = pyexiv2.Image(self.fn)
     742              image_exif.readMetadata()
     743              self.data["Titre"] = image_exif.getComment()
     744              self.taille()
     745              self.data["Resolution"] = "%s x %s " % (self.x, self.y)
     746              self.data["Orientation"] = "1"
     747              for i in clef:
     748                  try:
     749                      self.data[clef[i]] = image_exif.interpretedExifValue(i).decode(config.Coding)
     750                  except:
     751                      self.data[clef[i]] = ""
     752          return self.data
     753
     754 -    def has_title(self):
     755          """return true if the image is entitled"""
     756          if self.data == None:
     757              self.exif()
     758          if  self.data["Titre"]:
     759              return True
     760          else:
     761              return False
     762
     763
     764 -    def show(self, Xsize=600, Ysize=600):
     765          """return a pixbuf to shows the image in a Gtk window"""
     766          scaled_buf = None
     767          if Xsize > config.ImageWidth :  config.ImageWidth = Xsize
     768          if Ysize > config.ImageHeight: config.ImageHeight = Ysize
     769          self.taille()
     770          R = min(float(Xsize) / self.x, float(Ysize) / self.y)
     771          if R < 1:
     772              nx = int(R * self.x)
     773              ny = int(R * self.y)
     774          else:
     775              nx = self.x
     776              ny = self.y
     777          if imageCache is not None:
     778              if self.filename in imageCache.ordered:
     779                  data = imageCache[ self.filename ]
     780                  if (data.get_width() == nx) and (data.get_height() == ny):
     781                      scaled_buf = data
     782                      if config.DEBUG: print("Sucessfully fetched %s from cache, cache size: %i images, %.3f MBytes" % (self.filename, len(imageCache.ordered), (imageCache.size / 1048576.0)))
     783                  elif (data.get_width() > nx) or (data.get_height() > ny):
     784                      if config.DEBUG:print("nx=%i,\tny=%i,\tw=%i,h=%i" % (nx, ny, data.get_width(), data.get_height()))
     785                      pixbuf = data
     786                      if config.DEBUG: print("Fetched data for %s have to be rescaled, cache size: %i images, %.3f MBytes" % (self.filename, len(imageCache.ordered), (imageCache.size / 1048576.0)))
     787                      scaled_buf = pixbuf.scale_simple(nx, ny, gtkInterpolation[config.Interpolation])
     788          if not scaled_buf:
     789              pixbuf = gtk.gdk.pixbuf_new_from_file(self.fn)
     790              if R < 1:
     791                  scaled_buf = pixbuf.scale_simple(nx, ny, gtkInterpolation[config.Interpolation])
     792              else :
     793                  scaled_buf = pixbuf
     794              if imageCache is not None:
     795                  imageCache[ self.filename ] = scaled_buf
     796                  if config.DEBUG: print("Sucessfully cached  %s, cache size: %i images, %.3f MBytes" % (self.filename, len(imageCache.ordered), (imageCache.size / 1048576.0)))
     797          return scaled_buf
     798
     799 -    def name(self, titre):
     800          """write the title of the photo inside the description field, in the JPEG header"""
     801          if os.name == 'nt' and self.f != None: del self.f
     802          image_exif = pyexiv2.Image(self.fn)
     803          image_exif.readMetadata()
     804          image_exif.setComment(titre)
     805          image_exif.writeMetadata()
     806
     807 -    def autorotate(self):
     808          """does autorotate the image according to the EXIF tag"""
     809          if os.name == 'nt' and self.f != None: del self.f
     810          os.system('%s -aip "%s"' % (exiftran, self.fn))
     811
     812 -    def ContrastMask(self, outfile):
     813          """Ceci est un filtre de debouchage de photographies, aussi appelé masque de contraste, il permet de rattrapper une photo trop contrasté, un contre jour, ...
     814          Écrit par Jérôme Kieffer, avec l'aide de la liste python@aful, en particulier A. Fayolles et F. Mantegazza
     815          avril 2006
     816          necessite numpy et PIL."""
     817          try:
     818              import numpy
     819          except:
     820              raise ImportError("This filter needs the numpy library available on https://sourceforge.net/projects/numpy/files/")
     821          self.LoadPIL()
     822          x, y = self.f.size
     823          ImageFile.MAXBLOCK = x * y
     824          img_array = numpy.fromstring(self.f.tostring(), dtype="UInt8").astype("UInt16")
     825          img_array.shape = (y, x, 3)
     826          red, green, blue = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2]
     827          desat_array = (numpy.minimum(numpy.minimum(red, green), blue) + numpy.maximum(numpy.maximum(red, green), blue)) / 2
     828          inv_desat = 255 - desat_array
     829          k = Image.fromarray(inv_desat, "L").convert("RGB")
     830          S = ImageChops.screen(self.f, k)
     831          M = ImageChops.multiply(self.f, k)
     832          F = ImageChops.add(ImageChops.multiply(self.f, S), ImageChops.multiply(ImageChops.invert(self.f), M))
     833          F.save(os.path.join(config.DefaultRepository, outfile), quality=90, progressive=True, Optimize=True)
     834          os.chmod(os.path.join(config.DefaultRepository, outfile), config.DefaultFileMode)
     835
     836  ########################################################
     837  # # # # # # fin de la classe photo # # # # # # # # # # #
     838  ########################################################
     839
     840 -class signature:
     841 -    def __init__(self, filename):
     842          """
     843          this filter allows add a signature to an image
     844          """
     845          self.img = None
     846          self.sig = Image.open(filename)
     847          self.sig.convert("RGB")
     848          (self.xs, self.ys) = self.sig.size
     849          self.bigsig = self.sig
     850          #The signature file is entented to be white on a black background, this inverts the color if necessary
     851          if ImageStat.Stat(self.sig)._getmean() > 127:
     852              self.sig = ImageChops.invert(self.sig)
     853
     854          self.orientation = -1 #this is an impossible value
     855          (self.x, self.y) = (self.xs, self.ys)
     856
     857 -    def mask(self, orientation=5):
     858          """
     859          x and y are the size of the initial image
     860          the orientation correspond to the position on a clock :
     861          0 for the center
     862          1 or 2 upper right
     863          3 centered in heith right side ...."""
     864          if orientation == self.orientation and (self.x, self.y) == self.bigsig.size:
     865              #no need to change the mask
     866              return
     867          self.orientation = orientation
     868          self.bigsig = Image.new("RGB", (self.x, self.y), (0, 0, 0))
     869          if self.x < self.xs or self.y < self.ys :
     870              #the signature is larger than the image
     871              return
     872          if self.orientation == 0:
     873              self.bigsig.paste(self.sig, (self.x / 2 - self.xs / 2, self.y / 2 - self.ys / 2, self.x / 2 - self.xs / 2 + self.xs, self.y / 2 - self.ys / 2 + self.ys))
     874          elif self.orientation in [1, 2]:
     875              self.bigsig.paste(self.sig, (self.x - self.xs, 0, self.x, self.ys))
     876          elif self.orientation == 3:
     877              self.bigsig.paste(self.sig, (self.x - self.xs, self.y / 2 - self.ys / 2, self.x, self.y / 2 - self.ys / 2 + self.ys))
     878          elif self.orientation in [ 5, 4]:
     879              self.bigsig.paste(self.sig, (self.x - self.xs, self.y - self.ys, self.x, self.y))
     880          elif self.orientation == 6:
     881              self.bigsig.paste(self.sig, (self.x / 2 - self.xs / 2, self.y - self.ys, self.x / 2 - self.xs / 2 + self.xs, self.y))
     882          elif self.orientation in [7, 8]:
     883              self.bigsig.paste(self.sig, (0, self.y - self.ys, self.xs, self.y))
     884          elif self.orientation == 9:
     885              self.bigsig.paste(self.sig, (0, self.y / 2 - self.ys / 2, self.xs, self.y / 2 - self.ys / 2 + self.ys))
     886          elif self.orientation in [10, 11]:
     887              self.bigsig.paste(self.sig, (0, 0, self.xs, self.ys))
     888          elif self.orientation == 12:
     889              self.bigsig.paste(self.sig, (self.x / 2 - self.xs / 2, 0, self.x / 2 - self.xs / 2 + self.xs, self.ys))
     890          return
     891
     892 -    def substract(self, inimage, orientation=5):
     893          """apply a substraction mask on the image"""
     894          self.img = inimage
     895          self.x, self.y = self.img.size
     896          ImageFile.MAXBLOCK = self.x * self.y
     897          self.mask(orientation)
     898          k = ImageChops.difference(self.img, self.bigsig)
     899          return k
     900
     901
     902
     903  ############################################################################################################
     904
     905 -def makedir(filen):
     906      """creates the tree structure for the file"""
     907      dire = os.path.dirname(filen)
     908      if os.path.isdir(dire):
     909          mkdir(filen)
     910      else:
     911          makedir(dire)
     912          mkdir(filen)
     913
     914 -def mkdir(filename):
     915      """create an empty directory with the given rights"""
     916  #    config=Config()
     917      os.mkdir(filename)
     918      os.chmod(filename, config.DefaultDirMode)
     919
     920
     921 -def FindFile(RootDir):
     922      """returns a list of the files with the given suffix in the given dir
     923      files=os.system('find "%s"  -iname "*.%s"'%(RootDir,suffix)).readlines()
     924      """
     925      files = []
     926  #    config=Config()
     927      for i in config.Extensions:
     928          files += parser().FindExts(RootDir, i)
     929      good = []
     930      l = len(RootDir) + 1
     931      for i in files: good.append(i.strip()[l:])
     932      good.sort()
     933      return good
     934
     935
     936  #######################################################################################
     937 -def ScaleImage(filename, filigrane=None):
     938      """common processing for one image : create a subfolder "scaled" and "thumb" : """
     939  #    config=Config()
     940      rootdir = os.path.dirname(filename)
     941      scaledir = os.path.join(rootdir, config.ScaledImages["Suffix"])
     942      thumbdir = os.path.join(rootdir, config.Thumbnails["Suffix"])
     943      if not os.path.isdir(scaledir) : mkdir(scaledir)
     944      if not os.path.isdir(thumbdir) : mkdir(thumbdir)
     945      Img = photo(filename)
     946      Param = config.ScaledImages.copy()
     947      Param.pop("Suffix")
     948      Param["Thumbname"] = os.path.join(scaledir, os.path.basename(filename))[:-4] + "--%s.jpg" % config.ScaledImages["Suffix"]
     949      Img.SaveThumb(**Param)
     950      Param = config.Thumbnails.copy()
     951      Param.pop("Suffix")
     952      Param["Thumbname"] = os.path.join(thumbdir, os.path.basename(filename))[:-4] + "--%s.jpg" % config.Thumbnails["Suffix"]
     953      Img.SaveThumb(**Param)
     954      if filigrane:
     955          filigrane.substract(Img.f).save(filename, quality=config.FiligraneQuality, optimize=config.FiligraneOptimize, progressive=config.FiligraneOptimize)
     956          os.chmod(filename, config.DefaultFileMode)
     957
     958
     959
     960
     961
     962
     963 -def latin1_to_ascii (unicrap):
     964      """This takes a UNICODE string and replaces Latin-1 characters with
     965          something equivalent in 7-bit ASCII. It returns a plain ASCII string.
     966          This function makes a best effort to convert Latin-1 characters into
     967          ASCII equivalents. It does not just strip out the Latin-1 characters.
     968          All characters in the standard 7-bit ASCII range are preserved.
     969          In the 8th bit range all the Latin-1 accented letters are converted
     970          to unaccented equivalents. Most symbol characters are converted to
     971          something meaningful. Anything not converted is deleted.
     972      """
     973      xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A',
     974          0xc6:'Ae', 0xc7:'C',
     975          0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E',
     976          0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I',
     977          0xd0:'Th', 0xd1:'N',
     978          0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O',
     979          0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U',
     980          0xdd:'Y', 0xde:'th', 0xdf:'ss',
     981          0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a',
     982          0xe6:'ae', 0xe7:'c',
     983          0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e',
     984          0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i',
     985          0xf0:'th', 0xf1:'n',
     986          0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o',
     987          0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u',
     988          0xfd:'y', 0xfe:'th', 0xff:'y',
     989          0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}',
     990          0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}',
     991          0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}',
     992          0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}',
     993          0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'",
     994          0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}',
     995          0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>',
     996          0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?',
     997          0xd7:'*', 0xf7:'/'
     998          }
     999
    1000      r = []
    1001      for i in unicrap:
    1002          if xlate.has_key(ord(i)):
    1003              r.append(xlate[ord(i)])
    1004          elif ord(i) >= 0x80:
    1005              pass
    1006          else:
    1007              r.append(str(i))
    1008      return "".join(r)
    1009
    1010 -def SmartSize(size):
    1011      """print the size of files in a pretty way"""
    1012      unit = "o"
    1013      fsize = float(size)
    1014      if len(str(size)) > 3:
    1015          size /= 1024
    1016          fsize /= 1024.0
    1017          unit = "ko"
    1018          if len(str(size)) > 3:
    1019              size = size / 1024
    1020              fsize /= 1024.0
    1021              unit = "Mo"
    1022              if len(str(size)) > 3:
    1023                  size = size / 1024
    1024                  fsize /= 1024.0
    1025                  unit = "Go"
    1026      return fsize, unit
    1027
    1028
    1029  ################################################################################
    1030  # We should refactorize this with os.walk !!!
    1031  ################################################################################
    1032 -class parser:
    1033      """this class searches all the jpeg files"""
    1034 -    def __init__(self):
    1035          self.imagelist = []
    1036          self.root = None
    1037          self.suffix = None
    1038
    1039 -    def OneDir(self, curent):
    1040          """ append all the imagesfiles to the list, then goes recursively to the subdirectories"""
    1041          ls = os.listdir(curent)
    1042          for i in ls:
    1043              a = os.path.join(curent, i)
    1044              if    os.path.isdir(a):
    1045                  self.OneDir(a)
    1046              if  os.path.isfile(a):
    1047                  if i[(-len(self.suffix)):].lower() == self.suffix:
    1048                      self.imagelist.append(os.path.join(curent, i))
    1049 -    def FindExts(self, root, suffix):
    1050          self.root = root
    1051          self.suffix = suffix
    1052          self.OneDir(self.root)
    1053          return self.imagelist
    1054
    1055
    1056 -def recursive_delete(dirname):
    1057      files = os.listdir(dirname)
    1058      for filename in files:
    1059          path = os.path.join (dirname, filename)
    1060          if os.path.isdir(path):
    1061              recursive_delete(path)
    1062          else:
    1063              print 'Removing file: "%s"' % path
    1064              os.remove(path)
    1065
    1066      print 'Removing directory:', dirname
    1067      os.rmdir(dirname)
    1068
    1069
    1070
    1071
    1072  if __name__ == "__main__":
    1073      ####################################################################################
    1074      #Definition de la classe des variables de configuration globales : Borg"""
    1075      config.DefaultRepository = os.path.abspath(sys.argv[1])
    1076      print config.DefaultRepository
    1077      RangeTout(sys.argv[1])
    1078