def extractJPEG(self):
        """
        extract the raw image to its right place
        """
        extension = op.splitext(self.strRawFile)[1].lower()
        strJpegFullPath = op.join(config.DefaultRepository, self.getJpegPath())
        if extension in config.RawExtensions:

            process = subprocess.Popen("%s %s" % (config.Dcraw, self.strRawFile), shell=True, stdout=subprocess.PIPE)
            ret = process.wait()
            if ret != 0:
                logger.error("'%s %s' ended with error %s" % (config.Dcraw, self.strRawFile, ret))
            data = process.stdout.readlines()
            img = Image.fromstring("RGB", tuple([int(i) for i in data[1].split()]), "".join(tuple(data[3:])))
            img.save(strJpegFullPath, format='JPEG')
            #Copy all metadata useful for us.
            exifJpeg = Exif(strJpegFullPath)
            exifJpeg.read()
            exifJpeg['Exif.Image.Orientation'] = 1
            exifJpeg["Exif.Photo.UserComment"] = self.strRawFile
            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']:
                try:
                    exifJpeg[metadata] = self.exif[metadata]
                except:
                    logger.error("Unable to copying metadata %s in file %s, value: %s" % (metadata, self.strRawFile, self.exif[metadata]))
            #self.exif.copyMetadataTo(self.strJepgFile)

            exifJpeg.write()

        else: #in config.Extensions, i.e. a JPEG file
            shutil.copy(self.strRawFile, strJpegFullPath)
            Exiftran.autorotate(strJpegFullPath)

        os.chmod(strJpegFullPath, config.DefaultFileMode)
Example #2
0
    def execute(self):
        with Exif() as et:
            for file in path_file_walk(self.src):
                try:
                    metadata = et.get_metadata(file)
                except Exception as e:
                    print(f"# ERROR rm {qp(file)} # {e}")
                    continue

                hierarchy = self.template.render(metadata).strip()
                dest_path = self.dest / hierarchy / file.name

                # Is the file already in the right place?
                if dest_path != file:
                    if dest_path.exists():
                        if self.verbose:
                            print(
                                f"# WARNING {self.action_name} {qp(file)} {qp(dest_path)} # Destination file already exists"
                            )
                    else:
                        if not dest_path.parent.exists():
                            print(f"mkdir -p {qp(dest_path.parent)}")
                            dest_path.parent.mkdir(parents=True, exist_ok=True)
                        if not self.quiet:
                            print(
                                f"{self.action_name} {qp(file)} {qp(dest_path)}"
                            )
                        if not self.dry_run:
                            self.action(file, dest_path)
Example #3
0
    def process_row(self, et: Exif, row):
        client = vision.ImageAnnotatorClient()

        file_name = Path(row['file_name'])

        # Sanity check that Keywords have not been set
        metadata = et.get_metadata(file_name)
        if 'Keywords' not in metadata:
            thumbnail = get_thumbnail(file_name, size='XL')
            if thumbnail.exists():
                with io.open(thumbnail, 'rb') as image_file:
                    content = image_file.read()
                image = vision.Image(content=content)

                response = client.label_detection(image=image)
                labels = response.label_annotations

                # params = [
                #     f'-Keywords={l}'
                #     for l in labels
                # ]
                # result = et.execute_raw(
                #     file_name,
                #     *params
                # )
                print(file_name, labels)
    def getJpegPath(self):

        if self.exif is None:
            self.exif = Exif(self.strRawFile)
            self.exif.read()
        if self.strJepgFile is None:
            self.strJepgFile = unicode2ascii("%s-%s.jpg" % (
                    self.exif.interpretedExifValue("Exif.Photo.DateTimeOriginal").replace(" ", os.sep).replace(":", "-", 2).replace(":", "h", 1).replace(":", "m", 1),
                    self.exif.interpretedExifValue("Exif.Image.Model").strip().split(",")[-1].replace("/", "").replace(" ", "_")
                    ))
            while op.isfile(op.join(config.DefaultRepository, self.strJepgFile)):
                number = ""
                idx = None
                listChar = list(self.strJepgFile[:-4])
                listChar.reverse()
                for val in listChar:
                    if val.isdigit():
                        number = val + number
                    elif val == "-":
                        idx = int(number)
                        break
                    else:
                        break
                if idx is None:
                    self.strJepgFile = self.strJepgFile[:-4] + "-1.jpg"
                else:
                    self.strJepgFile = self.strJepgFile[:-5 - len(number)] + "-%i.jpg" % (idx + 1)
        dirname = op.dirname(op.join(config.DefaultRepository, self.strJepgFile))
        if not op.isdir(dirname):
            makedir(dirname)

        return self.strJepgFile
