Version imprimable du sujet

Cliquez ici pour voir ce sujet dans son format original

Forums MacBidouille _ La Programmation En Général _ Modifier une donnée EXIF en applescript

Écrit par : Speed Moock 27 Oct 2016, 18:04

Bonjour à tous,

Voici ce que je souhaite faire :
Je dispose de dossiers et sous dossiers contenant des images dont le nom est structuré ainsi :
racine fixe égale à G_
puis code produit en 6 caractères
séparateur fixe _
puis code couleur sur 2 à 4 caractères
séparateur fixe _ZP_
Index d'image valeur 1 à 10
séparateur fixe _
Code interne unique sur 6 caractères

Exemple G_12345_320_ZP_1_565656.jpg
est l'image numéro 1 du produit 12345

En parallèle je dispose d'un énorme listing excel composé en colonne A des codes produits et en colonne B de la taille de ces produit (champ texte)

Je cherche à réaliser un script qui pour chaque image va chercher si elle en trouve la taille dans le listing et si oui écrira dans une donnée EXIF la valeur du champ texte.
Si le code produit n'est pas dans le listing, ne rien écrire.

le script demande donc de la récursivité et l'utilisation de EXIFTOOL (que j'ai téléchargé et installé mais sans savoir faire mieux...)

Avez-vous des idées ?

Merci

Écrit par : Jaypee 28 Oct 2016, 13:26

Speed Moock,

Au lieu d'empiler plusieurs outils, tu pourrais aussi en choisir un qui fait tout.

Et le langage Swift 3, te permet d'essayer interactivement (comme en Bash, en Perl, en Python ou Ruby) dans un playground créé dans XCode, ou dans un terminal. Ou sur le portaill d'IBM (qui a adopté Swift)
https://swiftlang.ng.bluemix.net/

Un gist (un snippet, un bout de code d'exemple) pour charger une image e tse préparer à lire ses propriétés :
https://gist.github.com/tsuyukimakoto/c7ef8ed096a3e22bda7d
Autre exemple:
http://erkkiahola3.blogspot.de/2015/11/exif-data.html

Par ailleurs, exporte le fichier Excel en csv qui est un simple fichier texte, plus facile à lire dans n'importe quel langage, sur n'importe quel OS.
Swift3 est open source et fonctionne très bien sur Linux aussi.

J-P

Écrit par : Speed Moock 28 Oct 2016, 13:38

Bonjour Jaypee,

Merci pour la proposition mais déjà que je suis laborieux en applescript, je ne suis pas rendu sur d'autres langages...

Je vais déjà essayer en AS, je passerai ceinture noire plus tard !

Écrit par : Jaypee 29 Oct 2016, 08:19

Pas de problème, Speed Moock. Si un outil est une solution, quel problème résoud-il que les autres outils ne savent pas mieux résoudre ?

Applescript demande une connaissance précise du modèle d'objets de macOS. Très facile à lire mais très difficile à écrire avec des nuances comme file, alias, POSIX file etc. Il n'est pas vraiment fait pour les apprentis programmeurs, et source de frustration. Facile pour des choses faciles. Mais quel autre langage ne l'est pas.

Avec la même énergie, on apprend un langage que beaucoup de gens maitrisent avec bien moins de frustrations durant le voyage.

J-P

Écrit par : hellomorld 29 Oct 2016, 09:11

C'est assez simple à faire avec Livecode (http://www.livecode.com). La version Community est opensource et gratuite. C'est un successeur d'Hypercard qui en a conservé le langage (et dont l'Applescript s'est aussi grandement inspiré)
Comme le dit Jaypee, ce sera beaucoup plus simple de travailler sur un fichier texte issu d'excel (je conseille plutôt le format texte tabulé que le vrai csv).
Je te joins 2 fichiers ("piles") qui devraient te servir de base. Un pour lister les fichiers d'une arborescence, l'autre pour utiliser Exiftools (il permet de géotagger un fichier)

 livecode.zip ( 4.94 Ko ) : 7
.


Écrit par : Jaypee 29 Oct 2016, 13:02

A mon avis on peut se passer de EXIFtools

Il y a dans macOS un petit trésor caché, la commande sips. Essaie un man sips.
Par exemple: sips -g pixelHeight /chemin/de/mon/image.jpg

Et AppleScript a dans sa liste d'applications dont on peut ouvrir le dictionnaire "Image Events"
Il faut commencer par sélectionner une image dans le Finder puis:

Code
tell application "Finder"
    set selectedItem to (item 1 of (get selection))
    set filePath to POSIX path of (selectedItem as string)
    tell application "Image Events"
        set myImage to open (file filePath)
        set dims to (dimensions of myImage)
        close
    end tell
    display dialog ("Width: " & ((item 1 of dims) as string) & ", Height: " & ((item 2 of dims) as string))
end tell


Edit:
Pour l'EXIF, Il faut des outils vraiment spécifiques, et en voici un gratuit et orienté AppleScript, avec un exemple d'extraction de metadata EXIF, voilà ce qu'il produit:
Code
{{exif type:exif make, exif unicode:"Apple"}, {exif type:exif model, exif unicode:"iPhone 6"}, {exif type:software, exif unicode:"Adobe Photoshop Lightroom 5.7 (Macintosh)"}, {exif type:exif creation date, exif unicode:"2016:03:27 15:58:21"}, {exif type:exposure time in seconds, exif float:0.058823529631, exif unicode:"1/17"}, {exif type:aperture fNum, exif float:2.200000047684, exif unicode:"22/10"}, {exif type:exposure program, exif int:2, exif unicode:"2"}, {exif type:ISO film speed, exif int:400, exif unicode:"400"}, {exif type:capture date, exif unicode:"2016:03:27 14:54:43"}, {exif type:exposure bias EV, exif float:0.0, exif unicode:"0/1"}, {exif type:metering mode, exif int:5, exif unicode:"5"}, {exif type:flash, exif int:16, exif unicode:"16"}, {exif type:focal length mm, exif float:4.150000095367, exif unicode:"83/20"}, {exif type:captureDateSubSecondTime, exif unicode:"905"}}


Le soft gratuit est ici:
http://www.yvs.eu.com

J-P

Écrit par : Speed Moock 29 Oct 2016, 18:05

Merci à tous !

Je vais profiter du long WE pour m'y pencher.

A bientôt

Écrit par : PBell 29 Oct 2016, 18:53

QUOTE (Speed Moock @ 27 Oct 2016, 19:04) *
pour chaque image va chercher si elle en trouve la taille dans le listing et si oui écrira dans une donnée EXIF la valeur du champ texte.


Bonsoir, en lisant cette demande, j'interprète que tu veux lire des dimensions sur Excel, et les écrire dans une sorte de commentaire dans le fichier image.
Mais je ne pense pas que tu veuilles changer la taille de l'image elle-même.

Dans ce cas, l'utilisation via "Image Events" ne peut être adéquates.

Il y a donc un premier groupe question à se poser, avant même de choisir le language:
- ces dimensions que tu veux mettre dans l'image, c'est dans quel but ?
- Est-ce juste un champ texte ?
- Quelle autre application va lire cette donnée ? sur Mac, sur PC ? (la réponse permettra de déterminer si la donnée peut être stockée dans les info file ou si elle doit être à l'intérieur de l'image).

Enfin, mon second groupe de questions est liée à la performance :
- un "énorme" listing Excel... c'est quoi ? 1000 lignes, 10 000 lignes ? 500 000 lignes ? (ce qui donnera aussi une idée du volume de fichier à traiter !)
- le dossier principal contient des sous dossiers : est-ce que tout le contenu n'est constitué que de fichier Image avec le même format de nom ? (ou d'autres fichier peuvent-ils exister ?)

Une fois répondu à ces questions, il sera plus facile de déterminer la méthode et donc le language approprié.
Bien sûr, comme l'indique Jaypee, le fichier Excel devra être traduit en texte (csv par exemple)

Cordialement

Écrit par : Jaypee 29 Oct 2016, 19:47

L'un des scripts de la documentation de "Imagine Photo" fait le boulot, mais le seul champ susceptible de recevoir le texte libre est la champ "info".

CODE
tell application "iMagine Photo"
set thisFile to choose file with prompt "Image file with exif data: "
set savedFile to choose file name default name "NewImageFile.jpg"
set copyrightText to text returned of (display dialog "Enter the copyright text: " default answer "Copyright Jaypee 2016")
set artistText to text returned of (display dialog "Photographers name: " default answer "Jaypee")
set iMageInfo to text returned of (display dialog "Information about the image: " default answer "Product Info")
set thisImporter to import graphic thisFile
tell thisImporter to make exporter with properties {export file type:"JPEG"}
set the export file location of thisImporter to savedFile
set exifData to the exif data of thisImporter
set the export exif data of thisImporter to exifData
set the export exif data of thisImporter to {{exif type:copyright, exif unicode:copyrightText}, {exif type:artist, exif unicode:artistText}, {exif type:info, exif unicode:iMageInfo}}
export thisImporter
close thisImporter
set thisImporter2 to import graphic savedFile
set exifData2 to the exif data of thisImporter2
close thisImporter2
end tell
exifData2


Et la copie de la photo avec les nouveaux EXIFs a bien été créée.

J-P

Écrit par : Speed Moock 30 Oct 2016, 00:23

Bonjour à tous

En lisant le post de Pbell je viens de me rendre compte d'un énorme quiproquo
La donnée taille dans mon fichier excel est la valeur en cm du produit photographié dans l'image, pas la taille en pixels de l'image 😜😜😜😜

Quant au listing excel il contient 30000 lignes...


Voilà je pense que cette précision de "taille" réorientera vos réflexions

Merci !!

Écrit par : PBell 30 Oct 2016, 07:22

