mercredi 20 mars 2013

Création de l'IHM en C# - Etape 3

Nous constatons que le bouton "Lancer l'import" est toujours désactivé.

Code

Nous allons créer une méthode afin de vérifier que les 2 dossiers sont bien sélectionnés et dans ce cas, nous allons activer le bouton.

private void checkImportButton()
{
    if ("" != srcText.Text && "" != destText.Text)
    {
        execBtn.Enabled = true;
    }
    else
    {
        execBtn.Enabled = false;
    }
}

Maintenant nous allons appeler cette méthode dans les 2 méthodes btn_Click

private void destButton_Click(object sender, EventArgs e)
{

    //Au clic, on créé le FolderBrowserDialog
    FolderBrowserDialog dialog = new FolderBrowserDialog();
    //on l'affiche
    dialog.ShowDialog();

    //on passe le dossier selectionné dans la valeur du champs text
    destText.Text = dialog.SelectedPath;
    
    checkImportButton();
}

Exécution


Nous allons relancer l'application avec un CTRL+F5 puis sélectionner les 2 dossiers. Nous voyons que le bouton "Lancer l'import" est désormais actif. Mais ce dernier ne fait rien. Il va falloir de la même façon ajouter un gestionnaire d’évènement dessus.


Création de l'IHM en C# - Etape 2

Nous avons désormais une interface mais aucune action n'est affectée aux différents boutons.

Sélection d'un dossier.


La première chose à faire est d'ajouter un listener d’évènement sur le bouton de sélection du dossier source.
Pour cela il suffit de double cliquer sur le bouton

Cela va faire 2 choses :

Créer la méthode suivante :
private void srcButton_Click(object sender, EventArgs e)
{

}

et ajouter la ligne suivante à la définition du bouton.

this.srcButton.Click += new System.EventHandler(this.srcButton_Click);

Il va maintenant falloir ajouter la selection du dossier source au clic sur le bouton. Pour cela nous allons utiliser la classe FolderBrowserDialog

private void srcButton_Click(object sender, EventArgs e)
{
    //Au clic, on créé le FolderBrowserDialog
    FolderBrowserDialog dialog = new FolderBrowserDialog();
    //on l'affiche
    dialog.ShowDialog();

    //on passe le dossier selectionné dans la valeur du champs text
    srcText.Text = dialog.SelectedPath;
}

Execution

Faire CTRL+F5 et la fenêtre suivante s'affiche.


Maintenant, nous allons cliquer sur le 1er bouton "Sélectionner" et la fenêtre suivante doit s'afficher.


Nous sélectionnons un dossier et l'on peut voir que le chemin s'affiche désormais dans le champs texte associé.


Nous allons répéter la même opération avec le 2e bouton afin de sélectionner le dossier cible.


private void destButton_Click(object sender, EventArgs e)
{

    //Au clic, on créé le FolderBrowserDialog
    FolderBrowserDialog dialog = new FolderBrowserDialog();
    //on l'affiche
    dialog.ShowDialog();

    //on passe le dossier selectionné dans la valeur du champs text
    destText.Text = dialog.SelectedPath;
}

Nous pouvons désormais sélectionner le dossier source et le dossier cible.

Création de l'IHM en C# - Etape 1


Création du projet.

C'est bien gentil d'avoir des méthodes, encore faut-il pouvoir les utiliser.
Pour cela nous allons créer un nouveau projet mais cette fois, pas en mode console mais en mode "Application Windows Forms" que l'on va nommer imagesBrowser.

Création de l'interface

Nous arrivons alors sur une page qui va nous permettre de "dessiner" l'interface.

Nous allons avoir besoin de :

  * 1 bouton pour selectionner le répertoire où se trouvent les images à copier.
  * 1 bouton pour selectionner le répertoire où nous allons copier les images.
  * 2 zones texte pour afficher les reprtoires selectionner.
  * 2 labels pour expliquer les zones.
  * 1 bouton pour lancer l'execution du code.

Pour cela il suffit d'aller dans la partie à droite nommée "Boite à outil" et déposer en drag'n drop les éléments nécéssaire.

Pour modifier les propriétés tel que défini ci-dessous, il faut faire un click droit sur l'élément et aller tout dans bas cliquer sur "Propriétés"

Button 1 : 
 - name : srcBtn
 - autoSize : true
 - text : Sélectionner
   
Button 2 : 
 - name : destBtn
 - autoSize : true
 - text : Sélectionner

Button 3 :

 - name : destBtn
 - autoSize : true
 - enable : false
 - text : Lancer l'import
 
Textbox 1 :
 
 - readonly : true
 - name : srcText

Textbox 2 : 
 
 - readonly : true
 - name : srcText 

Label 1 :

 - text : Dossier source
 
Label 2 :

 - text : Dossier cible
            
Je vous laisse faire mumuse avec l'outil pour obtenir un look proche de ceci.              


mardi 19 mars 2013

Etape intermédiaire

Voilà nous avons pour les 3 langages :

  • Une méthode de récupération des images.
  • Une méthode de lecture des données exif
  • Une méthode de copie de fichiers