Example #5
0
    def parse(self, jpeg):
        self.__offset = 2

        result = {}

        while True:
            segment_marker = struct.unpack_from("2c", jpeg, self.__offset)
            self.__offset += 2

            if segment_marker == self.__SOS:
                break

            if segment_marker == self.__EOI:
                break

            segment_length = struct.unpack_from(">H", jpeg, self.__offset)[0]
            for frame_header_marker in self.__SOFs:
                if segment_marker == frame_header_marker:
                    (height, width, channels) = struct.unpack_from(">HHB", jpeg, self.__offset + 3)
                    result["width"] = width
                    result["height"] = height
                    result["mode"] = self.__get_color_mode(channels)
                    break

            if segment_marker == self.__APP1:
                app1_magic = struct.unpack_from("6c", jpeg, self.__offset + 2)
                if app1_magic == (b'\x45', b'\x78', b'\x69', b'\x66', b'\x00', b'\x00'):
                    exif_parser = Exif(jpeg, self.__offset + 8, segment_length)
                    tags = [
                        (271, "maker"),
                        (272, "model"),
                        (2, "latitude"),
                        (4, "longitude"),
                        (36867, "DateTimeOriginal")
                    ]
                    for tag in tags:
                        try:
                            value = exif_parser.search_tag(jpeg, target_tag=tag[0], clear_offset=True)
                            result[tag[1]] = value
                        except ValueError:
                            pass

            self.__offset += segment_length

        return result
Example #6
0
    def create_exif(self, img, coords):
        with open(img, 'rb') as file:
            file = Exif(file)

            file.gps_latitude = coords['lat']
            file.gps_longitude = coords['lon']

            with open(img, 'wb') as new_image_file:
                new_image_file.write(file.get_file())
    def autoWB(self, outfile):
        """
        apply Auto White - Balance to the current image
        
        @param: the name of the output file (JPEG)
        @return: filtered Photo instance

        """
        try:
            import numpy
        except:
            logger.error("This filter needs the numpy library available on http://numpy.scipy.org/")
            return
        t0 = time.time()
        position = 5e-4
        rgb1 = numpy.fromstring(self.pil.tostring(), dtype="uint8")
        rgb1.shape = -1, 3
        rgb = rgb1.astype("float32")
        rgb1.sort(axis=0)
        pos_min = int(round(rgb1.shape[0] * position))
        pos_max = rgb1.shape[0] - pos_min
        rgb_min = rgb1[pos_min]
        rgb_max = rgb1[pos_max]
        rgb[:, 0] = 255.0 * (rgb[:, 0].clip(rgb_min[0], rgb_max[0]) - rgb_min[0]) / (rgb_max[0] - rgb_min[0])
        rgb[:, 1] = 255.0 * (rgb[:, 1].clip(rgb_min[1], rgb_max[1]) - rgb_min[1]) / (rgb_max[1] - rgb_min[1])
        rgb[:, 2] = 255.0 * (rgb[:, 2].clip(rgb_min[2], rgb_max[2]) - rgb_min[2]) / (rgb_max[2] - rgb_min[2])
        out = Image.fromstring("RGB", (self.pixelsX, self.pixelsY), rgb.round().astype("uint8").tostring())
        exitJpeg = op.join(config.DefaultRepository, outfile)
        out.save(exitJpeg, quality=80, progressive=True, Optimize=True)
        try:
            os.chmod(exitJpeg, config.DefaultFileMode)
        except IOError:
            logger.error("Unable to chmod %s" % outfile)
        exifJpeg = Exif(exitJpeg)
        exifJpeg.read()
        self.exif.copy(exifJpeg)
        logger.debug("Write metadata to %s", exitJpeg)
        exifJpeg.write()
        logger.info("The whoole Auto White-Balance took %.3f" % (time.time() - t0))
        return Photo(outfile)
Example #8
0
    def read_exif(self, img):
        with open(img, 'rb') as file:
            self.img_metas = Exif(file)

        if self.img_metas.has_exif:
            print(f'Exif : {self.img_metas.has_exif}')
            date = self.img_metas.datetime_original
            date = bytes(date, 'utf-8')
            date = date.decode('utf-8')
            print(date)
            return date
        else:
            print(f'Exif : {self.img_metas.has_exif}')
            return False