Bonjour,
Merci pour la précision quant aux tailles en cm (je m'en doutais) et à la taille du fichier Excel.

Il reste à savoir ce que tu vas faire de ces dimensions produits (lecture dans d'autres applications,...) une fois mises "dans l'image" pour savoir quel est la meilleure place pour enregistrer cette donnée.
Il y a en effet beaucoup de champs Exif possibles

Cordialement

Écrit par : Jaypee 30 Oct 2016, 10:08

Une recommandation de haut niveau.

Ce traitement est un cas d'école pour le "design pattern" appelé "Visiteur" qui sépare deux fonctions:
- Celle qui permet de parcourir complètement la structure à traiter, c'est le parcours récursif de liste ou d'arbre, à chaque point visité, on applique la deuxième fonction à tous les éléments intéressés à cette étape de la visite, puis on continue la visite.
- Celle dite fonction de visite qui fait le traitement désiré.

Dans le cas présent, la visite pourrait privilégier la liste Excel, et ce serait seulement un parcours de liste. Si on tient à récursiver, on applique la fonction de visite à la tête de liste, puis on parcourt le reste de la liste.
Soit on privilégie les images, et le parcours est un parcours d'arbre, la fonction de visite doit faire la recherche dans la liste Excel et l'écriture de l' EXIF. Dans ce cas, il peut être intéressant de trier au préalable cette liste selon la clé de recherche. On peut aussi envisager de charger la liste complètement en mémoire : Si la ligne fait 1Ko, on consommera 100 Mo de mémoire, une paille.
Comme il est plus commun de vouloir appliquer un certain traitement à tous les fichiers à partir d'une racine, cette approche est recommandée car plus générique et ré-utilisable

Pour l'adaptation en AppleScript:
- Les appels récursifs se signalent par le mot-clé "my"
on recFonction(x, y, z)
if test de fin then
finir
else
my recFonction(x', y', z')
end if
end recFonction

- Comment passer la fonction de visite, pour séparer le parcours du traitement?

Mon exemple simple

CODE
script mult2
on visite(n)
return n * 2
end visite
end script

script carre
on visite(n)
return n * n
end visite
end script

on parcours_liste(indice_depart, liste_num, fonc_visite)
local copie
copy liste_num to copie
if indice_depart > (the count of copie) then
return copie
else
set tete_de_liste to item indice_depart of copie
tell fonc_visite
set item indice_depart of copie to visite(tete_de_liste)
end tell
my parcours_liste(indice_depart + 1, copie, fonc_visite)
end if
end parcours_liste

set exemple to {1, 25, 7, 0, -2}

parcours_liste(1, exemple, mult2)
parcours_liste(1, exemple, carre)



Conclusion: Je ne recommande toujours pas AppleScript aux apprentis programmeurs car il est trop complexe... La preuve est dans mon dernier exemple
On y voit deux appels séparés, pas de réaffectation de variables.
Pourtant, si je n'avais pas explicitement déclaré une variable locale, le paramètre d'entrée "exemple" aurait implicitement "mué", il y aurait au final dans exemple la liste de carrés des doubles, alors qu'à aucun moment on n'a réaffecté la valeur de exemple dans un "set exemple to ...",
Pire encore, le piège est pressenti par un programmeur averti...
local copie
set copie to liste_num -- au lieu de copy liste_num to copie
Le "set" est une copie d'adresse, pas une copie de valeur, donc la déclaration de variable locale n' a aucun effet, à la fin la variable exemple est devenue discrètement {4, 2500, 196, 0, 16} "à l'insu de notre plein gré".

Au final, un effet de surprise, source de bugs difficiles à détecter. Bon courage à celles et ceux qui continuent dans cette voie dangereuse.

J-P

Écrit par : Speed Moock 30 Oct 2016, 15:57

Bonjour,

Pour répondre à Pbell :
Le nombre d'images sera toujours inférieur au nombre de lignes du listing excel. Il y a donc une inclusion de l'un dans l'autre, et donc il faut parcourir les dossiers images et en chercher la présence dans le listing.

si trouve => écriture de la donnée dans le champ exif "info" (quitte à écraser un contenu déjà existant)
si non trouvé = > on ne fait rien

Pour information la métadonnée est ensuite reprise sur un site internet comme élément descriptif du produit. Il y a certainement plus simple pour afficher une taille produit sur un site mais on ne me laisse pas le choix et on me demande que l'information soit dans l'image en tant que métadonnée.

@Jaype : merci pour iMagine, je découvre au passage une appli sympa !

Merci

Écrit par : Jaypee 30 Oct 2016, 17:21

Je complète mon exemple avec un parcours d'arbre. Vraiment c'est tout dans la subtilité, et ça demande énormément d'attention.

CODE
script mult2
on visite(n)
return n * 2
end visite
end script

script carre
on visite(n)
return n * n
end visite
end script

on parcours_liste(liste_num, fonc_visite)
local nums
copy {} to nums
if (the count of liste_num) > 0 then
repeat with num in liste_num
tell fonc_visite
copy visite(num) to end of nums
end tell
end repeat
return nums
else
return {}
end if
end parcours_liste

-- Un niveau a une liste de valeurs et une liste de sous-niveaux

set niveau2 to {valeurs:{200, 201}, sous_niveaux:{}}
set niveau1_2 to {valeurs:{120, 121}, sous_niveaux:{niveau2}}
set niveau1_1 to {valeurs:{110, 111}, sous_niveaux:{}}
set racine to {valeurs:{}, sous_niveaux:{niveau1_1, niveau1_2}}

on parcours_niveau(top, fonc_visite)
local sous_niv
if (count of (sous_niveaux of top)) > 0 then
copy {} to sous_niv
repeat with niv in (sous_niveaux of top)
copy my parcours_niveau(niv, fonc_visite) to end of sous_niv
end repeat
return {valeurs:parcours_liste(valeurs of top, fonc_visite), sous_niveaux:sous_niv}
else
return {valeurs:parcours_liste(valeurs of top, fonc_visite), sous_niveaux:{}}
end if
end parcours_niveau

parcours_niveau(racine, mult2)


La valeur de départ:
{valeurs:{}, sous_niveaux:{{valeurs:{110, 111}, sous_niveaux:{}}, {valeurs:{120, 121}, sous_niveaux:{{valeurs:{200, 201}, sous_niveaux:{}}}}}}
Le résultat:
{valeurs:{}, sous_niveaux:{{valeurs:{220, 222}, sous_niveaux:{}}, {valeurs:{240, 242}, sous_niveaux:{{valeurs:{400, 402}, sous_niveaux:{}}}}}}

Écrit par : Zeltron54 30 Oct 2016, 20:14

Bonsoir,

Tu peux aussi mettre ton information dans le champs "commentaires" que l'on vois avec un cmd+i .

Ce champs est facilement accessible en applescript avec un script genre : set comment of "chemin du fichier" to "taille du produit" .
Dans ce cas pas besoin d'exiftool.

Exemple avec un fichier texte:

00001 commentaire taille
00002 le com 2
00003 le com 3
00004 le com 4
00005 le com 5
00006 le com 6
00007 le com 7
00008 le com 8
00009 le com 9
00010 le com 10
00011

et un script style:

set CheminTXT to choose file with prompt "Sélectionnez le fichier texte" --Choix du fichier texte

my inspecter(cheminIMG, CheminTXT)
end tell

on inspecter(un_dossier, CheminTXT)

tell application "Finder"
-- traitement des fichiers image:
set les_fichiers to files of un_dossier
repeat with chaque_fichier in les_fichiers
-- traitement d'un fichier image

set nom to name of chaque_fichier --obtient la nom du fichier image
set code_produit to (items 3 thru 7 of nom) as string -- du troisième caractère au septième

-- traitement du fichier texte
open for access CheminTXT
read CheminTXT
set le_texte to the result -- récupère le fichier dans la variable
close access CheminTXT

set AppleScript's text item delimiters to (ASCII character 13)
set toutes_les_lignes to (every text item of le_texte) as list -- récupère les lignes du fichier
set AppleScript's text item delimiters to ""

repeat with i from 1 to count of toutes_les_lignes
set la_ligne to item i of toutes_les_lignes as string -- chaque ligne

set long to length of la_ligne -- si la ligne fait plus de 6 caractères(5 code produit+1tab)
if long > 6 then

set code_prod_list to items 1 thru 5 of la_ligne as string -- recupère la code produit

if code_produit = code_prod_list then -- compare

set lecom to items 7 thru -1 of la_ligne as string --si code produit = alors récupère la suite de la ligne

set comment of chaque_fichier to lecom -- écrit le commentaire

end if
end if
end repeat

end repeat
-- traitement des dossiers images:
set les_dossiers to folders of un_dossier
repeat with chaque_dossier in les_dossiers
-- traitement du dossier
my inspecter(chaque_dossier, CheminTXT)
end repeat
end tell
end inspecter

tell application "Finder"
display dialog "Ok j'ai terminé"
end tell

Écrit par : Jaypee 30 Oct 2016, 21:45

@Zeltron54,

Le champ Commentaires ne semble pas interrogeable par la commande sips, ni par la lecture des données EXIF.
Si la description L x H x P doit être exploitable par une application web, elle doit être accessible par une interface standard.

Il y a un module Node.js qui lit les données EXIF
https://github.com/RodrigoEspinosa/exif-cli

En revanche le champ Description est le champ EXIF imageInfo, et extractible par sips -g description photo.jpg
D'ailleurs, sachant cela, sips -s description "texte de la description" /chemin/vers/la/phooto.jpg est une interface d'écriture simplifiée:
un "do shell script" pourrait suffire.

J-P

Écrit par : Speed Moock 30 Oct 2016, 21:46

Merci Zeltron pour cette proposition, mais je l'avais écartée dès le départ car les personnes qui récupèrent mes images sont sur windows et disent ne pas savoir récupérer les commentaires finder.

A+

Écrit par : PBell 31 Oct 2016, 08:18

Bonjour,
Effectivement les seuls champs qui peuvent être utilisé avec des images, tant sur Mac que sur Windows doivent être des champs EXIF ou IPTC.
Les autres champs ne seront pas transférés d'un OS à l'autre (dommage que Microsoft n'ai pas aussi copié cette partie lorsqu'il a crée Windows).

Commençons par le language : nous avons jusqu'à 30 000 lignes dans le fichier Excel et sans doute autant de fichiers image. Donc la meilleure solution semble être un script shell Unix en terme de vitesse.
Cependant, j'ai préféré un script Applescript avec des appels Unix pour 2 raisons:
1) il faut commencer par demander à l'utilisateur le nom du dossier racine et du fichier Excel : c'est facile en AS. mais je ne pense pas que cela soit possible en shell Unix
2) J'ai des lacunes en shell qui ne me permettent pas d'assurer un script qui fonctionne. Mais les autres contributeurs pourront aussi traduire en shell

La demande pose 3 problèmes:
- récursivité dans l'arborescence des dossier et sous dossiers
- recherche rapide, à partir du produit, dans le fichier Excel, pour trouver la colonne B content un texte
- ajout de ce texte dans l'image elle-même (EXIF)


Récursivité:
La solution que j'ai retenue fonctionne, mais sera sans doute longue (peut être trop longue) sur 30000 fichier. Inutile de dire que je n'ai pas tester sur ce nombre, mais sur environ 200 images. C'est le point critique de performance. Je me suis basé sur la fonction "entire content" qui extrait tous les fichiers situés dans une arborescence. Si elle a le mérite d'être simple, ce n'est pas performant et j'ai de grands doutes sur 30 000 fichiers. Il sera sans doute utile de lancer le script plusieurs fois sur des dossiers de niveaux inférieurs pour se limiter à environ 1000 à 2000 images à chaque fois.
Si cela pose un problème, dis le, et je ré-écrirai cette partie en vrai récursivité. Il reste que 30 000 en un seul coup risque d'être très long.

Recherche rapide du produit dans le liste Excel:
Là, je me suis dis "facile !", mais c'est en fait là où j'ai passé le plus de temps. La fonction la plus rapide est une commande shell Unix 'grep'. 'Grep xxx yyy' est capable de trouver la ligne contenant xxx dans le fichier yay... Oui, mais qu'est-ce qu'une ligne ?
En fait, grep considère, comme beaucoup de logiciels, qu'un ligne est un ensemble de caractères se terminant par le caractère spécial linefeed (LF ascii=10). Oui mais... Excel en text tabdelimiter, en cdv, en UTF text,... ne mets pas ces fameux linefeed, mais des Carriage Return (CR ascii=13) ! donc pour grep, tout le fichier n'est qu'une ligne !!!
En analysant les différents format de sortie Excel, la solution est de sauvegarder ton fichier Excel en format "Windows fomatted text": là les fin de lignes sont faites avec le CR et le LF ! Du coup le grep fonctionne parfaitement.
Dans ce format de sortie Excel, la séparation entre les colonne se fait par une tabulation (ascii=9).
Pour les curieux, j'ai ajouté un '^' qui signifie que ce que je cherche doit être en début de ligne (au cas où la colonne B contienne des chiffres comme un code produit !)

Ajout du texte dans l'image:
Pas de doute ici, l'instruction à utiliser est le exiftool. Reste à savoir quel tag doit être utilisé. J'ai choisi 'imageDescription' car c'est une partie de l'EXIF standard et donc cela aura plus de chance d'être lu par de nombreux logiciels.

Voici le script résultant : (testé bien sûr)

CODE
set DosParent to choose folder "Sélectionner le dossier parent contenant les images à traiter"

set WFormatted to choose file "Sélectionner le fichier Windows formated contenant les dimensions"
set WPath to quoted form of (POSIX path of WFormatted)
set AppleScript's text item delimiters to {"_"}
set tab to ASCII character 9

tell application "Finder" to set MesImages to every file in entire contents of DosParent whose name starts with "G_"

repeat with uneImage in MesImages
    tell application "Finder" to set Produit to text item 2 of ((name of uneImage) as string)
    set R to ""
    try
        set R to do shell script "grep ^" & Produit & " " & WPath
    on error
        set R to ""
    end try
    if R is not "" then
        set DimText to text ((offset of tab in R) + 1) thru -1 of R
        set UnixPath to POSIX path of (uneImage as string)
        do shell script "/usr/local/bin/exiftool -ImageDescription='" & DimText & "' -Overwrite_Original " & quoted form of (UnixPath)
    end if
end repeat


Après traitement, tu pourra vérifier dans Aperçu, sur une image traitée, dans la fenêtre inspecteur, onglet IPTC, l'image description contenant ta colonne B.
Cordialement

Écrit par : Speed Moock 31 Oct 2016, 08:51

Salut

Merci Pbell
Toujours clair et concis merci !!
Je teste mercredi sur un vrai dossier et un vrai listing de plusieurs milliers d'éléments

Merci

Écrit par : Jaypee 31 Oct 2016, 09:53

Juste une petite remarque à propos d'EXIFtools, il n'est pas indispensable, car l'appli gratuite "iMagine Photo" est 100% orientée Applescipt, offrant le dictionnaire qui va bien.

Ensuite, je poursuis mon idée d'avoir tout le fichier Excel chargé en mémoire, qui est peut-être réaliste, ou pas...

CODE
set csv to {"p123,10x20x30", "p124,5x75x20", "p346,60x45x15", "p254,80x120x60", "p654,10x10x10"}
set memdb to {}
set text item delimiters to {","}
repeat with ligne in csv
copy {codeProduit:text item 1 of ligne, dimensions:text item 2 of ligne} to end of memdb
end repeat
set text item delimiters to {""}

script codeProduit
on trouve(val, liste)
repeat with e in liste
if (codeProduit of e) is val then
return e
end if
end repeat
end trouve
end script

tell codeProduit to set dims to dimensions of trouve("p346", memdb)
dims

Résultat: "60x45x15"
Note: On balaye séquentiellement la liste, il y a certainement un optimisation possible comme une recherche dichotomique.
CODE
set csv to {"p123,10x20x30", "p124,5x75x20", "p246,60x45x15", "p254,80x120x60", "p654,10x10x10", "p1123,10x20x30", "p1124,5x75x20", "p1246,60x45x15", "p1254,80x120x60", "p1654,10x10x10", "p2123,10x20x30", "p2124,5x75x20", "p2246,60x45x15", "p2254,80x120x60", "p2654,10x10x10"}
set memdb to {}
set text item delimiters to {","}
repeat with ligne in csv
copy {codeProduit:text item 1 of ligne, dimensions:text item 2 of ligne} to end of memdb
end repeat
set text item delimiters to {""}

script codeProduit
on trouve(val, liste)
repeat with e in liste
if (codeProduit of e) is val then
return e
end if
end repeat
end trouve
-- Prérequis, la liste est triée par code produit
-- A faire dans Excel avant exportation
on trouveDicho(val, liste)
return dicho(1, count of liste, val, liste)
end trouveDicho
-- fonction interne récursive
on dicho(debut, fin, val, liste)
local milieu
copy (debut + fin) div 2 to milieu
if liste is {} then
return {}
else if (val < codeProduit of item debut of liste) or (val > codeProduit of item fin of liste) then
return {}
else
-- cas chanceux
if val is codeProduit of item debut of liste then
return item debut of liste
else if val is codeProduit of item fin of liste then
return item fin of liste
-- cas général
else if val < codeProduit of item milieu of liste then
-- chercher de nouveau avant le milieu
return my dicho(debut, miliieu - 1, val, liste)
else
-- chercher de nouveau depuis milieu jusqu'à la fin
return my dicho(milieu, fin, val, liste)
end if
end if
end dicho
end script

tell codeProduit to set x to trouveDicho("p1246", memdb)
if x is not {} then
dimensions of x
end if


PS: Sur un exemple aussi trivial, la même dichotomie répétée 100 000 fois est plus longue de quelques secondes 19.0 contre 15.0 pour la recherche séquentielle. Sur 30 000 éléments, elle fait sûrement un meilleur score.
J-P

Écrit par : PBell 31 Oct 2016, 19:09

Bonsoir Jaypee,
Je ne connaissais pas l'application iMagine Photo que je viens de télécharger sur ton conseil. Merci
Effectivement, le dictionnaire des commandes Applescript est assez complet quant à la manipulation des images.
Cela en fait un outil très intéressant.

Par contre, en terme de données EXIF seules, le type de données gérées est beaucoup plus limité que Exiftool: le dictionnaire 'Exif type' liste environ 26 types.
Exiftool ne traite que les données exif, ce qui est bien plus limité que iMagine Photo, mais il le fait bien mieux avec des centaines de tag possibles.
Je ne les ai jamais compté, mais si tu fais "exiftool -list -EXIF:All" sur le Terminal tu auras la liste. De plus Exiftool ne gère pas que les données purement Exif, mais aussi d'autres tags: "exiftool -list" te donnera les tag Exif et les autres (environ 39 pages de liste de tags sur mon moniteur 27' !!)
J'ajouterai que Exiftool dispose aussi de fonctions de traitement en masse directement au niveau Unix, comme par exemple traiter tous les fichiers d'un dossier en une seule instruction.

