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$ 
   5  #* $Id$ 
   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 
  33   
  34  from exiftran import Exiftran 
  35   
  36  try: 
  37      import Image, ImageStat, ImageChops, ImageFile 
  38  except: 
  39      raise ImportError("Selector needs PIL: Python Imaging Library\n PIL is available from http://www.pythonware.com/products/pil/") 
  40  try: 
  41      import pygtk ; pygtk.require('2.0') 
  42      import gtk 
  43      import gtk.glade as GTKglade 
  44  except ImportError: 
  45      raise ImportError("Selector needs pygtk and glade-2 available from http://www.pygtk.org/") 
  46  #Variables globales qui sont des CONSTANTES ! 
  47  gtkInterpolation = [gtk.gdk.INTERP_NEAREST, gtk.gdk.INTERP_TILES, gtk.gdk.INTERP_BILINEAR, gtk.gdk.INTERP_HYPER] 
  48  #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. 
  49  #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. 
  50  #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. 
  51  #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). 
  52   
  53   
  54  #here we detect the OS runnng the program so that we can call exftran in the right way 
  55  installdir = os.path.dirname(__file__) 
  56  if os.name == 'nt': #sys.platform == 'win32': 
  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      ConfFile = ["/etc/imagizer.conf", os.path.join(os.getenv("HOME"), ".imagizer"), ".imagizer"] 
  60   
  61  unifiedglade = os.path.join(installdir, "selector.glade") 
  62  from signals import Signal 
  63  from config import Config 
  64  config = Config() 
  65  config.load(ConfFile) 
  66  if config.ImageCache > 1: 
  67      import imagecache 
  68      imageCache = imagecache.ImageCache(maxSize=config.ImageCache) 
  69  else: 
  70      imageCache = None 
  71  import pyexiv2 
  72   
  73   
  74   
  75  #class Model: 
  76  #    """ Implémentation de l'applicatif 
  77  #    """ 
  78  #    def __init__(self, label): 
  79  #        """ 
  80  #        """ 
  81  #        self.__label = label 
  82  #        self.startSignal = Signal() 
  83  #        self.refreshSignal = Signal() 
  84  #         
  85  #    def start(self): 
  86  #        """ Lance les calculs 
  87  #        """ 
  88  #        self.startSignal.emit(self.__label, NBVALUES) 
  89  #        for i in xrange(NBVALUES): 
  90  #            time.sleep(0.5) 
  91  #             
  92  #            # On lève le signal de rafraichissement des vues éventuelles 
  93  #            # Note qu'ici on ne sait absolument pas si quelque chose s'affiche ou non 
  94  #            # ni de quelle façon c'est affiché. 
  95  #            self.refreshSignal.emit(i) 
  96   
  97   
  98   