Example #9
0
 def execute(self):
     with DB(self.cache_dir) as db:
         with Exif() as et:
             for scan_dir in self.scan_dirs:
                 for file in path_file_walk(scan_dir):
                     try:
                         action = self.scan_file(db, et, file)
                     except Exception as e:
                         print(f"# ERROR rm {qp(file)} # {e}")
                         continue
                     if self.verbose and action is None:
                         print(f"# SKIPPED {file}")
                     elif not self.quiet and action:
                         print(f"{action.upper()} {qp(file)}")
         self.scan_db(db)
Example #10
0
        def index():
            with DB(self.cache_dir) as db:
                with Exif() as et:
                    start = bytes.fromhex(request.args.get('start', ''))
                    rows = db.image_select_duplicate_dhash(start).fetchall()

                    duplicates = get_duplicates(et, rows, predictor)

                    if duplicates:
                        alerts = get_alerts(duplicates)

                        last_dhash = rows[-1]['dhash']
                        # Maximum hash value as a long
                        percentage = (
                            100 * int.from_bytes(last_dhash, "big")
                        ) / 340282366920938463463374607431768211455
                        return render_template('index.html',
                                               duplicates=duplicates,
                                               alerts=alerts,
                                               percentage=percentage,
                                               last_dhash=last_dhash.hex())
                    else:
                        return render_template('empty.html')
Example #11
0
 def _read(self):
     self.extra_zero = self._io.ensure_fixed_contents(b"\x00")
     self._raw_data = self._io.read_bytes_full()
     io = KaitaiStream(BytesIO(self._raw_data))
     self.data = Exif(io)
Example #12
0
 def __init__(self, img_path):
     with open(img_path, 'rb') as file:
         self.image = Exif(file)
Example #13
0
 def execute(self):
     with DB(self.cache_dir) as db:
         with Exif() as et:
             for row in db._execute(self.sql_select, self.sql_params):
                 self.process_row(et, row)
 def getExif(self):
     if self._exif is None:
         self._exif = Exif(self.fn)
         self._exif.read()
     return self._exif