En résumé, pour traiter l'image elle-même (dimensions, échelle, rotation), la modifier (ajouter des formes/shapes,...) iMagine Photo est parfait. Merci encore pour l'information.
Pour traiter uniquement les tag exils ou autres, je préfère rester à exiftool, qui est aussi gratuit.

Cordialement

Écrit par : Jaypee 1 Nov 2016, 09:09

Merci à tous également... Un simple sujet comme celui-ci peut être l'occasion de recherche, et d'acquisition d'expérience.
Je m'intéresse en ce moment aux les performances et au comportement général d'AppleScript

La gestion de mémoire semble le point faible, dans ce sens que les performances baissent fortement lorsque une mémoire importante est requise. J'inclurai mon programme de test pour simuler une "base de données", un tableau de 20000 produits + dimensions, comme ceux que Speed Moock utilise.
La seule génération des produits (boucle et formatage de texte) ralentit significativement au delà de 8500 à 9000 articles.

Ensuite des recherches aléatoires de produits donne des résultats assez médiocres, de l'ordre de 3 à 4 secondes pour 10000 articles.

Comme je pressens la dégradation non-linéaire des performances (données x 3 n'impliquent pas performances x 3, mais plus) ma conclusion serait que:
- Si la communication inter applis fonctionne bien, il faut privilégier la délégation des fonctions complexes aux applis qui savent bien le faire, comme la recherche du produit dans la base. Ou alors prévoir une petite base de données SQL (SQLite est en standard, utilisé par le système lui-même)
- Si cette communication est pareillement médiocre, je crains qu'AppleScript soit limité par ses performances, il faut s'intéresser à des solutions de programmation plus classiques comme Python, Ruby pour les langages de script, et à Swift puisqu'il est promu par Apple (et par IBM maintenant. Une digression sur IBM: Sur un parc de 35000 Macs et autant de PC Windows,les Macs ont coûté 500$ de moins de support par leur service informatique que les PC par an)

Et pendant que j'écrivais les résultats sont tombés:
- 3000 articles: Recherche < 1 s
- 10000 articles: Recherche autour de 3 ou 4 s
- 20000 articles: Recherche autour de 15 à 20 s
Ma méthode de recherche dans le tableau:
- Dichotomie jusqu'à tomber en dessous d'une limite de 100, où la recherche devient séquentielle
- Pour éviter les re-copies de mémoire, la recherche séquentielle est faite entre deux indices départ et fin, sans changer le grand tableau de départ. Normalement, comme il a été observé plus tôt, AppelScript passe ses arguments systématiquement par adresse, dont passer la liste, ne consiste qu'à passer l'adresse, sans recopie.

Conclusion: Non, avec AppleScript on ne doit pas tout charger en mémoire. Même si en quantité, elle ne manque pas, sa gestion par AppleScript est extrêmement peu performante.

J-P
PS: Le code. Pour voir les messages défiler, cliquer sur le 3e l'icone (i) <flèche retour> <document avec des lignes> pour faire apparaître la console, puis choisir l'onglet "messages" ou "événements"

CODE
global CAPACITE
copy 3000 to CAPACITE

set memdb to {}

global FMT
copy 5 to FMT
on format(n, f)
copy "00000" & n to padded
copy (count of padded) to l
return text (1 + l - f) thru l of padded
end format

set startTime to do shell script "date +%s"
repeat with i from 1 to CAPACITE
copy {codeProduit:"p" & format(i, FMT), dimensions:"" & 10 * (1 + i mod 5) & "x" & 20 * (1 + i mod 3) & "x" & 50 * (1 + i mod 2)} to end of memdb
if (0 is i mod 500) then
log "==> " & i
end if
end repeat
set finishTime to do shell script "date +%s"
set creation_bd to finishTime - startTime

script codeProduit
on trouve(val, liste)
copy 100 to LIMITE
-- log val
local l
copy (count of liste) to l
if l is 0 then
return {codeProduit:"liste vide", dimensions:"n/a"}
else if val is "" then
return {codeProduit:"numéro produit vide", dimensions:"n/a"}
else if l < LIMITE then
return seq(1, count of liste, val, liste)
else
return dicho(1, count of liste, val, liste, LIMITE)
end if
end trouve

-- Recherche séquentielle
on seq(debut, fin, val, liste)
log "seq " & debut & " " & fin & " " & val
if (val < (codeProduit of (item debut of liste))) or (val > (codeProduit of (item fin of liste))) then
return {codeProduit:"hors limites", dimensions:"n/a"}
else
repeat with i from debut to fin
if (codeProduit of item i of liste) is val then
-- log "Trouvé séquentiellement"
return item i of liste
end if
end repeat
end if
end seq


-- Recherche dichotomique récursive
-- Prérequis, la liste est triée par code produit
-- A faire dans Excel avant exportation

on dicho(debut, fin, val, liste, LIMITE)
-- log "dicho " & debut & " " & fin & " " & val
local milieu, taille
copy fin - debut to taille
if taille ≤ LIMITE then
-- log "Redirigé vers seq"
return my seq(debut, fin, val, liste)
else if (val < (codeProduit of (item debut of liste))) or (val > (codeProduit of (item fin of liste))) then
return {codeProduit:"hors limites", dimensions:"n/a"}
else
set milieu to (debut + fin) div 2
-- cas chanceux
if val is codeProduit of item debut of liste then
return item debut of liste
else if val is codeProduit of item fin of liste then
return item fin of liste
-- cas général
else if val < codeProduit of (item milieu of liste) then
-- chercher de nouveau avant le milieu
return my dicho(debut, milieu - 1, val, liste, LIMITE)
else if val ≥ codeProduit of (item milieu of liste) then
-- chercher de nouveau depuis milieu jusqu'à la fin
return my dicho(milieu, fin, val, liste, LIMITE)
else
return {codeProduit:"bizarre", dimensions:"spéciales"}
end if
end if
end dicho
end script

set bench to {}
set ECHANTILLON to (CAPACITE div 5)
set testStartTime to do shell script "date +%s"
repeat with n from 1 to ECHANTILLON
set prod to "p" & format(random number from 1 to CAPACITE, FMT)
set startTime to do shell script "date +%s"
tell codeProduit to set x to trouve(prod, memdb)
if x is not {} then
set y to {produit:codeProduit of x, dimensions:dimensions of x}
else
set y to {}
end if
set finishTime to do shell script "date +%s"
set timeItTook to finishTime - startTime
copy {recherche:prod, trouve:y, execution:timeItTook} to end of bench
log "" & n & " " & timeItTook
end repeat
set testFinishTime to do shell script "date +%s"

log "Base de données de " & CAPACITE & " articles générée en " & creation_bd & " s"
log "Temps total écoulé pour les " & ECHANTILLON & " tests " & (testFinishTime - testStartTime) & " s"

set total to 0.0
repeat with resultat in bench
copy total + (execution of resultat) to total
end repeat
-- bench
log "Temps moyen, hors génération aléatoire " & (total / ECHANTILLON) & " s"

Écrit par : Speed Moock 1 Nov 2016, 10:10

Bonjour à tous,

J'ai testé sur un dossier contenant 291 images (sans sous dossiers) et un listing d'autant de ligne correspondant pile poile au contenu du dossier.
Résultat : 3 minutes de travail (je suis sur un McBook pro 2.2 Ghz Intel Core i7 et avec 4Go de mémoire DDR3 à 1333 Mhz).... donc oui ça sera trés (trop) long sur mes dossiers de travail.

A+

Écrit par : Jaypee 1 Nov 2016, 16:43

Je recommande très vivement de créer une petite base de données sqlite3. Même depuis AppleScript, le temps de recherche est virtuellement nul avec une base de 32000 articles !

Etape 1: Générer le csv en Ruby. OK. C'est un one-liner, un peu chevelu... Durée pour 32000 articles: instantané. Dans un terminal taper irb (Interactive RuBy) c'est déjà installé en standard sur tous les Macs.

Code
File.open '/Users/jaypee/workspaces/Ruby/bdproduits.csv', 'w' do |f| (1..32000).each  {|i| f.write "p#{i.to_s.rjust(5,"0")},#{10*(1 + i % 5)}x#{20 * (1 + i % 3)}x#{5 * (1 + i % 2)}\n"}  end


Etape 2: Générer la bd SQLite3. Durée: Moins qu'il n'en faut pour le documenter ici.
Code
cd /Users/jaypee/workspaces/Ruby
sqlite3 produits.db
create table produits(code, dimensions);
.mode csv
.import '/Users/jaypee/workspaces/Ruby/bdproduits.csv' produits
.exit

La syntaxe en ligne de commande est: sqlite3 <base.bd> <requête SQL>

Etape 3: Bench en AppleScript
CODE
global CAPACITE
copy 3000 to CAPACITE

global FMT
copy 5 to FMT
on format(n, f)
copy "00000" & n to padded
copy (count of padded) to l
return text (1 + l - f) thru l of padded
end format

set bench to {}
set ECHANTILLON to (CAPACITE div 5)
set testStartTime to do shell script "date +%s"
repeat with n from 1 to ECHANTILLON
set prod to "p" & format(random number from 1 to CAPACITE, FMT)
set startTime to do shell script "date +%s"
copy "/usr/bin/sqlite3 /Users/jaypee/workspaces/Ruby/produits.db \"select dimensions from produits where code = '" & prod & "'\"" to cmd
-- log cmd
set dims to do shell script cmd
set y to {produit:prod, dimensions:dims}
set finishTime to do shell script "date +%s"
set timeItTook to finishTime - startTime
copy {recherche:prod, trouve:y, execution:timeItTook} to end of bench
log "" & n & " " & timeItTook
end repeat
set testFinishTime to do shell script "date +%s"

log "Temps total écoulé pour les " & ECHANTILLON & " tests " & (testFinishTime - testStartTime) & " s"

set total to 0.0
repeat with resultat in bench
copy total + (execution of resultat) to total
end repeat
log "Temps moyen, hors génération aléatoire " & (total / ECHANTILLON) & " s"
bench


Résultats
(*Temps total écoulé pour les 600 tests 94,0 s*)
(*Temps moyen, hors génération aléatoire 0,116666666667 s*)

(*Temps total écoulé pour les 2000 tests 362,0 s*)
(*Temps moyen, hors génération aléatoire 0,119 s*)

Les perfs sont stables, quel que soit le nombre d'articles.

Comparaison avec recherche en AppleScript de mon précédent script
(*Base de données de 3000 articles générée en 6,0 s*)
(*Temps total écoulé pour les 600 tests 322,0 s*)
(*Temps moyen, hors génération aléatoire 0,476666666667 s*)

(*Base de données de 10000 articles générée en 171,0 s*)
(*Temps total écoulé pour les 2000 tests 9685,0 s*)
(*Temps moyen, hors génération aléatoire 4,808 s*)

Conclusion: Avec 10000 articles, des perfs moyennes améliorées 40.4 fois, 26 fois en temps écoulé, de plus de deux heures à 6 minutes

J-P

Écrit par : Speed Moock 1 Nov 2016, 21:49

Ah oui quand même...

Bon je vais me creuser la tête pour adapter ce code à mon besoin...

Merci Jaypee !

Écrit par : PBell 1 Nov 2016, 23:24

Bonsoir,
Comme l'a justement indiqué Jaypee, ce type de demande est intéressant car il force à se poser d'autres questions et donc améliore la compréhension.

Je me suis donc penché sur la question de la vitesse.
Sur le grep, je n'avais aucun doute que cela soit la plus rapide. Je confirme grep est au niveau bas shell donc pas mieux.

Sur l'Exiftool, c'est affectivement assez rapide mais il ne faut pas oublier qu'une écriture de donnée EXIF implique en fait une ré-ecriture complète du fichier image; en effet la structure de données Exif implique l'insertion de la donnée (elle ne peut être ajoutée à la fin) et donc la ré-écriture de l'image.
La vitesse dépend des accès disque : disque dur/mémoire flash, accès serveur/carte disque, type de connexion (SATA, USB,...).
Elle dépend aussi de la taille de l'image traitée : je doute que des images pour un site web fassent 10Mb chacune, mais tout de même, ce sera plus long de ré-écrire 300Ko par image que 30Ko !

Il reste, comme l'a parfaitement décrit Jaypee, la question de l'utilisation de la mémoire par AS.
J'ai commencé par revoir le script pour utiliser la récursivité avec des listes de folder et de fichiers. Beaucoup mieux, mais pas encore assez.
J'ai donc mixé un script de base avec un appel de récursivité, pour réduire l'utilisation de la mémoire au maximum. Elle est maintenant dépendante, essentiellement du nombre niveau de sous dossiers.

Une fois la mise au point faite, et comme il me semblait que la performance était là, j'ai décidé de tester à plus grande échelle.
J'ai crée un fichier Excel avec 30 000 lignes de 2 colonnes:
1) code produit = nombre sur 6 chiffres,
2) texte de dimension = ("Dimension " & code produit & "cm") .. qui me permet de vérifier que le grep à partir du code produit est correct
Comme expliqué précédemment, j'ai enregistré cette liste de 30 000 produits/dimension sur un fichier Window Formatted text.