99 -class ModelProcessSelected:
100 """Implemantation MVC de la procedure ProcessSelected"""
101 - def __init__(self):
102 """ 103 """ 104 self.__label = "Un moment..." 105 self.startSignal = Signal() 106 self.refreshSignal = Signal() 107 self.finishSignal = Signal() 108 self.NbrJobsSignal = Signal()
109 - def start(self, List):
110 """ Lance les calculs 111 """ 112 113 def SplitIntoPages(pathday, GlobalCount): 114 """Split a directory (pathday) into pages of 20 images""" 115 files = [] 116 for i in os.listdir(pathday): 117 if os.path.splitext(i)[1] in config.Extensions:files.append(i) 118 files.sort() 119 if len(files) > config.NbrPerPage: 120 pages = 1 + (len(files) - 1) / config.NbrPerPage 121 for i in range(1, pages + 1): 122 folder = os.path.join(pathday, config.PagePrefix + str(i)) 123 if not os.path.isdir(folder): mkdir(folder) 124 for j in range(len(files)): 125 i = 1 + (j) / config.NbrPerPage 126 filename = os.path.join(pathday, config.PagePrefix + str(i), files[j]) 127 self.refreshSignal.emit(GlobalCount, files[j]) 128 GlobalCount += 1 129 shutil.move(os.path.join(pathday, files[j]), filename) 130 ScaleImage(filename, filigrane) 131 else: 132 for j in files: 133 self.refreshSignal.emit(GlobalCount, j) 134 GlobalCount += 1 135 ScaleImage(os.path.join(pathday, j), filigrane) 136 return GlobalCount
137 def ArrangeOneFile(dirname, filename): 138 try: 139 timetuple = time.strptime(filename[:19], "%Y-%m-%d_%Hh%Mm%S") 140 suffix = filename[19:] 141 except ValueError: 142 try: 143 timetuple = time.strptime(filename[:11], "%Y-%m-%d_") 144 suffix = filename[11:] 145 except ValueError: 146 print("Unable to handle such file: %s" % filename) 147 return 148 daydir = os.path.join(SelectedDir, time.strftime("%Y-%m-%d", timetuple)) 149 if not os.path.isdir(daydir): 150 os.mkdir(daydir) 151 shutil.move(os.path.join(dirname, filename), os.path.join(daydir, time.strftime("%Hh%Mm%S", timetuple) + suffix))
152 153 self.startSignal.emit(self.__label, max(1, len(List))) 154 if config.Filigrane: 155 filigrane = signature(config.FiligraneSource) 156 else: 157 filigrane = None 158 159 SelectedDir = os.path.join(config.DefaultRepository, config.SelectedDirectory) 160 self.refreshSignal.emit(-1, "copie des fichiers existants") 161 if not os.path.isdir(SelectedDir): mkdir(SelectedDir) 162 #####first of all : copy the subfolders into the day folder to help mixing the files 163 AlsoProcess = 0 164 for day in os.listdir(SelectedDir): 165 #if SingleDir : revert to a foldered structure 166 DayOrFile = os.path.join(SelectedDir, day) 167 if os.path.isfile(DayOrFile): 168 ArrangeOneFile(SelectedDir, day) 169 AlsoProcess += 1 170 #end SingleDir normalization 171 elif os.path.isdir(DayOrFile): 172 if day in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]: 173 recursive_delete(DayOrFile) 174 elif day.find(config.PagePrefix) == 0: #subpages in SIngleDir mode that need to be flatten 175 for File in os.listdir(DayOrFile): 176 if os.path.isfile(os.path.join(DayOrFile, File)): 177 ArrangeOneFile(DayOrFile, File) 178 AlsoProcess += 1 179 # elif os.path.isdir(os.path.join(DayOrFile,File)) and File in [config.ScaledImages["Suffix"],config.Thumbnails["Suffix"]]: 180 # recursive_delete(os.path.join(DayOrFile,File)) 181 recursive_delete(DayOrFile) 182 else: 183 for File in os.listdir(DayOrFile): 184 if File.find(config.PagePrefix) == 0: 185 if os.path.isdir(os.path.join(SelectedDir, day, File)): 186 for strImageFile in os.listdir(os.path.join(SelectedDir, day, File)): 187 src = os.path.join(SelectedDir, day, File, strImageFile) 188 dst = os.path.join(SelectedDir, day, strImageFile) 189 if os.path.isfile(src) and not os.path.exists(dst): 190 shutil.move(src, dst) 191 AlsoProcess += 1 192 if (os.path.isdir(src)) and (os.path.split(src)[1] in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]): 193 shutil.rmtree(src) 194 else: 195 if os.path.splitext(File)[1] in config.Extensions: 196 AlsoProcess += 1 197 198 #######then copy the selected files to their folders########################### 199 for File in List: 200 dest = os.path.join(SelectedDir, File) 201 src = os.path.join(config.DefaultRepository, File) 202 destdir = os.path.dirname(dest) 203 if not os.path.isdir(destdir): makedir(destdir) 204 if not os.path.exists(dest): 205 print "copie de %s " % (File) 206 shutil.copy(src, dest) 207 try: 208 os.chmod(dest, config.DefaultFileMode) 209 except OSError: 210 print("Warning: unable to chmod %s" % dest) 211 AlsoProcess += 1 212 else : 213 print "%s existe déja" % (dest) 214 if AlsoProcess > 0:self.NbrJobsSignal.emit(AlsoProcess) 215 ######copy the comments of the directory to the Selected directory 216 AlreadyDone = [] 217 for File in List: 218 directory = os.path.split(File)[0] 219 if directory in AlreadyDone: 220 continue 221 else: 222 AlreadyDone.append(directory) 223 dst = os.path.join(SelectedDir, directory, config.CommentFile) 224 src = os.path.join(config.DefaultRepository, directory, config.CommentFile) 225 if os.path.isfile(src): 226 shutil.copy(src, dst) 227 228 ########finaly recreate the structure with pages or make a single page ######################## 229 dirs = os.listdir(SelectedDir) 230 dirs.sort() 231 # print "config.ExportSingleDir = "+str(config.ExportSingleDir) 232 if config.ExportSingleDir: #SingleDir 233 #first move all files to the root 234 for day in dirs: 235 daydir = os.path.join(SelectedDir, day) 236 for filename in os.listdir(daydir): 237 try: 238 timetuple = time.strptime(day[:10] + "_" + filename[:8], "%Y-%m-%d_%Hh%Mm%S") 239 suffix = filename[8:] 240 except ValueError: 241 try: 242 timetuple = time.strptime(day[:10], "%Y-%m-%d") 243 suffix = filename 244 except ValueError: 245 print ("Unable to handle dir: %s\t file: %s" % (day, filename)) 246 continue 247 src = os.path.join(daydir, filename) 248 dst = os.path.join(SelectedDir, time.strftime("%Y-%m-%d_%Hh%Mm%S", timetuple) + suffix) 249 shutil.move(src, dst) 250 recursive_delete(daydir) 251 SplitIntoPages(SelectedDir, 0) 252 else: #Multidir 253 GlobalCount = 0 254 for day in dirs: 255 GlobalCount = SplitIntoPages(os.path.join(SelectedDir, day), GlobalCount) 256 257 self.finishSignal.emit() 258 259 260
261 -class ModelCopySelected:
262 """Implemantation MVC de la procedure CopySelected"""
263 - def __init__(self):
264 """ 265 """ 266 self.__label = "Un moment..." 267 self.startSignal = Signal() 268 self.refreshSignal = Signal() 269 self.finishSignal = Signal() 270 self.NbrJobsSignal = Signal()
271 - def start(self, List):
272 """ Lance les calculs 273 """ 274 self.startSignal.emit(self.__label, max(1, len(List))) 275 if config.Filigrane: 276 filigrane = signature(config.FiligraneSource) 277 else: 278 filigrane = None 279 280 SelectedDir = os.path.join(config.DefaultRepository, config.SelectedDirectory) 281 self.refreshSignal.emit(-1, "copie des fichiers existants") 282 if not os.path.isdir(SelectedDir): mkdir(SelectedDir) 283 #####first of all : copy the subfolders into the day folder to help mixing the files 284 for day in os.listdir(SelectedDir): 285 for File in os.listdir(os.path.join(SelectedDir, day)): 286 if File.find(config.PagePrefix) == 0: 287 if os.path.isdir(os.path.join(SelectedDir, day, File)): 288 for strImageFile in os.listdir(os.path.join(SelectedDir, day, File)): 289 src = os.path.join(SelectedDir, day, File, strImageFile) 290 dst = os.path.join(SelectedDir, day, strImageFile) 291 if os.path.isfile(src) and not os.path.exists(dst): 292 shutil.move(src, dst) 293 if (os.path.isdir(src)) and (os.path.split(src)[1] in [config.ScaledImages["Suffix"], config.Thumbnails["Suffix"]]): 294 shutil.rmtree(src) 295 296 #######then copy the selected files to their folders########################### 297 GlobalCount = 0 298 for File in List: 299 dest = os.path.join(SelectedDir, File) 300 src = os.path.join(config.DefaultRepository, File) 301 destdir = os.path.dirname(dest) 302 self.refreshSignal.emit(GlobalCount, File) 303 GlobalCount += 1 304 if not os.path.isdir(destdir): makedir(destdir) 305 if not os.path.exists(dest): 306 if filigrane: 307 Img = Image.open(src) 308 filigrane.substract(Img).save(dest, quality=config.FiligraneQuality, optimize=config.FiligraneOptimize, progressive=config.FiligraneOptimize) 309 else: 310 shutil.copy(src, dest) 311 try: 312 os.chmod(dest, config.DefaultFileMode) 313 except OSError: 314 print("Warning: unable to chmod %s" % dest) 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 = findFiles(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 myPhoto = photo(i) 379 try: 380 imageCache[i] = myPhoto 381 except: 382 pass 383 data = myPhoto.readExif() 384 try: 385 datei, heurei = data["Heure"].split() 386 date = re.sub(":", "-", datei) 387 heurej = re.sub(":", "h", heurei, 1) 388 model = data["Modele"].split(",")[-1] 389 heure = unicode_to_ascii("%s-%s.jpg" % (re.sub(":", "m", heurej, 1), re.sub("/", "", re.sub(" ", "_", model)))) 390 except ValueError: 391 date = time.strftime("%Y-%m-%d", time.gmtime(os.path.getctime(os.path.join(RootDir, i)))) 392 heure = unicode_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])))) 393 if not (os.path.isdir(os.path.join(RootDir, date))) : mkdir(os.path.join(RootDir, date)) 394 strImageFile = os.path.join(RootDir, date, heure) 395 ToProcess = os.path.join(date, heure) 396 if os.path.isfile(strImageFile): 397 print "Problème ... %s existe déja " % i 398 s = 0 399 for j in os.listdir(os.path.join(RootDir, date)): 400 if j.find(heure[:-4]) == 0:s += 1 401 ToProcess = os.path.join(date, heure[:-4] + "-%s.jpg" % s) 402 strImageFile = os.path.join(RootDir, ToProcess) 403 shutil.move(os.path.join(RootDir, i), strImageFile) 404 try: 405 os.chown(strImageFile, uid, gid) 406 os.chmod(strImageFile, config.DefaultFileMode) 407 except OSError: 408 print "Warning: unable to chown ot chmod %s" % strImageFile 409 myPhoto = photo(strImageFile) 410 # Save the old image name in exif tag 411 myPhoto.storeOriginalName(i) 412 413 if config.AutoRotate: 414 myPhoto.autorotate() 415 416 #Set the new images in cache for further display 417 try: 418 imageCache[ ToProcess ] = myPhoto 419 except: 420 pass 421 ################################################## 422 AllreadyDone.append(ToProcess) 423 NewFiles.append(ToProcess) 424 AllreadyDone.sort() 425 self.finishSignal.emit() 426 427 if len(NewFiles) > 0: 428 FirstImage = min(NewFiles) 429 return AllreadyDone, AllreadyDone.index(FirstImage) 430 else: 431 return AllreadyDone, 0
432
433 -class Controler:
434 """ Implémentation du contrôleur de la vue utilisant la console"""
435 - def __init__(self, model, view):
436 # self.__model = model # Ne sert pas ici, car on ne fait que des actions modèle -> vue 437 self.__view = view 438 439 # Connection des signaux 440 model.startSignal.connect(self.__startCallback) 441 model.refreshSignal.connect(self.__refreshCallback) 442 model.finishSignal.connect(self.__stopCallback) 443 model.NbrJobsSignal.connect(self.__NBJCallback)
444 - def __startCallback(self, label, nbVal):
445 """ Callback pour le signal de début de progressbar.""" 446 self.__view.creatProgressBar(label, nbVal)
447 - def __refreshCallback(self, i, filename):
448 """ Mise à jour de la progressbar.""" 449 self.__view.updateProgressBar(i, filename)
450 - def __stopCallback(self):
451 """ Callback pour le signal de fin de splashscreen.""" 452 self.__view.finish()
453 - def __NBJCallback(self, NbrJobs):
454 """ Callback pour redefinir le nombre de job totaux.""" 455 self.__view.ProgressBarMax(NbrJobs)
456 457 458
459 -class ControlerX:
460 """ Implémentation du contrôleur. C'est lui qui lie les modèle et la(les) vue(s)."""
461 - def __init__(self, model, viewx):
462 # self.__model = model # Ne sert pas ici, car on ne fait que des actions modèle -> vue 463 self.__viewx = viewx 464 # Connection des signaux 465 model.startSignal.connect(self.__startCallback) 466 model.refreshSignal.connect(self.__refreshCallback) 467 model.finishSignal.connect(self.__stopCallback) 468 model.NbrJobsSignal.connect(self.__NBJCallback)
469 - def __startCallback(self, label, nbVal):
470 """ Callback pour le signal de début de progressbar.""" 471 self.__viewx.creatProgressBar(label, nbVal)
472 - def __refreshCallback(self, i, filename):
473 """ Mise à jour de la progressbar. """ 474 self.__viewx.updateProgressBar(i, filename)
475 - def __stopCallback(self):
476 """ ferme la fenetre. Callback pour le signal de fin de splashscreen.""" 477 self.__viewx.finish()
478 - def __NBJCallback(self, NbrJobs):
479 """ Callback pour redefinir le nombre de job totaux.""" 480 self.__viewx.ProgressBarMax(NbrJobs)
481 482 483
484 -class View:
485 """ Implémentation de la vue. 486 Utilisation de la console. 487 """
488 - def __init__(self):
489 """ On initialise la vue.""" 490 self.__nbVal = None
491 - def creatProgressBar(self, label, nbVal):
492 """ Création de la progressbar. """ 493 self.__nbVal = nbVal 494 print label
495
496 - def ProgressBarMax(self, nbVal):
497 """re-definit le nombre maximum de la progress-bar""" 498 self.__nbVal = nbVal
499 # print "Modification du maximum : %i"%self.__nbVal 500
501 - def updateProgressBar(self, h, filename):
502 """ Mise à jour de la progressbar 503 """ 504 print "%5.1f %% processing ... %s" % (100.0 * (h + 1) / self.__nbVal, filename)
505 - def finish(self):
506 """nothin in text mode""" 507 pass
508
509 -class ViewX:
510 """ 511 Implementation of the view as a splashscren 512 """
513 - def __init__(self):
514 """ 515 Initialization of the view in the constructor 516 517 Ici, on ne fait rien, car la progressbar sera créée au moment 518 où on en aura besoin. Dans un cas réel, on initialise les widgets 519 de l'interface graphique 520 """ 521 self.__nbVal = None 522 self.xml = None 523 self.pb = None
524
525 - def creatProgressBar(self, label, nbVal):
526 """ 527 Creation of a progress bar. 528 """ 529 self.xml = GTKglade.XML(unifiedglade, root="splash") 530 self.xml.get_widget("image").set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(os.path.join(installdir, "Splash.png"))) 531 self.pb = self.xml.get_widget("progress") 532 self.xml.get_widget("splash").set_title(label) 533 self.xml.get_widget("splash").show() 534 while gtk.events_pending():gtk.main_iteration() 535 self.__nbVal = nbVal
536
537 - def ProgressBarMax(self, nbVal):
538 """re-definit le nombre maximum de la progress-bar""" 539 self.__nbVal = nbVal
540 541
542 - def updateProgressBar(self, h, filename):
543 """ 544 Update the progress-bar to the given value with the given filename writen on it 545 546 fr: Mise à jour de la progressbar 547 fr: Dans le cas d'un toolkit, c'est ici qu'il faudra appeler le traitement 548 fr:des évènements. 549 550 @param h: current number of the file 551 @type h: integer or float 552 @param filename: name of the current element 553 @type filename: string 554 @return: None 555 """ 556 if h < self.__nbVal: 557 self.pb.set_fraction(float(h + 1) / self.__nbVal) 558 else: 559 self.pb.set_fraction(1.0) 560 self.pb.set_text(filename) 561 while gtk.events_pending():gtk.main_iteration()
562 - def finish(self):
563 """destroys the interface of the splash screen""" 564 self.xml.get_widget("splash").destroy() 565 while gtk.events_pending():gtk.main_iteration() 566 del self.xml 567 gc.collect()
568 569
570 -def RangeTout(repository, bUseX=True):
571 """moves all the JPEG files to a directory named from their day and with the 572 name according to the time 573 This is a MVC implementation 574 575 @param repository: the name of the starting repository 576 @type repository: string 577 @param bUseX: set to False to disable the use of the graphical splash screen 578 @type bUseX: boolean 579 """ 580 model = ModelRangeTout() 581 view = View() 582 Controler(model, view) 583 if bUseX: 584 viewx = ViewX() 585 ControlerX(model, viewx) 586 return model.start(repository)
587 588 589
590 -def ProcessSelected(SelectedFiles):
591 """This procedure uses the MVC implementation of processSelected 592 It makes a copy of all selected photos and scales them 593 copy all the selected files to "selected" subdirectory, 20 per page 594 """ 595 print "execution %s" % SelectedFiles 596 model = ModelProcessSelected() 597 view = View() 598 Controler(model, view) 599 viewx = ViewX() 600 ControlerX(model, viewx) 601 model.start(SelectedFiles)
602
603 -def CopySelected(SelectedFiles):
604 """This procedure makes a copy of all selected photos and scales them 605 copy all the selected files to "selected" subdirectory 606 """ 607 print "Copy %s" % SelectedFiles 608 model = ModelCopySelected() 609 view = View() 610 Controler(model, view) 611 viewx = ViewX() 612 ControlerX(model, viewx) 613 model.start(SelectedFiles)
614 615 616 617 618 ########################################################## 619 # # # # # # Début de la classe photo # # # # # # # # # # # 620 ##########################################################
621 -class photo(object):
622 """class photo that does all the operations available on photos""" 623 GaussianKernel = None 624
625 - def __init__(self, filename):
626 self.filename = filename 627 self.fn = os.path.join(config.DefaultRepository, self.filename) 628 self.metadata = None 629 self.pixelsX = None 630 self.pixelsY = None 631 self.pil = None 632 self.exif = None 633 if not os.path.isfile(self.fn): 634 print "Erreur, le fichier %s n'existe pas" % self.fn 635 # self.bImageCache = (imageCache is not None) 636 self.scaledPixbuffer = None 637 self.orientation = 1
638
639 - def LoadPIL(self):
640 """Load the image""" 641 self.pil = Image.open(self.fn)
642
643 - def larg(self):
644 """width-height of a jpeg file""" 645 self.taille() 646 return self.pixelsX - self.pixelsY
647
648 - def taille(self):
649 """width and height of a jpeg file""" 650 if self.pixelsX == None and self.pixelsY == None: 651 self.LoadPIL() 652 self.pixelsX, self.pixelsY = self.pil.size
653
654 - def SaveThumb(self, strThumbFile, Size=160, Interpolation=1, Quality=75, Progressive=False, Optimize=False, ExifExtraction=False):
655 """save a thumbnail of the given name, with the given size and the interpolation methode (quality) 656 resampling filters : 657 NONE = 0 658 NEAREST = 0 659 ANTIALIAS = 1 # 3-lobed lanczos 660 LINEAR = BILINEAR = 2 661 CUBIC = BICUBIC = 3 662 """ 663 if os.path.isfile(strThumbFile): 664 print "sorry, file %s exists" % strThumbFile 665 else: 666 if self.exif is None: 667 self.exif = pyexiv2.Image(self.fn) 668 self.exif.readMetadata() 669 extract = False 670 print "process file %s exists" % strThumbFile 671 if ExifExtraction: 672 try: 673 self.exif.dumpThumbnailToFile(strThumbFile[:-4]) 674 extract = True 675 except (OSError, IOError): 676 extract = False 677 #Check if the thumbnail is correctly oriented 678 if os.path.isfile(strThumbFile): 679 thumbImag = photo(strThumbFile) 680 if self.larg()*thumbImag.larg() < 0: 681 print("Warning: thumbnail was not with the same orientation as original: %s" % self.filename) 682 os.remove(strThumbFile) 683 extract = False 684 if not extract: 685 # print "on essaie avec PIL" 686 if self.pil is None: 687 self.LoadPIL() 688 copyOfImage = self.pil.copy() 689 copyOfImage.thumbnail((Size, Size), Interpolation) 690 copyOfImage.save(strThumbFile, quality=Quality, progressive=Progressive, optimize=Optimize) 691 try: 692 os.chmod(strThumbFile, config.DefaultFileMode) 693 except OSError: 694 print("Warning: unable to chmod %s" % strThumbFile)
695 696
697 - def Rotate(self, angle=0):
698 """does a looseless rotation of the given jpeg file""" 699 if os.name == 'nt' and self.pil != None: 700 del self.pil 701 self.taille() 702 x = self.pixelsX 703 y = self.pixelsY 704 if config.DEBUG: 705 print("Before rotation %i, x=%i, y=%i, scaledX=%i, scaledY=%i" % (angle, x, y, self.scaledPixbuffer.get_width(), self.scaledPixbuffer.get_height())) 706 707 if angle == 90: 708 if imageCache is not None: 709 Exiftran.rotate90(self.fn) 710 # os.system('%s -ip -9 "%s" &' % (exiftran, self.fn)) 711 newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_CLOCKWISE) 712 self.pixelsX = y 713 self.pixelsY = x 714 self.metadata["Resolution"] = "%i x % i" % (y, x) 715 else: 716 Exiftran.rotate90(self.fn) 717 # os.system('%s -ip -9 "%s" ' % (exiftran, self.fn)) 718 self.pixelsX = None 719 self.pixelsY = None 720 elif angle == 270: 721 if imageCache is not None: 722 Exiftran.rotate270(self.fn) 723 # os.system('%s -ip -2 "%s" &' % (exiftran, self.fn)) 724 newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE) 725 self.pixelsX = y 726 self.pixelsY = x 727 self.metadata["Resolution"] = "%i x % i" % (y, x) 728 else: 729 Exiftran.rotate270(self.fn) 730 # os.system('%s -ip -2 "%s" ' % (exiftran, self.fn)) 731 self.pixelsX = None 732 self.pixelsY = None 733 elif angle == 180: 734 if imageCache is not None: 735 Exiftran.rotate180(self.fn) 736 # os.system('%s -ip -1 "%s" &' % (exiftran, self.fn)) 737 newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN) 738 else: 739 Exiftran.rotate180(self.fn) 740 # os.system('%s -ip -1 "%s" ' % (exiftran, self.fn)) 741 self.pixelsX = None 742 self.pixelsY = None 743 else: 744 print "Erreur ! il n'est pas possible de faire une rotation de ce type sans perte de donnée." 745 if imageCache is not None: 746 self.scaledPixbuffer = newPixbuffer 747 if config.DEBUG: 748 print("After rotation %i, x=%i, y=%i, scaledX=%i, scaledY=%i" % (angle, self.pixelsX, self.pixelsY, self.scaledPixbuffer.get_width(), self.scaledPixbuffer.get_height()))
749 750
751 - def RemoveFromCache(self):
752 """remove the curent image from the Cache .... for various reasons""" 753 if imageCache is not None: 754 if self.filename in imageCache.ordered: 755 imageCache.imageDict.pop(self.filename) 756 index = imageCache.ordered.index(self.filename) 757 imageCache.ordered.pop(index) 758 imageCache.size -= 1
759 760
761 - def Trash(self):
762 """Send the file to the trash folder""" 763 self.RemoveFromCache() 764 Trashdir = os.path.join(config.DefaultRepository, config.TrashDirectory) 765 td = os.path.dirname(os.path.join(Trashdir, self.filename)) 766 if not os.path.isdir(td): makedir(td) 767 shutil.move(self.fn, os.path.join(Trashdir, self.filename))
768 769
770 - def readExif(self):
771 """return exif data + title from the photo""" 772 clef = {'Exif.Image.Make':'Marque', 773 'Exif.Image.Model':'Modele', 774 'Exif.Photo.DateTimeOriginal':'Heure', 775 'Exif.Photo.ExposureTime':'Vitesse', 776 'Exif.Photo.FNumber':'Ouverture', 777 # 'Exif.Photo.DateTimeOriginal':'Heure2', 778 'Exif.Photo.ExposureBiasValue':'Bias', 779 'Exif.Photo.Flash':'Flash', 780 'Exif.Photo.FocalLength':'Focale', 781 'Exif.Photo.ISOSpeedRatings':'Iso' , 782 # 'Exif.Image.Orientation':'Orientation' 783 } 784 785 if self.metadata is None: 786 self.metadata = {} 787 self.metadata["Taille"] = "%.2f %s" % SmartSize(os.path.getsize(self.fn)) 788 self.exif = pyexiv2.Image(self.fn) 789 self.exif.readMetadata() 790 self.metadata["Titre"] = self.exif.getComment() 791 if self.pixelsX and self.pixelsY: 792 self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY) 793 else: 794 try: 795 self.pixelsX = self.exif["Exif.Photo.PixelXDimension"] 796 self.pixelsY = self.exif["Exif.Photo.PixelYDimension"] 797 except (IndexError, KeyError): 798 self.taille() 799 self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY) 800 if "Exif.Image.Orientation" in self.exif.exifKeys(): 801 self.orientation = self.exif["Exif.Image.Orientation"] 802 for key in clef: 803 try: 804 self.metadata[clef[key]] = self.exif.interpretedExifValue(key).decode(config.Coding).strip() 805 except: 806 self.metadata[clef[key]] = u"" 807 return self.metadata.copy()
808 809
810 - def has_title(self):
811 """return true if the image is entitled""" 812 if self.metadata == None: 813 self.readExif() 814 if self.metadata["Titre"]: 815 return True 816 else: 817 return False
818 819
820 - def show(self, Xsize=600, Ysize=600):
821 """return a pixbuf to shows the image in a Gtk window""" 822 scaled_buf = None 823 if Xsize > config.ImageWidth : 824 config.ImageWidth = Xsize 825 if Ysize > config.ImageHeight: 826 config.ImageHeight = Ysize 827 self.taille() 828 829 # Prepare the big image to be put in cache 830 Rbig = min(float(config.ImageWidth) / self.pixelsX, float(config.ImageHeight) / self.pixelsY) 831 if Rbig < 1: 832 nxBig = int(round(Rbig * self.pixelsX)) 833 nyBig = int(round(Rbig * self.pixelsY)) 834 else: 835 nxBig = self.pixelsX 836 nyBig = self.pixelsY 837 838 R = min(float(Xsize) / self.pixelsX, float(Ysize) / self.pixelsY) 839 if R < 1: 840 nx = int(round(R * self.pixelsX)) 841 ny = int(round(R * self.pixelsY)) 842 else: 843 nx = self.pixelsX 844 ny = self.pixelsY 845 846 if self.scaledPixbuffer is None: 847 pixbuf = gtk.gdk.pixbuf_new_from_file(self.fn) 848 # Put in Cache the "BIG" image 849 if Rbig < 1: 850 self.scaledPixbuffer = pixbuf.scale_simple(nxBig, nyBig, gtkInterpolation[config.Interpolation]) 851 else : 852 self.scaledPixbuffer = pixbuf 853 if config.DEBUG: 854 print("Sucessfully cached %s, size (%i,%i)" % (self.filename, nxBig, nyBig)) 855 if (self.scaledPixbuffer.get_width() == nx) and (self.scaledPixbuffer.get_height() == ny): 856 scaled_buf = self.scaledPixbuffer 857 if config.DEBUG: 858 print("Sucessfully fetched %s from cache, directly with the right size." % (self.filename)) 859 else: 860 if config.DEBUG: 861 print("%s pixmap in buffer but not with the right shape: nx=%i,\tny=%i,\tw=%i,h=%i" % (self.filename, nx, ny, self.scaledPixbuffer.get_width(), self.scaledPixbuffer.get_height())) 862 scaled_buf = self.scaledPixbuffer.scale_simple(nx, ny, gtkInterpolation[config.Interpolation]) 863 return scaled_buf
864 865
866 - def name(self, titre):
867 """write the title of the photo inside the description field, in the JPEG header""" 868 if os.name == 'nt' and self.pil != None: 869 self.pil = None 870 self.metadata["Titre"] = titre 871 self.exif.setComment(titre) 872 self.exif.writeMetadata()
873 874
875 - def renameFile(self, newname):
876 """ 877 rename the current instance of photo: 878 -Move the file 879 -update the cache 880 -change the name and other attributes of the instance 881 -change the exif metadata. 882 """ 883 oldname = self.filename 884 newfn = os.path.join(config.DefaultRepository, newname) 885 os.rename(self.fn, newfn) 886 self.filename = newname 887 self.fn = newfn 888 self.exif = newfn 889 if self.exif is not None: 890 self.exif = pyexiv2.Image(self.fn) 891 self.exif.readMetadata() 892 if (imageCache is not None) and oldname in imageCache: 893 imageCache.rename(oldname, newname)
894 895
896 - def storeOriginalName(self, originalName):
897 """ 898 Save the original name of the file into the Exif.Photo.UserComment tag. 899 This tag is usually not used, people prefer the JPEG tag for entiteling images. 900 901 @param originalName: name of the file before it was processed by selector 902 @type originalName: python string 903 """ 904 if self.metadata == None: 905 self.readExif() 906 self.exif["Exif.Photo.UserComment"] = originalName 907 self.exif.writeMetadata()
908 909
910 - def autorotate(self):
911 """does autorotate the image according to the EXIF tag""" 912 if os.name == 'nt' and self.pil is not None: 913 del self.pil 914 915 self.readExif() 916 if self.orientation != 1: 917 Exiftran.autorotate(self.fn) 918 # os.system('%s -aip "%s" &' % (exiftran, self.fn)) 919 if self.orientation > 4: 920 self.pixelsX = self.exif["Exif.Photo.PixelYDimension"] 921 self.pixelsY = self.exif["Exif.Photo.PixelXDimension"] 922 self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY) 923 self.orientation = 1
924 925
926 - def ContrastMask(self, outfile):
927 """Ceci est un filtre de debouchage de photographies, aussi appelé masque de contraste, il permet de rattrapper une photo trop contrasté, un contre jour, ... 928 Écrit par Jérôme Kieffer, avec l'aide de la liste python@aful, en particulier A. Fayolles et F. Mantegazza 929 avril 2006 930 necessite numpy et PIL.""" 931 try: 932 import numpy 933 import scipy 934 import scipy.signal as signal 935 except: 936 raise ImportError("This filter needs the numpy library available on https://sourceforge.net/projects/numpy/files/") 937 938 if photo.GaussianKernel is None: 939 size = 15 940 x, y = numpy.mgrid[-size:size + 1, -size:size + 1] 941 g = numpy.exp(-(x ** 2 / float(size) + y ** 2 / float(size))) 942 photo.GaussianKernel = g / g.sum() 943 944 self.LoadPIL() 945 x, y = self.pil.size 946 ImageFile.MAXBLOCK = x * y 947 img_array = numpy.fromstring(self.pil.tostring(), dtype="UInt8").astype("UInt16") 948 img_array.shape = (y, x, 3) 949 red, green, blue = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2] 950 desat_array = (numpy.minimum(numpy.minimum(red, green), blue) + numpy.maximum(numpy.maximum(red, green), blue)) / 2 951 inv_desat = 255 - desat_array 952 blured_inv_desat = signal.convolve(inv_desat, photo.GaussianKernel, mode='valid') 953 954 k = Image.fromarray(blured_inv_desat, "L").convert("RGB") 955 S = ImageChops.screen(self.pil, k) 956 M = ImageChops.multiply(self.pil, k) 957 F = ImageChops.add(ImageChops.multiply(self.pil, S), ImageChops.multiply(ImageChops.invert(self.pil), M)) 958 F.save(os.path.join(config.DefaultRepository, outfile), quality=90, progressive=True, Optimize=True) 959 try: 960 os.chmod(os.path.join(config.DefaultRepository, outfile), config.DefaultFileMode) 961 except: 962 print("Warning: unable to chmod %s" % outfile)
963 964 965 ######################################################## 966 # # # # # # fin de la classe photo # # # # # # # # # # # 967 ######################################################## 968
969 -class signature:
970 - def __init__(self, filename):
971 """ 972 this filter allows add a signature to an image 973 """ 974 self.img = None 975 self.sig = Image.open(filename) 976 self.sig.convert("RGB") 977 (self.xs, self.ys) = self.sig.size 978 self.bigsig = self.sig 979 #The signature file is entented to be white on a black background, this inverts the color if necessary 980 if ImageStat.Stat(self.sig)._getmean() > 127: 981 self.sig = ImageChops.invert(self.sig) 982 983 self.orientation = -1 #this is an impossible value 984 (self.x, self.y) = (self.xs, self.ys)
985
986 - def mask(self, orientation=5):
987 """ 988 x and y are the size of the initial image 989 the orientation correspond to the position on a clock : 990 0 for the center 991 1 or 2 upper right 992 3 centered in heith right side ....""" 993 if orientation == self.orientation and (self.x, self.y) == self.bigsig.size: 994 #no need to change the mask 995 return 996 self.orientation = orientation 997 self.bigsig = Image.new("RGB", (self.x, self.y), (0, 0, 0)) 998 if self.x < self.xs or self.y < self.ys : 999 #the signature is larger than the image 1000 return 1001 if self.orientation == 0: 1002 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)) 1003 elif self.orientation in [1, 2]: 1004 self.bigsig.paste(self.sig, (self.x - self.xs, 0, self.x, self.ys)) 1005 elif self.orientation == 3: 1006 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)) 1007 elif self.orientation in [ 5, 4]: 1008 self.bigsig.paste(self.sig, (self.x - self.xs, self.y - self.ys, self.x, self.y)) 1009 elif self.orientation == 6: 1010 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)) 1011 elif self.orientation in [7, 8]: 1012 self.bigsig.paste(self.sig, (0, self.y - self.ys, self.xs, self.y)) 1013 elif self.orientation == 9: 1014 self.bigsig.paste(self.sig, (0, self.y / 2 - self.ys / 2, self.xs, self.y / 2 - self.ys / 2 + self.ys)) 1015 elif self.orientation in [10, 11]: 1016 self.bigsig.paste(self.sig, (0, 0, self.xs, self.ys)) 1017 elif self.orientation == 12: 1018 self.bigsig.paste(self.sig, (self.x / 2 - self.xs / 2, 0, self.x / 2 - self.xs / 2 + self.xs, self.ys)) 1019 return
1020
1021 - def substract(self, inimage, orientation=5):
1022 """apply a substraction mask on the image""" 1023 self.img = inimage 1024 self.x, self.y = self.img.size 1025 ImageFile.MAXBLOCK = self.x * self.y 1026 self.mask(orientation) 1027 k = ImageChops.difference(self.img, self.bigsig) 1028 return k
1029
1030 -class RawImage:
1031 """ class for handling raw images 1032 - extract thumbnails 1033 - copy them in the repository 1034 """
1035 - def __init__(self, strRawFile):
1036 """ 1037 Contructor of the class 1038 1039 @param strRawFile: path to the RawImage 1040 @type strRawFile: string 1041 """ 1042 self.strRawFile = strRawFile 1043 self.exif = None 1044 self.strJepgFile = None 1045 # if config.DEBUG: 1046 print("Importing [Raw|Jpeg] image %s" % strRawFile)
1047
1048 - def getJpegPath(self):
1049 1050 if self.exif is None: 1051 self.exif = pyexiv2.Image(self.strRawFile) 1052 self.exif.readMetadata() 1053 if self.strJepgFile is None: 1054 self.strJepgFile = unicode_to_ascii("%s-%s.jpg" % ( 1055 self.exif.interpretedExifValue("Exif.Photo.DateTimeOriginal").replace(" ", os.sep).replace(":", "-", 2).replace(":", "h", 1).replace(":", "m", 1), 1056 self.exif.interpretedExifValue("Exif.Image.Model").strip().split(",")[-1].replace("/", "").replace(" ", "_") 1057 )) 1058 while os.path.isfile(os.path.join(config.DefaultRepository, self.strJepgFile)): 1059 number = "" 1060 idx = None 1061 listChar = list(self.strJepgFile[:-4]) 1062 listChar.reverse() 1063 for val in listChar: 1064 if val.isdigit(): 1065 number = val + number 1066 elif val == "-": 1067 idx = int(number) 1068 break 1069 else: 1070 break 1071 if idx is None: 1072 self.strJepgFile = self.strJepgFile[:-4] + "-1.jpg" 1073 else: 1074 self.strJepgFile = self.strJepgFile[:-5 - len(number)] + "-%i.jpg" % (idx + 1) 1075 dirname = os.path.dirname(os.path.join(config.DefaultRepository, self.strJepgFile)) 1076 if not os.path.isdir(dirname): 1077 makedir(dirname) 1078 1079 return self.strJepgFile
1080
1081 - def extractJPEG(self):
1082 """ 1083 extract the raw image to its right place 1084 """ 1085 extension = os.path.splitext(self.strRawFile)[1].lower() 1086 strJpegFullPath = os.path.join(config.DefaultRepository, self.getJpegPath()) 1087 if extension in config.RawExtensions: 1088 data = os.popen("%s %s" % (config.Dcraw, self.strRawFile)).readlines() 1089 img = Image.fromstring("RGB", tuple([int(i) for i in data[1].split()]), "".join(tuple(data[3:]))) 1090 img.save(strJpegFullPath, format='JPEG') 1091 #Copy all metadata useful for us. 1092 exifJpeg = pyexiv2.Image(strJpegFullPath) 1093 exifJpeg.readMetadata() 1094 exifJpeg['Exif.Image.Orientation'] = 1 1095 exifJpeg["Exif.Photo.UserComment"] = self.strRawFile 1096 for metadata in [ 'Exif.Image.Make', 'Exif.Image.Model', 'Exif.Photo.DateTimeOriginal', 'Exif.Photo.ExposureTime', 'Exif.Photo.FNumber', 'Exif.Photo.ExposureBiasValue', 'Exif.Photo.Flash', 'Exif.Photo.FocalLength', 'Exif.Photo.ISOSpeedRatings']: 1097 try: 1098 exifJpeg[metadata] = self.exif[metadata] 1099 except: 1100 print("error in copying metadata %s in file %s, value: %s" % (metadata, self.strRawFile, self.exif[metadata])) 1101 #self.exif.copyMetadataTo(self.strJepgFile) 1102 exifJpeg.writeMetadata() 1103 1104 else: #in config.Extensions, i.e. a JPEG file 1105 shutil.copy(self.strRawFile, strJpegFullPath) 1106 Exiftran.autorotate(strJpegFullPath) 1107 1108 os.chmod(strJpegFullPath, config.DefaultFileMode)
1109 1110 1111 1112 1113 1114 ############################################################################################################ 1115
1116 -def makedir(filen):
1117 """creates the tree structure for the file""" 1118 dire = os.path.dirname(filen) 1119 if os.path.isdir(dire): 1120 mkdir(filen) 1121 else: 1122 makedir(dire) 1123 mkdir(filen)
1124
1125 -def mkdir(filename):
1126 """create an empty directory with the given rights""" 1127 # config=Config() 1128 os.mkdir(filename) 1129 try: 1130 os.chmod(filename, config.DefaultDirMode) 1131 except OSError: 1132 print("Warning: unable to chmod %s" % filename)
1133
1134 -def findFiles(strRootDir, lstExtentions=config.Extensions, bFromRoot=False):
1135 """ 1136 Equivalent to: 1137 files=os.system('find "%s" -iname "*.%s"'%(RootDir,suffix)).readlines() 1138 1139 @param strRootDir: path of the root of the search 1140 @type strRootDir: string 1141 @param lstExtentions: list of string representing interesting extensions 1142 @param bFromRoot: start the return path from / instead of the strRootDir 1143 @return: the list of the files with the given suffix in the given dir 1144 @rtype: list of strings 1145 """ 1146 listFiles = [] 1147 if strRootDir.endswith("os.sep"): 1148 lenRoot = len(strRootDir) 1149 else: 1150 lenRoot = len(strRootDir) + 1 1151 for root, dirs, files in os.walk(strRootDir): 1152 for oneFile in files: 1153 if os.path.splitext(oneFile)[1].lower() in lstExtentions: 1154 fullPath = os.path.join(root, oneFile) 1155 if bFromRoot: 1156 listFiles.append(fullPath) 1157 else: 1158 assert len(fullPath) > lenRoot 1159 listFiles.append(fullPath[lenRoot:]) 1160 return listFiles
1161 1162 1163 1164 1165 #######################################################################################
1166 -def ScaleImage(filename, filigrane=None):
1167 """common processing for one image : create a subfolder "scaled" and "thumb" : """ 1168 # config=Config() 1169 rootdir = os.path.dirname(filename) 1170 scaledir = os.path.join(rootdir, config.ScaledImages["Suffix"]) 1171 thumbdir = os.path.join(rootdir, config.Thumbnails["Suffix"]) 1172 if not os.path.isdir(scaledir) : mkdir(scaledir) 1173 if not os.path.isdir(thumbdir) : mkdir(thumbdir) 1174 Img = photo(filename) 1175 Param = config.ScaledImages.copy() 1176 Param.pop("Suffix") 1177 Param["strThumbFile"] = os.path.join(scaledir, os.path.basename(filename))[:-4] + "--%s.jpg" % config.ScaledImages["Suffix"] 1178 Img.SaveThumb(**Param) 1179 Param = config.Thumbnails.copy() 1180 Param.pop("Suffix") 1181 Param["strThumbFile"] = os.path.join(thumbdir, os.path.basename(filename))[:-4] + "--%s.jpg" % config.Thumbnails["Suffix"] 1182 Img.SaveThumb(**Param) 1183 if filigrane: 1184 filigrane.substract(Img.f).save(filename, quality=config.FiligraneQuality, optimize=config.FiligraneOptimize, progressive=config.FiligraneOptimize) 1185 try: 1186 os.chmod(filename, config.DefaultFileMode) 1187 except OSError: 1188 print("Warning: unable to chmod %s" % filename)
1189
1190 -def unicode_to_ascii(unicrap):
1191 """ 1192 This takes a UNICODE string and replaces unicode characters with 1193 something equivalent in 7-bit ASCII. It returns a plain ASCII string. 1194 This function makes a best effort to convert unicode characters into 1195 ASCII equivalents. It does not just strip out the Latin-1 characters. 1196 All characters in the standard 7-bit ASCII range are preserved. 1197 In the 8th bit range all the Latin-1 accented letters are converted 1198 to unaccented equivalents. Most symbol characters are converted to 1199 something meaningful. Anything not converted is deleted. 1200 """ 1201 xlate = {0xc0:'A', 0xc1:'A', 0xc2:'A', 0xc3:'A', 0xc4:'A', 0xc5:'A', 1202 0xc6:'Ae', 0xc7:'C', 1203 0xc8:'E', 0xc9:'E', 0xca:'E', 0xcb:'E', 1204 0xcc:'I', 0xcd:'I', 0xce:'I', 0xcf:'I', 1205 0xd0:'Th', 0xd1:'N', 1206 0xd2:'O', 0xd3:'O', 0xd4:'O', 0xd5:'O', 0xd6:'O', 0xd8:'O', 1207 0xd9:'U', 0xda:'U', 0xdb:'U', 0xdc:'U', 1208 0xdd:'Y', 0xde:'th', 0xdf:'ss', 1209 0xe0:'a', 0xe1:'a', 0xe2:'a', 0xe3:'a', 0xe4:'a', 0xe5:'a', 1210 0xe6:'ae', 0xe7:'c', 1211 0xe8:'e', 0xe9:'e', 0xea:'e', 0xeb:'e', 1212 0xec:'i', 0xed:'i', 0xee:'i', 0xef:'i', 1213 0xf0:'th', 0xf1:'n', 1214 0xf2:'o', 0xf3:'o', 0xf4:'o', 0xf5:'o', 0xf6:'o', 0xf8:'o', 1215 0xf9:'u', 0xfa:'u', 0xfb:'u', 0xfc:'u', 1216 0xfd:'y', 0xfe:'th', 0xff:'y', 1217 0xa1:'!', 0xa2:'{cent}', 0xa3:'{pound}', 0xa4:'{currency}', 1218 0xa5:'{yen}', 0xa6:'|', 0xa7:'{section}', 0xa8:'{umlaut}', 1219 0xa9:'{C}', 0xaa:'{^a}', 0xab:'<<', 0xac:'{not}', 1220 0xad:'-', 0xae:'{R}', 0xaf:'_', 0xb0:'{degrees}', 1221 0xb1:'{+/-}', 0xb2:'{^2}', 0xb3:'{^3}', 0xb4:"'", 1222 0xb5:'{micro}', 0xb6:'{paragraph}', 0xb7:'*', 0xb8:'{cedilla}', 1223 0xb9:'{^1}', 0xba:'{^o}', 0xbb:'>>', 1224 0xbc:'{1/4}', 0xbd:'{1/2}', 0xbe:'{3/4}', 0xbf:'?', 1225 0xd7:'*', 0xf7:'/' 1226 } 1227 1228 r = [] 1229 for i in unicrap: 1230 if xlate.has_key(ord(i)): 1231 r.append(xlate[ord(i)]) 1232 elif ord(i) >= 0x80: 1233 pass 1234 else: 1235 r.append(str(i)) 1236 return "".join(r)
1237
1238 -def SmartSize(size):
1239 """print the size of files in a pretty way""" 1240 unit = "o" 1241 fsize = float(size) 1242 if len(str(size)) > 3: 1243 size /= 1024 1244 fsize /= 1024.0 1245 unit = "ko" 1246 if len(str(size)) > 3: 1247 size = size / 1024 1248 fsize /= 1024.0 1249 unit = "Mo" 1250 if len(str(size)) > 3: 1251 size = size / 1024 1252 fsize /= 1024.0 1253 unit = "Go" 1254 return fsize, unit
1255 1256
1257 -def recursive_delete(strDirname):
1258 """ 1259 Delete everything reachable from the directory named in "top", 1260 assuming there are no symbolic links. 1261 CAUTION: This is dangerous! For example, if top == '/', it 1262 could delete all your disk files. 1263 @param strDirname: top directory to delete 1264 @type strDirname: string 1265 """ 1266 for root, dirs, files in os.walk(strDirname, topdown=False): 1267 for name in files: 1268 os.remove(os.path.join(root, name)) 1269 for name in dirs: 1270 os.rmdir(os.path.join(root, name)) 1271 os.rmdir(strDirname)
1272 1273 1274 1275 1276 if __name__ == "__main__": 1277 #################################################################################### 1278 #Definition de la classe des variables de configuration globales : Borg""" 1279 config.DefaultRepository = os.path.abspath(sys.argv[1]) 1280 print config.DefaultRepository 1281 RangeTout(sys.argv[1]) 1282