class Photo(object):
    """class photo that does all the operations available on photos"""
    _gaussian = blur.Gaussian()

    def __init__(self, filename, dontCache=False):
        """
        @param filename: Name of the image file, starting from the repository root
        """
        self.filename = filename
        self.fn = op.join(config.DefaultRepository, self.filename)
        if not op.isfile(self.fn):
            logger.error("No such photo %s" % self.fn)
        self.metadata = None
        self._pixelsX = None
        self._pixelsY = None
        self._pil = None
        self._exif = None
        self.scaledPixbuffer = None
        self.orientation = 1
        if (imageCache is not None) and (filename in imageCache):
            logger.debug("Image %s found in Cache", filename)
            fromCache = imageCache[filename]
            self.metadata = fromCache.metadata
            self._pixelsX = fromCache.pixelsX
            self._pixelsY = fromCache.pixelsY
            self._pil = fromCache.pil
            self._exif = fromCache.exif
            self.scaledPixbuffer = fromCache.scaledPixbuffer
            self.orientation = fromCache.orientation
        else:
            logger.debug("Image %s not in Cache", filename)
            if dontCache is False:
                imageCache[filename] = self
        return None


    def getPIL(self):
        if self._pil is None:
            self._pil = Image.open(self.fn)
        return self._pil
    def setPIL(self, value):self._pil = value
    def delPIL(self):
        del self._pil
        self.pil = None
    pil = property(getPIL, setPIL, delPIL, doc="property: PIL object")

    def loadPIL(self):
        """Deprecated method to load PIL data"""
        import traceback
        logger.warning("Use of loadPIL is deprecated!!!")
        traceback.print_stack()
        self.getPIL()

    def getPixelsX(self):
        if self._pixelsX is None:
            self._pixelsX = self.pil.size[0]
        return self._pixelsX
    def setPixelsX(self, value): self._pixelsX = value
    pixelsX = property(getPixelsX, setPixelsX, doc="Property to get the size in pixels via PIL")

    def getPixelsY(self):
        if self._pixelsY is None:
            self._pixelsY = self.pil.size[1]
        return self._pixelsY
    def setPixelsY(self, value): self._pixelsY = value
    pixelsY = property(getPixelsY, setPixelsY, doc="Property to get the size in pixels via PIL")


    def getExif(self):
        if self._exif is None:
            self._exif = Exif(self.fn)
            self._exif.read()
        return self._exif
    exif = property(getExif, doc="property for exif data")

    def larg(self):
        """width-height of a jpeg file"""
        return self.pixelsX - self.pixelsY


    def taille(self):
        """Deprecated method to taille data"""
        import traceback
        logger.warning("Use of taille is deprecated!!!")
        traceback.print_stack()
        self.getPIL()


    def saveThumb(self, strThumbFile, Size=160, Interpolation=1, Quality=75, Progressive=False, Optimize=False, ExifExtraction=False):
        """save a thumbnail of the given name, with the given size and the interpolation methode (quality) 
        resampling filters :
        NONE = 0
        NEAREST = 0
        ANTIALIAS = 1 # 3-lobed lanczos
        LINEAR = BILINEAR = 2
        CUBIC = BICUBIC = 3
        """
        if  op.isfile(strThumbFile):
            logger.warning("Thumbnail %s exists" % strThumbFile)
        else:
            extract = False
            print "process file %s exists" % strThumbFile
            if ExifExtraction:
                try:
                    self.exif.dumpThumbnailToFile(strThumbFile[:-4])
                    extract = True
                except (OSError, IOError):
                    extract = False
                #Check if the thumbnail is correctly oriented
                if op.isfile(strThumbFile):
                    thumbImag = Photo(strThumbFile)
                    if self.larg() * thumbImag.larg() < 0:
                        print("Warning: thumbnail was not with the same orientation as original: %s" % self.filename)
                        os.remove(strThumbFile)
                        extract = False
            if not extract:
                copyOfImage = self.pil.copy()
                copyOfImage.thumbnail((Size, Size), Interpolation)
                copyOfImage.save(strThumbFile, quality=Quality, progressive=Progressive, optimize=Optimize)
            try:
                os.chmod(strThumbFile, config.DefaultFileMode)
            except OSError:
                print("Warning: unable to chmod %s" % strThumbFile)


    def rotate(self, angle=0):
        """does a looseless rotation of the given jpeg file"""
        if os.name == 'nt' and self.pil != None:
            del self.pil
        x = self.pixelsX
        y = self.pixelsY
        logger.debug("Before rotation %i, x=%i, y=%i, scaledX=%i, scaledY=%i" % (angle, x, y, self.scaledPixbuffer.get_width(), self.scaledPixbuffer.get_height()))

        if angle == 90:
            if imageCache is not None:
                Exiftran.rotate90(self.fn)
                newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_CLOCKWISE)
                logger.debug("rotate 90 of %s" % newPixbuffer)
                self.pixelsX = y
                self.pixelsY = x
                if self.metadata is not None:
                    self.metadata["Resolution"] = "%i x % i" % (y, x)
            else:
                Exiftran.rotate90(self.fn)
                self.pixelsX = None
                self.pixelsY = None
        elif angle == 270:
            if imageCache is not None:
                Exiftran.rotate270(self.fn)
                newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
                logger.debug("rotate 270 of %s" % newPixbuffer)
                self.pixelsX = y
                self.pixelsY = x
                if self.metadata is not None:
                    self.metadata["Resolution"] = "%i x % i" % (y, x)
            else:
                Exiftran.rotate270(self.fn)
                self.pixelsX = None
                self.pixelsY = None
        elif angle == 180:
            if imageCache is not None:
                Exiftran.rotate180(self.fn)
                newPixbuffer = self.scaledPixbuffer.rotate_simple(gtk.gdk.PIXBUF_ROTATE_UPSIDEDOWN)
                logger.debug("rotate 270 of %s" % newPixbuffer)
            else:
                Exiftran.rotate180(self.fn)
                self.pixelsX = None
                self.pixelsY = None
        else:
            print "Erreur ! il n'est pas possible de faire une rotation de ce type sans perte de donnée."
        if imageCache is not None:
            self.scaledPixbuffer = newPixbuffer
            imageCache[self.filename] = self
        logger.debug("After   rotation %i, x=%i, y=%i, scaledX=%i, scaledY=%i" % (angle, self.pixelsX, self.pixelsY, self.scaledPixbuffer.get_width(), self.scaledPixbuffer.get_height()))


    def removeFromCache(self):
        """remove the curent image from the Cache .... for various reasons"""
        if imageCache is not None:
            if self.filename in imageCache.ordered:
                imageCache.pop(self.filename)


    def trash(self):
        """Send the file to the trash folder"""
        self.removeFromCache()
        Trashdir = op.join(config.DefaultRepository, config.TrashDirectory)
        td = op.dirname(op.join(Trashdir, self.filename))
        if not op.isdir(td):
            makedir(td)
        shutil.move(self.fn, op.join(Trashdir, self.filename))
        logger.debug("sent %s to trash" % self.filename)
        self.removeFromCache()


    def readExif(self):
        """
        return exif data + title from the photo
        """
        clef = {'Exif.Image.Make':'Marque',
 'Exif.Image.Model':'Modele',
 'Exif.Photo.DateTimeOriginal':'Heure',
 'Exif.Photo.ExposureTime':'Vitesse',
 'Exif.Photo.FNumber':'Ouverture',
# 'Exif.Photo.DateTimeOriginal':'Heure2',
 'Exif.Photo.ExposureBiasValue':'Bias',
 'Exif.Photo.Flash':'Flash',
 'Exif.Photo.FocalLength':'Focale',
 'Exif.Photo.ISOSpeedRatings':'Iso' ,
# 'Exif.Image.Orientation':'Orientation'
}

        if self.metadata is None:
            self.metadata = {}
            self.metadata["Taille"] = "%.2f %s" % smartSize(op.getsize(self.fn))
            self.metadata["Titre"] = self.exif.comment
            try:
                rate = self.exif["Exif.Image.Rating"]
            except KeyError:
                self.metadata["Rate"] = 0
                self.exif["Exif.Image.Rating"] = 0
            else:
                if "value" in dir(rate): # pyexiv2 v0.2+
                    self.metadata["Rate"] = int(rate.value)
                else: # pyexiv2 v0.1
                    self.metadata["Rate"] = int(float(rate))

            if self._pixelsX and self._pixelsY:
                self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY)
            else:
                try:
                    self.pixelsX = self.exif["Exif.Photo.PixelXDimension"]
                    self.pixelsY = self.exif["Exif.Photo.PixelYDimension"]
                except (IndexError, KeyError):
                    pass
                else:
                    if "human_value" in dir(self.pixelsX):
                        self.pixelsX = self.pixelsX.value
                        self.pixelsY = self.pixelsY.value
                self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY)
            if "Exif.Image.Orientation" in self.exif.exif_keys:
                self.orientation = self.exif["Exif.Image.Orientation"]
                if "human_value" in dir(self.orientation):
                    self.orientation = self.orientation.value
            for key in clef:
                try:
                    self.metadata[clef[key]] = self.exif.interpretedExifValue(key).decode(config.Coding).strip()
                except (IndexError, KeyError):
                    self.metadata[clef[key]] = u""
        return self.metadata.copy()


    def has_title(self):
        """
        return true if the image is entitled
        """
        if self.metadata == None:
            self.readExif()
        if  self.metadata["Titre"]:
            return True
        else:
            return False


    def show(self, Xsize=600, Ysize=600):
        """
        return a pixbuf to shows the image in a Gtk window
        """

        scaled_buf = None
        if Xsize > config.ImageWidth :
            config.ImageWidth = Xsize
        if Ysize > config.ImageHeight:
            config.ImageHeight = Ysize