J'ai ensuite écrit un petit script qui me génère des images Jpg avec un nom du type G_xxxxxx_21_ZP_1_test.jpg.
Le script a fait varier xxxxxx de 120 000 à 131 200, donc 11200 images, que j'ai éclatées en 4 sous dossiers, regroupées dans un dossier parent.

Voilà pour l'environnement de test ! Je pense ainsi me rapprocher du besoin en terme de volume. Non ?

Lors de mes tests, je me suis aussi aperçu que les "log" que je mettais ralentissaient. Surtout, j'ai découvert que lancer le script à partir de l'éditeur est bien plus lent que de le sauver en tant qu'application puis de lancer l'application. Le moniteur d'activité confirme une plus faible utilisation de mémoire. Je pense que c'est en partie du à la notion d'historique de l'éditeur, qui n'existe plus en mode "application".
J'ai fait une première série de tests avec toutes les fonctions (boucle, récursivité, recherche produit, conversion de posix,...), sauf l'écriture Exif elle-même.
Par exemple, un premier test sur 200 images avec le nouveau script tourne en 23 secondes dans l'éditeur et seulement en 11 secondes en mode application !
Pour 1000 images, je passe de 2minutes 49 secondes à 1 minute seulement en mode application.
J'ai enfin supprimé les log directs pour passer à l'écriture d'un fichier log .txt, mais en passant via du shell ce qui ne ralenti pas (ou si peu !).