Avec ces 3 méthodes on peut déjà commencer à faire une première application. Pour cela, il va falloir organiser tout cela et créer une première IHM

Copie des fichiers

L'étape suivante sera de copier (ou déplacer) les fichiers vers leur destination.

Java

Code

Je vais utiliser l'API common-IO d'apache qui permet de gérer simplement la copie de fichier. Cette API créé les dossiers si elle n'existe pas et gère correctement les exceptions.

for (String file : listImage) {
 File targetFile = fs.getImageNameFromExif(file);
 if (targetFile != null) {
  try {
   FileUtils.copyFile(new File(file), targetFile);
  } catch (IOException e) {
   fs.error.append(e.getMessage());
  }   
 }
}

Résultat

C'est long ! 12 minutes de copie. Il y a 10 Go de données. Je testerais par la suite combien de temps un simple copier / coller prendrais. 

PHP

Code

Pas besoin d'aller chercher loin. La fonction copy suffira.

//On parcours les images.
foreach ($fs->listImages as $file) {

  //On récupère le chemin cible et le nom de fichier
  $target = $fs->getImageNameFromExif($file);
  
  //On créé le dossier cible
  $dir = $targetBase."/".$target["dir"];
  if (!file_exists($dir)) {
    mkdir($dir, 0777, true);
  } 
  
  //On copy le fichier à proprement parlé
  $targetPath = $dir."/".$target["src"];
  if (!copy($file, $targetPath)) {
    array_push($err, "cannot copy ".$file." to ".$targetPath);
  } 
}


Résultat

Attention, avant d’exécuter  penser à bien supprimer les fichiers copiés par la précédente méthode en java, sans quoi le test serait différent.

C'est long aussi ! 11 minutes, c'est un peu mieux mais au final comme la récupération des données EXIF prenait 1 minutes de plus on arrive à la même performance.

C#

Code

Je vais utiliser la class FileSystem qui a l'avantage de créer les répertoires manquants.

foreach (string file in p.files)
{
    string target = p.getImageNameFromExif(file);
    FileSystem.CopyFile(file, target, true);
}

Résultat

C'est un peu mieux, on est à 8 minutes environ. Ca reste long mais c'est tout de même 1/3 de moins que le java.

Bilan

Au final le C# est le plus performant. Malgré un moins bon temps pour la lecture des données EXIF les performances liées aux accès file system ce langage est en tête même si les différences ne sont pas flagrante avec peut être le PHP en peu en retrait.

Pour info un copier / coller de l'ensemble de ces images a pris un peu plus de 7 minutes.

Bilan "Données EXIF"

J'ai passé ma fonction de récupération des données EXIF pour les 6850 fichiers listées par la précédente méthode.

Les résultats sont très variés.

  1. Java gagne avec un temps d'exécution de 75s
  2. PHP est deuxième avec un temps de 120s
  3. C# est bon dernier avec un temps de 350s
Honnêtement je pense que le mauvais temps de c# est plus lié à une mauvaise implémentation de ma part que d'un soucis de langage. Je pense que je charge l'image entière alors que seul le header suffirait. Je vais essayer de trouver une meilleure implémentation afin de refaire ce test, car dans ces conditions je serais obligé d'éliminer ce langage mais je doute que l'on ne puisse pas trouver mieux.

Mise à jour

J'ai trouvé un portage de l'API Metadata Extractor en C#
C'est mieux mais on reste à un temps d’exécution de 110s. Cela fait donc en moyenne 5ms de plus par image. Ce n'est pas énorme mais avec le grand nombre cela fait tout de même une différence significative.


lundi 18 mars 2013

[C#] - Données EXIF

Là j'ai un peu plus galéré. J'ai du pas mal chercher sur le net pour trouver comment récupérer les données exif.

J'ai trouvé quelques librairie mais la complexité était trop grande, que j'ai décidé de coder moi même (en m'aidant d'exemples du site msdn) la fonction de récupération.

Code

public void getImageNameFromExif(string imagePath)
        {

            //On charge la photo
            Bitmap photo = new Bitmap(imagePath);

            //On récupère les metadata
            PropertyItem[] pi = photo.PropertyItems;
            
            //On récupère la date (voir exif.org pour le code/tag Original DateTime)
            PropertyItem dateItem = photo.GetPropertyItem(36867);

            //On récupère la valeur de la propriété
            Encoding ascii=Encoding.ASCII;
            string dateString = ascii.GetString(dateItem.Value, 0, dateItem.Len - 1);
            
            //On convertit la chaine de caractère récupérée en DateTime
            string format = "yyyy:MM:dd hh:mm:ss";
            DateTime date = DateTime.ParseExact(dateString, format, CultureInfo.InvariantCulture);

            //On génère le nom du fichier. 
            string format_out = "{0:yyyy/MM/dd/yyyyMMdd_hhmmss}";
            String outFile = String.Format(format_out, date);
            String targetPath = this.basePath + "/"  + outFile + ".jpg";

            //On affiche pour debug
            Console.WriteLine(targetPath);
           
        }

Là non plus, ce n'est pas trop compliqué mais sans aide, il serait impossible de trouver seul certaines 'astuces'