#        Prepare the big image to be put in cache
        Rbig = min(float(config.ImageWidth) / self.pixelsX, float(config.ImageHeight) / self.pixelsY)
        if Rbig < 1:
            nxBig = int(round(Rbig * self.pixelsX))
            nyBig = int(round(Rbig * self.pixelsY))
        else:
            nxBig = self.pixelsX
            nyBig = self.pixelsY

        R = min(float(Xsize) / self.pixelsX, float(Ysize) / self.pixelsY)
        if R < 1:
            nx = int(round(R * self.pixelsX))
            ny = int(round(R * self.pixelsY))
        else:
            nx = self.pixelsX
            ny = self.pixelsY

#       Put in Cache the "BIG" image
        if self.scaledPixbuffer is None:
            logger.debug("self.scaledPixbuffer is empty")
            pixbuf = gtk.gdk.pixbuf_new_from_file(self.fn)
            if Rbig < 1:
                self.scaledPixbuffer = pixbuf.scale_simple(nxBig, nyBig, gtkInterpolation[config.Interpolation])
            else :
                self.scaledPixbuffer = pixbuf
            logger.debug("To Cached  %s, size (%i,%i)" % (self.filename, nxBig, nyBig))
        if (self.scaledPixbuffer.get_width() == nx) and (self.scaledPixbuffer.get_height() == ny):
            scaled_buf = self.scaledPixbuffer
            logger.debug("In cache No resize %s" % self.filename)
        else:
            logger.debug("In cache To resize %s" % self.filename)
            scaled_buf = self.scaledPixbuffer.scale_simple(nx, ny, gtkInterpolation[config.Interpolation])
        return scaled_buf


    def name(self, titre, rate=None):
        """write the title of the photo inside the description field, in the JPEG header"""
        if os.name == 'nt' and self.pil != None:
            self.pil = None
        self.metadata["Titre"] = titre
        if rate is not None:
            self.metadata["Rate"] = rate
            self.exif["Exif.Image.Rating"] = int(rate)
        self.exif.comment = titre
        try:
            self.exif.write()
        except IOError as error:
            logger.warning("Got IO exception %s: file has probably changed:\n photo.name=%s\n exif.name=%s\n pil.name=%s" %
                           (error, self.filename, self.exif.filename, self.pil.filename))


    def renameFile(self, newname):
        """
        rename the current instance of photo:
        -Move the file
        -update the cache
        -change the name and other attributes of the instance 
        -change the exif metadata. 
        """
        oldname = self.filename
        newfn = op.join(config.DefaultRepository, newname)
        os.rename(self.fn, newfn)
        self.filename = newname
        self.fn = newfn
        self._exif = None
        if (imageCache is not None) and (oldname in imageCache):
            imageCache.rename(oldname, newname)


    def storeOriginalName(self, originalName):
        """
        Save the original name of the file into the Exif.Photo.UserComment tag.
        This tag is usually not used, people prefer the JPEG tag for entiteling images.
        
        @param  originalName: name of the file before it was processed by selector
        @type   originalName: python string
        """
        self.exif["Exif.Photo.UserComment"] = originalName
        self.exif.write()


    def autorotate(self):
        """does autorotate the image according to the EXIF tag"""
        if os.name == 'nt' and self.pil is not None:
            del self.pil
        self.readExif()
        if self.orientation != 1:
            Exiftran.autorotate(self.fn)
            if self.orientation > 4:
                self.pixelsX = self.exif["Exif.Photo.PixelYDimension"]
                self.pixelsY = self.exif["Exif.Photo.PixelXDimension"]
                self.metadata["Resolution"] = "%s x %s " % (self.pixelsX, self.pixelsY)
            self.orientation = 1


    def contrastMask(self, outfile):
        """Ceci est un filtre de debouchage de photographies, aussi appelé masque de contraste, 
        il permet de rattrapper une photo trop contrasté, un contre jour, ...
        Écrit par Jérôme Kieffer, avec l'aide de la liste python@aful, 
        en particulier A. Fayolles et F. Mantegazza avril 2006
        necessite numpy et PIL.
        
        @param: the name of the output file (JPEG)
        @return: filtered Photo instance
        """

        try:
            import numpy