Comme vous pouvez le voir, j'ai limité les appels au Finder au maximum (le Finder est connu pour sa lenteur).

Une fois tous ces tests positifs, j'ai remis l'écriture des Exif. p
Désormais, pour 200 images, cela passe de 11 secondes à 62 secondes. Et encore, mes images sont petites (40Ko) !!
In finé, après quelques ajustements sur le script, j'ai lancé le script avec les 11200 images réparties en 4 sous dossiers, le fichier produits/dimensions de 30 000 lignes: temps de traitement total 49 minutes, tout compris !!
Je crois que je ne peux faire mieux !
...sauf à passer via un script shell direct.

Voici le script dernière version. Il faut l'enregistrer sous forme d'application et lancer l'application (ne pas lancer via l'éditeur !)

CODE
-- balayge recursif des dossiers / sous dossiers
global C
global WPath
global Fichier_Log

set DosParent to choose folder with prompt "Sélectionner le dossier parent"
set Fichier_Log to (((path to desktop) as text) & "mon_journal.txt")


set WFormatted to choose file with prompt "Sélectionner le fichier Windows formated contenant les dimensions"
set WPath to quoted form of (POSIX path of WFormatted)
set AppleScript's text item delimiters to {"_"}
set tab to ASCII character 9

set C to 0
set T1 to (current date)

LitDossier(DosParent)
set T2 to (current date)
Slog((C & return & (T1 as string) & return & (T2 as string)), Fichier_Log)
display dialog (C & return & T1 as string) & return & T2 as string

-- sous routine pour la récursivité
on LitDossier(Doss)
    tell application "Finder" to set Liste_item to (every item of Doss) -- lit tous les items du dossier
    repeat with Un_item in Liste_item
        tell application "Finder" to set SK to kind of Un_item
        if SK is "Dossier" then -- c'est un dossier, donc appel récursif
            LitDossier(Un_item)
        else -- c'est une image à traiter
            tell application "Finder" to set N to (name of Un_item) as string
            if N starts with "G_" then
                set Produit to text item 2 of N
                set R to ""
                try
                    set R to do shell script "grep ^" & Produit & " " & WPath
                on error
                    set R to ""
                end try
                if R is not "" then
                    set DimText to text ((offset of tab in R) + 1) thru -1 of R
                    set UnixPath to POSIX path of (Un_item as string)
                    Slog(Produit & "//" & DimText, Fichier_Log)
                    do shell script "/usr/local/bin/exiftool -ImageDescription='" & DimText & "' -Overwrite_Original " & quoted form of (UnixPath)
                end if
                set C to C + 1
            end if -- starts with "G_"
        end if -- dossier ou file
    end repeat
end LitDossier

on Slog(msg, MFichier) -- Fichier log via Shell *****************************************
    --    set the UnixPath to POSIX path of MFichier
    try
        -- Créer si besoin
        do shell script "touch " & (quoted form of (POSIX path of MFichier))
        -- Ajouter le message en fin de fichier
        do shell script "echo " & (quoted form of msg) & " >> " & (quoted form of (POSIX path of MFichier))
    end try
end Slog


Pour savoir où en est le script, il suffit de voir sur le bureau le fichier "mon_journal.txt", de le sélectionner et "barre d'espace" pour afficher le contenu via la fonction Coup d'Oeil. Dans mes tests, je savais à combien d'images j'en était car elles sont numérotées à partir de 120000.

Plus tes images sont grandes, plus le temps de traitement sera long.
Plus la vitesse d'écriture de tes images sur le support est élevée, plus rapide sera le script.
Il peut être judicieux de copier le dossier parent en local au lieu de le laisser sur le serveur.

Cordialement

PS : toutes ces mesures ont été faites sur un imac27, processeur Intel core i7 2,8GHz , ram 8Go et ElCapitain.

Écrit par : Speed Moock 1 Nov 2016, 23:43

Merci Pbell !
Je teste ceci demain en environnement pro
Sur le nombre de niveaux d'arboresence tu as vu juste
Je peux faciliter le traitement en listant dans un excel le listing de mes images et en restreignant le fichier contenant les tailles à ces seules images

Par contre mes images font 5 Mo...


Bref je reviens vers vous avec des nouvelles sous peu

Écrit par : Jaypee 2 Nov 2016, 07:15

Speed Moock,
Vu la tournure que prend les choses, je crois que tu as intérêt à faire tout en shell script.
A la base fais juste un script "de visite" pour une image
- parsing du nom => produit
- dims=`sqlite3 db sql` # apostrophe inversées pour exécuter la commande, ajouter un dollar devant $dims pour lire la valeur de dims
- exiftools pour écrire l'info dans l'image


Puis le parcours... il existe en standard avec la commande shell find, exemple:

find . : liste tous les fichiers sous le répertoire courant (= le point)

find racine/ -name "*jpg" -exec mon_script.sh {} \; # Trouver tous les jpeg sous la racine et leur appliquer mon script

Conseil d'ami, AppleScript n'apporte plus rien à ce stade, hormis compliquer la tâche

J-P

Écrit par : PBell 2 Nov 2016, 08:21

Merci Jaypee,
Je suis assez d'accord avec toi. J'avais indiqué au départ que le language qui me semblait le plus approprié était le shell script.
Mais je ne suis que très novice en shell script, c'est pourquoi j'ai "habillé" le grep et le exiftool avec de l'Applescript.

Par exemple, lister les fichiers dont le titre commence par "G_" et se termine par ".jpg" avec récursivité sur les sous dossiers ne me pose pas de problème en shell, pas plus que le grep. Je devrais même pouvoir adapter le grep avec un pipe pour ne prendre que la partie après la tabulation.

Par contre, je ne maîtrise pas le traitement au milieu.
J'ai vu qu'il y a des boucles "for" en shell, mais aussi que certains exemples font des pipes "|" en lieu de place de cette boucle pour appliquer une instruction à chaque fichier.
J'ai plein de questions...et pas de réponses !

Il me semble que le pipe est OK à condition que chaque fichier trouvé avec le find ne nécessite qu'une seule instruction. Est-ce exact ?
si plusieurs instructions, il faut utiliser le "for"...?
Dans ce cas, quelle est la syntaxe pour avoir plusieurs instructions dans la boucle et surtout dans quelle variable est le fichier en cours ? Bref quel est l'équivalent des classique repeat/end repeat ou encore for/next (VBA)
Pour le parsing du nom, je devrais pouvoir trouver tout seul, mais pour ces points, tes commentaires, voire un exemple m'aiderait à mieux comprendre.

Merci d'avance.

@SpeedMock, si tes fichiers font 5Mo, de toute façon tu verra que l'essentiel du temps de traitement est l'insertion de la donnée Exif qui nécessite la recopie de l'image: soit, pour 30 000 images, une recopie de 150Go !! d'où l'intérêt de vérifier ta vitesse d'accès disque

Cordialement

Écrit par : yponomeute 2 Nov 2016, 08:38

Citation (PBell @ 31 Oct 2016, 08:18) *
1) il faut commencer par demander à l'utilisateur le nom du dossier racine et du fichier Excel : c'est facile en AS. mais je ne pense pas que cela soit possible en shell Unix