#            import scipy.signal as signal
        except:
            logger.error("This filter needs the numpy library available on https://sourceforge.net/projects/numpy/files/")
            return

        t0 = time.time()
        dimX, dimY = self.pil.size

        ImageFile.MAXBLOCK = dimX * dimY
        img_array = numpy.fromstring(self.pil.tostring(), dtype="UInt8").astype("float32")
        img_array.shape = (dimY, dimX, 3)
        red, green, blue = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2]
        #nota: this is faster than desat2=(ar.max(axis=2)+ar.min(axis=2))/2
        desat_array = (numpy.minimum(numpy.minimum(red, green), blue) + numpy.maximum(numpy.maximum(red, green), blue)) / 2.0
        inv_desat = 255. - desat_array
        blured_inv_desat = self._gaussian.blur(inv_desat, config.ContrastMaskGaussianSize)
        bisi = numpy.round(blured_inv_desat).astype("uint8")
        k = Image.fromarray(bisi, "L").convert("RGB")
        S = ImageChops.screen(self.pil, k)
        M = ImageChops.multiply(self.pil, k)
        F = ImageChops.add(ImageChops.multiply(self.pil, S), ImageChops.multiply(ImageChops.invert(self.pil), M))
        exitJpeg = op.join(config.DefaultRepository, outfile)
        F.save(exitJpeg, quality=80, progressive=True, Optimize=True)
        try:
            os.chmod(exitJpeg, config.DefaultFileMode)
        except IOError:
            logger.error("Unable to chmod %s" % outfile)
        exifJpeg = Exif(exitJpeg)
        exifJpeg.read()
        self.exif.copy(exifJpeg)
        exifJpeg.comment = self.exif.comment
#
#        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',
#                         "Exif.Image.Orientation", "Exif.Photo.UserComment"
#                         ]:
#            if metadata in self.exif:
#                logger.debug("Copying metadata %s", metadata)
#                try:
#                    exifJpeg[metadata] = self.exif[metadata]
#                except KeyError:
#                    pass #'Tag not set'-> unable to copy it
#                except:
#                    logger.error("Unable to copying metadata %s in file %s, value: %s" % (metadata, self.filename, self.exif[metadata]))
        logger.debug("Write metadata to %s", exitJpeg)
        exifJpeg.write()
        logger.info("The whoole contrast mask took %.3f" % (time.time() - t0))
        return Photo(outfile)

    def autoWB(self, outfile):
        """
        apply Auto White - Balance to the current image
        
        @param: the name of the output file (JPEG)
        @return: filtered Photo instance

        """
        try:
            import numpy
        except:
            logger.error("This filter needs the numpy library available on http://numpy.scipy.org/")
            return
        t0 = time.time()
        position = 5e-4
        rgb1 = numpy.fromstring(self.pil.tostring(), dtype="uint8")
        rgb1.shape = -1, 3
        rgb = rgb1.astype("float32")
        rgb1.sort(axis=0)
        pos_min = int(round(rgb1.shape[0] * position))
        pos_max = rgb1.shape[0] - pos_min
        rgb_min = rgb1[pos_min]
        rgb_max = rgb1[pos_max]
        rgb[:, 0] = 255.0 * (rgb[:, 0].clip(rgb_min[0], rgb_max[0]) - rgb_min[0]) / (rgb_max[0] - rgb_min[0])
        rgb[:, 1] = 255.0 * (rgb[:, 1].clip(rgb_min[1], rgb_max[1]) - rgb_min[1]) / (rgb_max[1] - rgb_min[1])
        rgb[:, 2] = 255.0 * (rgb[:, 2].clip(rgb_min[2], rgb_max[2]) - rgb_min[2]) / (rgb_max[2] - rgb_min[2])
        out = Image.fromstring("RGB", (self.pixelsX, self.pixelsY), rgb.round().astype("uint8").tostring())
        exitJpeg = op.join(config.DefaultRepository, outfile)
        out.save(exitJpeg, quality=80, progressive=True, Optimize=True)
        try:
            os.chmod(exitJpeg, config.DefaultFileMode)
        except IOError:
            logger.error("Unable to chmod %s" % outfile)
        exifJpeg = Exif(exitJpeg)
        exifJpeg.read()
        self.exif.copy(exifJpeg)
        logger.debug("Write metadata to %s", exitJpeg)
        exifJpeg.write()
        logger.info("The whoole Auto White-Balance took %.3f" % (time.time() - t0))
        return Photo(outfile)
    def contrastMask(self, outfile):
        """Ceci est un filtre de debouchage de photographies, aussi appelé masque de contraste, 
        il permet de rattrapper une photo trop contrasté, un contre jour, ...
        Écrit par Jérôme Kieffer, avec l'aide de la liste python@aful, 
        en particulier A. Fayolles et F. Mantegazza avril 2006
        necessite numpy et PIL.
        
        @param: the name of the output file (JPEG)
        @return: filtered Photo instance
        """

        try:
            import numpy
#            import scipy.signal as signal
        except:
            logger.error("This filter needs the numpy library available on https://sourceforge.net/projects/numpy/files/")
            return

        t0 = time.time()
        dimX, dimY = self.pil.size

        ImageFile.MAXBLOCK = dimX * dimY
        img_array = numpy.fromstring(self.pil.tostring(), dtype="UInt8").astype("float32")
        img_array.shape = (dimY, dimX, 3)
        red, green, blue = img_array[:, :, 0], img_array[:, :, 1], img_array[:, :, 2]
        #nota: this is faster than desat2=(ar.max(axis=2)+ar.min(axis=2))/2
        desat_array = (numpy.minimum(numpy.minimum(red, green), blue) + numpy.maximum(numpy.maximum(red, green), blue)) / 2.0
        inv_desat = 255. - desat_array
        blured_inv_desat = self._gaussian.blur(inv_desat, config.ContrastMaskGaussianSize)
        bisi = numpy.round(blured_inv_desat).astype("uint8")
        k = Image.fromarray(bisi, "L").convert("RGB")
        S = ImageChops.screen(self.pil, k)
        M = ImageChops.multiply(self.pil, k)
        F = ImageChops.add(ImageChops.multiply(self.pil, S), ImageChops.multiply(ImageChops.invert(self.pil), M))
        exitJpeg = op.join(config.DefaultRepository, outfile)
        F.save(exitJpeg, quality=80, progressive=True, Optimize=True)
        try:
            os.chmod(exitJpeg, config.DefaultFileMode)
        except IOError:
            logger.error("Unable to chmod %s" % outfile)
        exifJpeg = Exif(exitJpeg)
        exifJpeg.read()
        self.exif.copy(exifJpeg)
        exifJpeg.comment = self.exif.comment