Il me semble qu'avec platypus c'est possible (à confirmer, cela fait longtemps que je ne l'ai pas utilisé) : http://sveinbjorn.org/platypus


D'autre part j'ai déjà eu des traitements sur des dizaines de milliers de fichiers en script shell. J'avais adopté une solution en plusieurs étapes qui fonctionnait bien : au lieu de faire le traitement dans une boucle j'écrivais la ligne de commande à exécuter dans un fichier, et ensuite j'exécutais les lignes du fichier.
Par exemple :

Code
echo "ma ligne de commande shell;" >> liste_commandes.sh


et ensuite

Code
bash  liste_commandes.sh


En fait j'utilise systématiquement cette technique lorsque je développe un script, cela me permet de ne pas exécuter les commandes en phase de développement et de pouvoir facilement vérifier que script produit bien la bonne commande.


Écrit par : Jaypee 2 Nov 2016, 13:02

@PBell
Il faut raisonner "Inversion de controle" ou "méthode Hollywood" ou "Ne nous appelez pas, NOUS vous appelerons".
Ce n'est pas au script de trouver où chercher il faut lui apporter comme argument (c-à-d $1, puisque $0 est le nom du script lui-même) donc $1 est un nom de la form G_*.jpg

Et ce script travaille sur ce seul fichier mais fait bien le boulot de A à Z.
- extrait le produit du nom
- recherche les dimensions dans la base SQLite3
- ecrit les exifs
- ecrit une trace à la console pour qu'on puisse suivre la progression du travail.

Pas de boucle for, un seul fichier est traité

Appelons ce script dims2exif.sh qui devra avoir le mode 755 (ou +x) c-à-d éxécutable


Puis de l'extérieur un autre script s'occupe de fournir les noms individuellement:
- Voir juste pour la compréhension la sortie de:
find /racine -name "G_*jpg" # normalement il faudrait ajouter -print, mais c'est implicite.
ça trouve récursivement tous les fihciers jpg sous la racine

Pour ce deuxième script, appelons le dossier2exif.sh et qui prend "/racine" en entrée (donc $1=/racine) et le motif "G_*jpg" comme $2
find $1 -name $2 -exec dims2exif.sh {} \; # <--- les {} représente une ligne de la réponse

Il faut suivre les bonnes pratiques habituelles, afficher l'aide si on envoie la commande sans rien. $# sert à compter les arguments donc si $# < 2 afficher l'aide sinon commencer le traitement.
Il faut valider les entrées $1 existe-t-il ? $1 se termine-t-il avec {jpg|gif|png..}

Idem pour les deux scripts.

On peut commencer simple:
dims2exif.sh ne contient que: ls -l $1 # lister le fichier donné.

Puis on travaille le dossier2exif.sh. On doit voir la sortie du ls -l uniquement pour les fichiers du motif. Quand tout celà est OK, on s'attaque à dims2exif.sh, facile à tester pour un seul fichier.
- Indice pour le parsing du nom -> produit: echo $1 | awk -F _ '{echo $2}' devrait faire le boulot.

C'est juste des directions générales.
J-P
PS: Sur les pipes. Les pipes sont les emblèmes de la programmation du style "fonctionnel". L'idée c'est que la liste devant le pipe, doit être transformée par une fonction pour fournir une liste de même taille. En programmation fonctionnelle, c'est un map: en gros f(liste) = liste des f(x), pour tout x appartenant à la liste. Parfois, une liste donne une seule valeur, genre une liste de nombres done une valeur unique, la somme ou le produit de tous les nombres. C'est ce qu'on appelle un réduce. Map et Reduce sont les deux mamelles de ce style de programamtion.

Pour pouvoir utiliser une liste de noms de fichier il faut souvent faire un: ls -C1: listing sur une colonne, mais un foreach est ensuite indispensable. Je préfère le pattern "Visiteur" de la commande find.

Écrit par : Jaypee 2 Nov 2016, 13:17

@yponomeute @PBell

Une fois qu'on a le script shell qui fait bien tout avec juste la racine et le motif des images, là on peut utiliser AppleScript pour faire un truc sympa pour la saisie des données.

Si je me rapelle bien, avec XCode + AppleScript on peut faire un vrai dialogue modal macOS, avec toutes les questions en même temps dans un formulaire et les boutons qui vont bien.

J-P


Écrit par : PBell 2 Nov 2016, 19:53

Bonsoir Jaypee,
Merci de ton aide...j'ai encore besoin de ton guidage.
J'ai bien compris l'utilisation d'un script de boucle et d'un autre de sub routine qui traite le fichier un par un dans la boucle.
Cependant, je me suis exercé et j'ai un problème avec la boucle.

Commençons par ce qui fonctionne :
Au début de mon script, je teste pour l'instant que l'utilisateur entre bien 2 paramètres: le dossier et le fichier produit/dimension

CODE
#!/bin/bash
# test des paramètres d’entrée
if [ $# != 2 ]
then
echo ‘Erreur: il faut 2 paramètres, le dossier et le fichier des dimensions’
exit
fi

Je peux ainsi utiliser, comme tu l'indiques, les variable $1 (le dossier) et $2 le fichier produit/dimensions

Pour le traitement du fichier, extraction du nom du produit ici pour le fichier G_121200_125200/G_121202_21_ZP_1_test.jpg et recherche/extraction des dimensions dans le fichier WF30000.txt.
Là aussi, tout va bien: Je définie d'abord la variable produit, puis j'utilise cette variable pour obtenir la variable dim qui contient la colonne 2 :
CODE
produit=$(basename /Users/imac27/Desktop/G_121200_125200/G_121202_21_ZP_1_test.jpg | cut -c3-8)
pdim=$(grep $produit /Users/imac27/Desktop/WF30000.txt | cut -c8-100)

la variable pdim renvoie les dimensions qui seront utilisables dans l'exif.
Je n'ai pas de souci sur la partie exif pour l'instant.


Là où je bloque c'est sur la boucle. J'ai bien un find qui fonctionne en mode terminal direct, et me donne la liste des fichiers voulus:
CODE
find  /dossier -name  "G_*.jpg“

Mais dans mon shell script, si je fais un find $1 -name "G_*.jpg", j'ai une erreur sur le directory.
-idem en changeant les " par des '
-idem si je remplace directement le $1 par le dossier en litéral.

J'ai essayé de définir une variable Liste à partir de find pour faire une boucle for...sans succès !!
CODE
Liste= find mon_dossier -name 'G_$.jpg'
for fichier in Liste
do
echo 'voici mon fichier=' fichier
done

Cela ne marche pas. Je ne comprends pas mon erreur. J'ai essayé une autre boucle qui, elle, fonctionne :
CODE
for fichier in /Users/imac27/Desktop/Traitement_images/Masstest/Petit/*
do
echo ‘fichier trouve=‘ $(basename $fichier)
done


Help !!
Merci d'avance

Écrit par : Jaypee 2 Nov 2016, 21:52

@PBell, oublie la boucle, et penche-toi sur la commande find. Ensuite, pareil, oublie les greps et les pipes compliqués... C'est chic, mais si ce script plante, tu ne seras pas là pour le réparer. Autant utiliser la technologie la plus efficace qui existe et faire le moins de code possible.
Un proverbe de programmeur: La ligne de code qui ne plantera jamais, c'est celle qu'on aura évité d'écrire.

Pour la démo, je vais générer une nouvelle base compatible avec les codes produits de 2 à 5 caractères, sans formatage par des zéros.

Code
[jaypee:~/workspaces/Ruby]$ irb
irb(main):001:0> File.open '/Users/jaypee/workspaces/Ruby/bdproduits.csv', 'w' do |f| (11..32000).each  {|i| f.write "#{i.to_s},#{10*(1 + i % 5)}x#{20 * (1 + i % 3)}x#{5 * (1 + i % 2)}\n"} end


Puis la base elle même:
Code
[jaypee:~/workspaces/Ruby]$ sqlite3 produits.db
SQLite version 3.14.0 2016-07-26 15:17:14
Enter ".help" for usage hints.
sqlite> create table produits(code, dimensions);
sqlite> .mode csv
sqlite> .import /Users/jaypee/workspaces/Ruby/bdproduits.csv produits
sqlite> .exit


Voici un début de script dims2exif.sh
Code
#!/bin/sh
if [ $# -lt 1 ]; then
    echo Usage: $0 chemin/vers/image.jpg
    exit 0
fi
prod=`echo $1 | awk -F _ '{print $2}'`
echo $prod
dims=`sqlite3 ./produits.db "select dimensions from produits where code = '$prod'"`
echo $dims


Et un exemple d'exécution:
Code
[jaypee:~/workspaces/Ruby]$ ./dims2exif.sh G_12345_320_ZP_1_565656.jpg                      
12345
10x20x10


Pour finir, je fabrique de faux fichiers de taille nulle avec la commande touch dans un dossier pictures:
Code
[jaypee:~/workspaces/Ruby]$ mkdir pictures
[jaypee:~/workspaces/Ruby]$ irb
irb(main):001:0> ile.open '/Users/jaypee/workspaces/Ruby/fakeimg.sh', 'w' do |f| (11..32).each { |i| (11..12).each { |j| f.write "touch pictures/G_#{i.to_s}_#{j.to_s}_ZF_1_XYZ.jpg\n" } } end


On a créé un script fakeimg.sh qui va les créer:
Code
[jaypee:~/workspaces/Ruby]$ chmod +x ./fakeimg.sh
[jaypee:~/workspaces/Ruby]$ ./fakeimg.sh
[jaypee:~/workspaces/Ruby]$ ls -l pictures
total 0
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_11_11_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_11_12_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_12_11_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_12_12_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_13_11_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_13_12_ZF_1_XYZ.jpg
-rw-r--r--  1 jaypee  staff  0  2 nov 22:24 G_14_11_ZF_1_XYZ.jpg
...


Enfin, on utilise la commande find
Code
[jaypee:~/workspaces/Ruby]$ find pictures -name "*jpg" -exec /users/jaypee/workspaces/Ruby/dims2exif.sh {} \;
11
20x60x10
11
20x60x10
12
30x20x5
12
30x20x5
...


La fin finale ? Pas encore. Un exemple de projet Xcode/Cocoa avec de l'AppleScript comme moteur:
http://stackoverflow.com/questions/34834976/basic-xcode-7-example-project-that-uses-applescript-and-a-gui l'article contient un lien sur un zip d'exemple

Les deux seuls fichiers à modifier:
- MainMenu.xib pour l' interface utilisateur
- AppDelegate.applescript pour le code
Le reste est fourni et à utiliser tel quel.

J-P

Écrit par : teddy7545 3 Nov 2016, 03:57

Speed Moock

Le problème de fond et les discussions engagées sont très intéressantes et j'y regarderais d'un peu plus près quand je serais de retour de déplacement ou je suis actuellement.
Personnellement c'est vrai que je me serais davantage tourné vers un shell script pur voir un code ObjC.
Au pire il est assez simple de faire un interface ASOC (applescript objectiveC) et d'appeler un shell ou tout autre code plus performant que AS.
Dans tous les cas si la réécriture de tous les fichiers est nécessaire , le traitement de centaines de fichiers prendra toujours un certain temps dépendant du support sur lequel les fichiers se trouvent.
Les pistes d'optimisations déjà amorcées sont en tous les cas nécessaires et fort intéressantes.
Dans l'attente de regarder cela de plus près, je répond aux problèmes que tu as rencontré dans l'usage du script Shell.

Citation
Là où je bloque c'est sur la boucle. J'ai bien un find qui fonctionne en mode terminal direct, et me donne la liste des fichiers voulus:
Code
find  /dossier -name  "G_*.jpg“


Mais dans mon shell script, si je fais un find $1 -name "G_*.jpg", j'ai une erreur sur le directory.
-idem en changeant les " par des '
-idem si je remplace directement le $1 par le dossier en litéral.

J'ai essayé de définir une variable Liste à partir de find pour faire une boucle for...sans succès !!
Code
Liste= find mon_dossier -name 'G_$.jpg'
for fichier in Liste
do
echo 'voici mon fichier=' fichier
done

Cela ne marche pas. Je ne comprends pas mon erreur.

Pour le problème de find c'est manifestement /dossier qui pose problème et sans doute pas la suite.
Il faut donc vérifier si tu entre un chemin relatif ou non (si tu utilises un chemin relatif tu dois alors tenir compte de l'endroit ou se trouve le script)
Pour éviter les erreurs et les problèmes, il est plus simple d'entrer un chemin complet partant de la racine comme argument
Pas de raison que ça ne fonctionne pas (je suppose qu'il n'y a pas d'espace dans ton chemin sinon quelques précautions supplémentaires sont à prévoir).
Au besoin indiques nous quel chemin tu utilises précisément comme argument, ou as tu stocké ton script et éventuellement depuis quel répertoire tu le lance.

Ensuite pour définir une variable tu dois faire :
Code
Liste=$(find "$1" -name 'G_*.jpg')
Pour avoir la liste entière (dans cette version tu peux utiliser les ' ou les " indifféremment

ou
Code
Liste=$(find "$1" -name "G_$2*.jpg")
Si tu veux n'effectuer la tache que sur le fichier défini dans ta variable $2 (attention la substitution exige alors l'usage des " )

Pour l'utilisation de ta boucle, tu as fais plusieurs erreurs sur l'usage de tes variables.
Si tu défini la variable Liste .... tu devras alors utiliser $Liste pour l'appeler (idem avec fichier qui s'appelle avec $fichier)

Ci-dessous un petit exemple qui fonctionne chez moi et basé sur le début de code que tu avais proposé:
Code
#!/bin/bash
# test des paramètres d’entrée
if [ $# != 2 ]
then
echo ‘Erreur: il faut 2 paramètres, le dossier et le fichier des dimensions’
exit
fi

Liste=$(find "$1" -name "G_*.jpg")
for fichier in "$Liste"
do
echo 'voici mon fichier=' "$fichier"
done
exit


Bon courage

Écrit par : Speed Moock 3 Nov 2016, 07:38

Bonjour TEddy7545

Le sujet m'échappe complètement et je laisse Jaypee et PBell faire avancer leurs recherches. C'est ce que je trouve magique avec ce forum et l'informatique de passionnés, un sujet a priori simple révèle énormément de subtilités.
Pour ma part je suis sur la version 100% applescript qui tourne lentement mais au moins j'en comprends le moteur.
Les échanges restent toutefois intéressants !

Merci à tous,

Écrit par : Jaypee 3 Nov 2016, 11:47

Speed Moock,

J'entends ton souhait.

Je vais faire un dernier test de perf en quasiment vrai grandeur, je vais utiliser de faux fichier jpg, juste pour leur nom, et utiliser une vrai image sacrifice dans la quelle je vais réellement réécrire les EXIF data. Ainsi toutes les vraies opérations sont simulées.

Le choix entre tout comprendre et avoir une solution utilisable sera facile à faire. Ce sera du genre plusieurs heures d'attente contre quelques minutes pour traiter un dossier complet.

Cela n'empêche pas que la solution restera lisible et facilement modifiable.

Je me rends compte de "corner cases":
- A quoi sert l'index avant l'Id unique de la fin du nom?
- Que faire si on a plusieurs index pour le même code produit, on utilise les mêmes dimensions pour tous les index ?

J-P

Écrit par : Speed Moock 3 Nov 2016, 13:36

Bonjour Jaypee,
- l'index est un identifiant unique généré par notre outil de shooting afin de garantir l'unicité des images, même si un produit revient en shooting plusieurs semaines après (un produit est défini par le couple code produit & code couleur.)
- il peut y avoir dans le listing excel des doublons de code produit mais ils auront toujours la même taille Exif à rajouter. Du coup le script peut s'arrêter au premier trouvé.

A terme je devrai faire évoluer le script pour écrire plusieurs EXIF renseignées dans les autres colonnes (C, D et E). J'essaierai d'être malin et de bien comprendre le code.

Quoiqu'il en soit merci encore

Écrit par : PBell 3 Nov 2016, 19:09

Bonsoir Teddy7545,
Grâce à tes conseils, je crois que je progresse un peu.

Mon script est presque complet : il prends tous les fichiers des sous dossier, pour chacun, il extrait le code produit, puis recherche, dans la base, ce code et extrait la colonne 2 contenant les dimensions.
J'ai ajouté un test au cas où il ne trouve pas de dimensions (code produit inexistant dans la base): dans ce cas , pas d'exif.

Enfin, dans le cas où des dimensions sont trouvées, je peux faire l'exiftool.

CODE
#!/bin/bash
# une fois ce texte script enregistré en .sh, faire un chmod +x xxx.sh pour le rendre exécutable
# test des paramètres d’entrée
if [ $# != 2 ]
then
echo ‘Erreur: il faut 2 paramètres, le dossier et le fichier des dimensions’
exit
fi

Liste=$(find "$1" -name "G_*.jpg") #liste les fichiers dans les dossiers et sous dossiers

for fichier in $Liste
do
produit=$(basename $fichier  | cut -c3-8) # pour chaque fichier, extrait le produit = caractères 3 à 8 du nom
pdim=$(grep $produit $2 | cut -c12-100) # cherche via le produit, les dimensions à partir du car 8 (après 6 car de produit et 1 tab)

if [ -z $pdim ]
then
echo “Pas de dimension pour “ $produit
else
echo ‘dim=‘  ‘“$pdim“’
echo ’fichier=‘ $fichier
/usr/local/bin/exiftool -ImageDescription=“test N2“ -Overwrite_Original $fichier
fi
done
echo ‘fin de script’
exit


Comme tu le vois, j'ai remplacé dans cette version la variable pdim (qui doit contenir les dimensions) par "test N2" donc avec un espace...et c'est là mon problème.
C'était, je l'ai compris avec ton post précédent, déjà mon problème de boucle dans un de mes sous dossier avec des espaces dans le nom.
Si le contenu de la variable ne contient pas d'espace, tout va bien et le script fonctionne entièrement.
Si le contenu de la variable contient des espaces, le shell interprète ces espaces comme des séparateurs...d'où les erreurs.

J'ai encore des doutes sur l'utilisation des " et des ' , mais je n'arrive pas à maîtriser les cas où une variable (nom de fichier ou ces dimensions) contient des espaces.
Comment demander au Shell de ne pas les interpréter ? Comment forcer l'utilisation des variables avec des ' (comme en AS)?

J'ai besoin de lumière. Merci d'avance.

Écrit par : Speed Moock 3 Nov 2016, 21:09

Bonsoir,

Une question sur la commande do shell script :
comment y implanter plusieurs instruction successives similaires ?
exemple :
do shell script "/usr/local/bin/exiftool -Title='" & DimText4 & "' -Overwrite_Original " & quoted form of (UnixPath)
do shell script "/usr/local/bin/exiftool -City='" & DimText5 & "' -Overwrite_Original " & quoted form of (UnixPath)
que je voudrai regrouper sous une unique do shell script
do shell script "/usr/local/bin/exiftool -Title='" & DimText4 & "'; "/usr/local/bin/exiftool -City='" & DimText5 -Overwrite_Original " & quoted form of (UnixPath)

merci
& "' -Overwrite_Original " & quoted form of (UnixPath)

Écrit par : PBell 3 Nov 2016, 21:37

Bonsoir SpeedMoock,
Il faut mettre tous les champs à modifier à la suite avec des espaces entre :

CODE
/usr/local/bin/exiftool -ImageDescription=‘testPC’ -City='testCity' -Title='Mon_titre' -Overwrite_Original Unixpath


Ce qui donne, en appelant via Applescript pour Title et City:
CODE
do shell script "/usr/local/bin/exiftool -Title='" & DimText4 & "' -City='" & DimText5 -Overwrite_Original " & quoted form of (UnixPath)

(ne pas oublier les espace entre les champs)

Cordialement

Écrit par : Jaypee 3 Nov 2016, 21:46

Je suis en train de faire un timing grandeur nature je m'attends à plus d'une heure de traitement pour 32000 images. J'ai fait 32000 copies d'un petit jpeg de 16K avec les noms qui vont bien. il faut compter 1/3 à 1/2 seconde par image, soit entre 10000 et 16000 secondes en tout.

@PBell regarde la commande tr (fais un man tr dans le Terminal)

Code
[jaypee:~]$ x=`echo "A B CD E F" | tr " " "_"`
[jaypee:~]$ echo $x
A_B_CD_E_F

Merci pour la syntaxe de commande exiftool

Voici mon script final
Code
#!/bin/sh
###############
# Script: dims2exif.sh
###############
if [ $# -lt 1 ]; then
    echo Usage: $0 chemin/vers/image.jpg
    exit 0
fi
fichier=`basename $1`
prod=`echo $fichier | awk -F _ '{print $2}'`
dims=`sqlite3 ./produits.db "select dimensions from produits where code = '$prod'"`
exiftool -ImageDescription=$dims  -Overwrite_Original $1 > /dev/null
echo $1


Et la commande pour l'utiliser:
Code
find pictures/ -name "*jpg" -exec /users/jaypee/workspaces/Ruby/dims2exif.sh {} \;


Un des fichiers modifié:
Code
[jaypee:~/workspaces/Ruby]$ exiftool -exif:ImageDescription pictures/G_14490_12_ZF_1_XYZ.jpg
Image Description               : 10x20x5

J-P

Écrit par : teddy7545 4 Nov 2016, 02:49

Bonsoir,

Désolé Speed Moock, j'avais en effet lu un peu trop vite les messages.
Mon message précédant s'adressait effectivement davantage aux questions de PBelle qu'à celles de Speed Moock (même si l'objectif final commun est de pouvoir contribuer à solutionner son problème).

Même si Jaypee a avancé de son côté en proposant de nouvelles évolutions de son script, je répond ici pour les problèmes rencontrés par PBelle dans son dernier script avec la boucle.

@PBell
Attention à ne pas confondre ‘ avec ' ou encore “ avec "
En effet, en shell ces caractères ne se comportent pas de la même façon.

Dans tes exemples, les quotes utilisés ne sont pas toujours les bons...ce qui explique la majorité de tes difficultées

Si tu utilises simplement un texte comprenant un espace, les simples quote suffisent --> ' (mais pas ‘)
Par contre, si tu utilises une variable ($xx) , qui potentiellement contiendra un espace ou des espaces, il faudra utiliser les doubles quotes qui permettent l'expansion de la variable (y compris si elle contient un ou des espaces) dans la chaine de caractère --> (mais pas “).

Si je reprends ton script directement, en y apportant les corrections nécessaires, cela devait fonctionner comme cela :

Code
#!/bin/bash
# une fois ce texte script enregistré en .sh, faire un chmod +x xxx.sh pour le rendre exécutable
# test des paramètres d’entrée
if [ $# != 2 ]
then
echo ‘Erreur: il faut 2 paramètres, le dossier et le fichier des dimensions’
exit
fi

Liste=$(find "$1" -name "G_*.jpg") #liste les fichiers dans les dossiers et sous dossiers

for fichier in "$Liste"
do
produit=$(basename "$fichier"  | cut -c3-8) # pour chaque fichier, extrait le produit = caractères 3 à 8 du nom
pdim=$(grep "$produit" "$2" | cut -c12-100) # cherche via le produit, les dimensions à partir du car 8 (après 6 car de produit et 1 tab)

if [ -z "$pdim" ]
then
echo 'Pas de dimension pour ' "$produit"
else
echo 'dim = '  "$pdim"
echo 'fichier = ' "$fichier"
/usr/local/bin/exiftool -ImageDescription="$pdim" -Overwrite_Original "$fichier"
fi
done
echo 'fin de script'
exit


J'ai également ajouté des doubles quotes autours des variables $1 et $2 pour permettre éventuellement des arguments avec des espaces.
Attention toutefois, pour utiliser un/des arguments avec espace, il faudra également nécessairement utiliser les doubles quotes pour cet argument dans la ligne de commande.

exemple:
Code
maFonction "Argument 1" "Argument 2"

Dis nous si ça fonctionne bien avec ces nouvelles modifications.

Bon courage.

Écrit par : Jaypee 4 Nov 2016, 06:48

Une note en forme de conclusion sur les performances pour un traitement réel de 32000 images

7041,09s user 740,67s system 91% cpu 2:21:44,00 total

Ce temps s'obtient en précédent l'appel au script de la commande time
time find pictures -name "*jpg" -exec /users/jaypee/workspaces/Ruby/dims2exif.sh

Comment générer ces images de test. Reprenez la ligne de Ruby avec la commande touch, il faut utiliser un "cp modele.jpg " au lieu de "touch ".

Validation au hasard:

Code
[jaypee:~/workspaces/Ruby]$ sqlite3 produits.db 'select * from produits where code="999"'
999|50x20x10
[jaypee:~/workspaces/Ruby]$ exiftool -exif:ImageDescription pictures/G_999_12_ZF_1_XYZ.jpg
Image Description               : 50x20x10


Au passage, petite remarque sur le grep:
CODE
[jaypee:~/workspaces/Ruby]$ grep 999 bdproduits.csv
999,50x20x10
1999,50x40x10
2999,50x60x10
3999,50x20x10
4999,50x40x10
5999,50x60x10
6999,50x20x10
7999,50x40x10
8999,50x60x10
9990,10x20x5
9991,20x40x10
9992,30x60x5
9993,40x20x10
9994,50x40x5
9995,10x60x10
9996,20x20x5
9997,30x40x10
9998,40x60x5
9999,50x20x10
10999,50x40x10
11999,50x60x10
12999,50x20x10
13999,50x40x10
14999,50x60x10
15999,50x20x10
16999,50x40x10
17999,50x60x10
18999,50x20x10
19990,10x40x5
19991,20x60x10
19992,30x20x5
19993,40x40x10
19994,50x60x5
19995,10x20x10
19996,20x40x5
19997,30x60x10
19998,40x20x5
19999,50x40x10
20999,50x60x10
21999,50x20x10
22999,50x40x10
23999,50x60x10
24999,50x20x10
25999,50x40x10
26999,50x60x10
27999,50x20x10
28999,50x40x10
29990,10x60x5
29991,20x20x10
29992,30x40x5
29993,40x60x10
29994,50x20x5
29995,10x40x10
29996,20x60x5
29997,30x20x10
29998,40x40x5
29999,50x60x10
30999,50x20x10
31999,50x40x10

Il faut au minimum
grep "^999," pour trouver:
999,50x20x10

Fidèle à mon principe, moins y'a de lignes, moins ça risque de planter, ma solution ne demande que 5 lignes de code, 1 ligne pour créer un bd SQLite, une autre pour créer une table, et deux de mieux pour importer le fichier CSV et elle a des performances connues. En fait seules deux lignes sont ardues:
- Extraire le produit du nom de fichier
prod=`echo $fichier | awk -F _ '{print $2}'`
- Extraire les dimensions de la bd
dims=`sqlite3 ./produits.db "select dimensions from produits where code = '$prod'"`

Dans ces deux lignes, même "pattern". Une commande qui va bien dans un terminal... comment capturer son résultat dans une variable dans un script: Avec l'accent grave
var=<ag><commande qui va bien><ag>, l'absence d'espace entre var et = est importante, sinon var est considéré comme une commande comme cd ou ls...

Merci à tous, en tout cas, j'ai appris plein de choses intéressantes en vous lisant avec ce problème apparemment anodin!

J-P

Écrit par : PBell 4 Nov 2016, 15:46

Grand merci à Teddy7545 : mon script fonctionne grâce à tes conseils !
Effectivement, j'avais mélangé les " et les “ que mes pauvres yeux n'ont pas détectés.

Merci aussi à Jaypee, car tes lignes m'ont indirectement aidées.
Effectivement le '^' du grep que j'avais mis sur le premier script a malencontreusement disparu des mes versions suivantes. Le voilà de retour à sa place !
J'aime bien ton idée de ne pas mettre la boucle dans le script mais d'appeler le script autant de fois que nécessaire. Je me demande cependant si, d'un appel à l'autre, le script reste en mémoire pour des raisons de performances...?

Par ailleurs, si j'ai bien compris la fonction 'awk' que tu utilises, elle est mieux que mon 'cut' qui suppose des longueurs fixes (mais cela faisait partie du descriptif de Speed Monck). Du coup, je me dis qu'il serait aussi judicieux de l'utiliser pour le résultat de mon 'grep'.
Mais dans ce cas, comment specifier que le séparateur -F est une tabulation (ascii 9) ...que je ne peux taper ?

Enfin, ai-je bien compris que le $(commande) pouvait être remplacé par `commande`? Les ag me semblent plus facile à lire (et on sait ce que la lisibilité est primordiale).

Merci à tous pour votre aide et ce cours.
Je ne m'étais jamais vraiment mis au shell, à part qq instructions dans le Terminal souvent appelées via AS.
... Je sens que je vais continuer à jouer un peu ;-)

Cordialement

Écrit par : Jaypee 5 Nov 2016, 07:44

@PBell

Apparemment, awk se comporte aussi naturellement que possible

Code
echo "A\tBC\tD\tE" | awk -F \t '{print $2}'
répond "BC". Il y a une autre méthode plus compliquée, mais celle-ci me convient. Awk utilise en réalité une variable interne FS, et le script awk avant la première accolade, peut commencer par initialiser FS
Code
echo "A\tBC\tD\tE" | awk 'BEGIN{FS="\t"};{print $2}'

Oui le $ ou l'accent grave se comportent comme une fonction "excecute", ils évaluent l'expression enfermée.

Pour la question de performance, il est assez facile de faire des tests et de décider. Ma méthode fait du 4.5 images/s. Si une autre méthode est plus efficace, il faut choisir l'autre méthode. Je ne défends pas la mienne mordicus. Je t'encourage à essayer mon procédé. Copie une image bidon sous 10 noms selon le codage de Speedy Moock et tourne ton script avec time devant. Puis 100, 1000, 3000, puis 10000, puis 32000. Comme tu maitrises les shell fait un script, qui te fabriquera un autre script shell qui fera le boulot. On appelle ça de la méta-programmation, programmer le programme smile.gif Sinon, fait confiance à mon script Ruby.
Code
File.open 'chemin/vers/imagestest.sh', 'w' do |f| (11..16000).each { |i| (11..12).each { |j| f.write "cp chemin/vers/mon/bidon.jpg pictures/G_#{i.to_s}_#{j.to_s}_ZF_1_XYZ.jpg\n" } } end
puis tu exécutes "sh imagestest.sh"

La commande find recherche récursivement sous la racine, et les fichiers jpg ou png ou gif se trouvant n'importe où sous la racine vont à un moment ou un autre être "visités". C'est plus l' illustration du "pattern" visiteur qui est intéressant, surtout si le parcours est "gratuit" fourni par une commande du système.

Enfin cela illustre aussi la notion d'inversion de contrôle qui est extrêmement importante. Ce n'est pas mon code qui doit aller chercher les données mais ce sont les données qui sont apportées à mon code. Dans un monde dit de "big data", c'est la seule approche possible, si les data sont infinies, on ne pourra jamais faire une boucle finie dessus, il faut se contenter d'une seule donnée, puis de la suivante quand elle arrivera...

Je reste attentif aux problèmes intéressants dont la résolution fait avancer la compréhension et la connaissance de la bidouille Mac. La création d'une vraie Interface Graphique avec Xcode, ou une page HTML pourrait conduire à d'autre idées créatives smile.gif PHP peut aider !
Code
<?php
shell_exec("yourscript.sh");
header('Location: http://www.website.com/page?success=true');
?>


J-P

Propulsé par Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)