#
#        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',
#                         "Exif.Image.Orientation", "Exif.Photo.UserComment"
#                         ]:
#            if metadata in self.exif:
#                logger.debug("Copying metadata %s", metadata)
#                try:
#                    exifJpeg[metadata] = self.exif[metadata]
#                except KeyError:
#                    pass #'Tag not set'-> unable to copy it
#                except:
#                    logger.error("Unable to copying metadata %s in file %s, value: %s" % (metadata, self.filename, self.exif[metadata]))
        logger.debug("Write metadata to %s", exitJpeg)
        exifJpeg.write()
        logger.info("The whoole contrast mask took %.3f" % (time.time() - t0))
        return Photo(outfile)
class RawImage:
    """ class for handling raw images
    - extract thumbnails
    - copy them in the repository
    """
    def __init__(self, strRawFile):
        """
        Contructor of the class
        
        @param strRawFile: path to the RawImage 
        @type strRawFile: string
        """
        self.strRawFile = strRawFile
        self.exif = None
        self.strJepgFile = None
        logger.info("Importing [Raw|Jpeg] image %s" % strRawFile)

    def getJpegPath(self):

        if self.exif is None:
            self.exif = Exif(self.strRawFile)
            self.exif.read()
        if self.strJepgFile is None:
            self.strJepgFile = unicode2ascii("%s-%s.jpg" % (
                    self.exif.interpretedExifValue("Exif.Photo.DateTimeOriginal").replace(" ", os.sep).replace(":", "-", 2).replace(":", "h", 1).replace(":", "m", 1),
                    self.exif.interpretedExifValue("Exif.Image.Model").strip().split(",")[-1].replace("/", "").replace(" ", "_")
                    ))
            while op.isfile(op.join(config.DefaultRepository, self.strJepgFile)):
                number = ""
                idx = None
                listChar = list(self.strJepgFile[:-4])
                listChar.reverse()
                for val in listChar:
                    if val.isdigit():
                        number = val + number
                    elif val == "-":
                        idx = int(number)
                        break
                    else:
                        break
                if idx is None:
                    self.strJepgFile = self.strJepgFile[:-4] + "-1.jpg"
                else:
                    self.strJepgFile = self.strJepgFile[:-5 - len(number)] + "-%i.jpg" % (idx + 1)
        dirname = op.dirname(op.join(config.DefaultRepository, self.strJepgFile))
        if not op.isdir(dirname):
            makedir(dirname)

        return self.strJepgFile

    def extractJPEG(self):
        """
        extract the raw image to its right place
        """
        extension = op.splitext(self.strRawFile)[1].lower()
        strJpegFullPath = op.join(config.DefaultRepository, self.getJpegPath())
        if extension in config.RawExtensions:

            process = subprocess.Popen("%s %s" % (config.Dcraw, self.strRawFile), shell=True, stdout=subprocess.PIPE)
            ret = process.wait()
            if ret != 0:
                logger.error("'%s %s' ended with error %s" % (config.Dcraw, self.strRawFile, ret))
            data = process.stdout.readlines()
            img = Image.fromstring("RGB", tuple([int(i) for i in data[1].split()]), "".join(tuple(data[3:])))
            img.save(strJpegFullPath, format='JPEG')
            #Copy all metadata useful for us.
            exifJpeg = Exif(strJpegFullPath)
            exifJpeg.read()
            exifJpeg['Exif.Image.Orientation'] = 1
            exifJpeg["Exif.Photo.UserComment"] = self.strRawFile
            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']:
                try:
                    exifJpeg[metadata] = self.exif[metadata]
                except:
                    logger.error("Unable to copying metadata %s in file %s, value: %s" % (metadata, self.strRawFile, self.exif[metadata]))
            #self.exif.copyMetadataTo(self.strJepgFile)

            exifJpeg.write()

        else: #in config.Extensions, i.e. a JPEG file
            shutil.copy(self.strRawFile, strJpegFullPath)
            Exiftran.autorotate(strJpegFullPath)

        os.chmod(strJpegFullPath, config.DefaultFileMode)