Outils pour utilisateurs

Outils du site


issue134:mon_histoire

Last month we ended on a happy note, with all the information we needed to attempt to solve the issue at stake. What was it? I wanted to be able to enter sort information in Rhythmbox in an easier way than to do it manually track by track. Which is boring when all the tracks on the same album share the the same data, plus it was frustrating that at times they even got lost (maybe a bug, maybe only my case). We figure out that an xml file contains the data we want to manipulate and python offers an xml library ready to do it. So what is next? I had to decide if I wanted to write a command line interface program (CLI) or a graphical user interface (GUI). The former are the text only ones you launch from the terminal, like grep or mp3diags, the latter ones are with windows and icons like audacity or Gnome text editor (gedit). Surely enough a GUI program is nicer and looks like the preferable choice. As I said in the introduction my programming knowledge needed a brush up and I had no experience in linux / gnome graphic environment. Add to that it would be my first python program I was very sceptical on going down that road.

Le mois dernier, nous avons terminé sur une note positive, car nous avions toutes les informations nécessaires pour essayer de résoudre mon problème. Qu'est-ce que c'était ? je voulais pouvoir entrer des informations de tri dans Rhythmbox plus facilement que manuellement, piste après piste. Ce qui est ennuyeux quand toutes les pistes sur un même album partagent les mêmes données ; en plus, ça me frustrait quand, parfois, ces informations se perdaient (peut-être un bug, et peut-être uniquement dans mon cas). Nous avons compris qu'un fichier xml contient les données que nous voulons manipuler et que Python propose une bibliothèque xml prête à le faire. Alors, que fait-on ensuite ?

Je devais décider si je voulais écrire un programme en ligne de commande (CLI) ou avec une interface utilisateur graphique (GUI). Un programme écrit pour CLI est lancé dans un terminal, par exemple grep ou mp3diags alors qu'en GUI, il y a des fenêtres et des icônes, par exemple Audacity ou l'éditeur de texte de Gnome (gedit).

Bien entendu, un programme en GUI est plus sympa et semble le choix préférable. Comme j'ai dit dans l'introduction, j'avais besoin de rafraîchir mes connaissances en programmation et je n'avais aucune expérience dans un environnement graphique Linux/Gnome. Si on y ajoute que ce serait mon premier programme en Python, l'idée de prendre cette voie-là me rendait très sceptique.

I have also seen that some linux applications are CLI based and then they also have GUI version, which looked like it was using the command based program to do the job. For example some software management application, like the graphical updater, are invoking a terminal command (apt) and then rendering the output in a more friendly way. Thus my decision was made. I was going to write a CLI based program and eventually I’d write a GUI interface on top of it. I called it fixrhy on the assumption that it was fixing something that rhythmbox wasn’t doing the way I wanted it. I know it’s arguable but hey, my application, my name! Ready to start my first ever python program I launched the Gnome text editor which I figured was more than enough for the task at hand.

J'ai également remarqué que certaines applications Linux sont basées sur CLI, mais ont également une version GUI, qui me donnait l'impression qu'il utilisait le programme en ligne de commande pour faire le travail, notamment des applications de gestion de logiciels, comme la mise à jour en format graphique, invoquent une commande du terminal (apt) et donnent le résultat d'une façon plus conviviale. Ainsi, j'ai pris ma décision. J'allais écrire un programme en CLI et ultérieurement j'écrirais une interface GUI par-dessus.

Je l'ai appelé fixrhy en supposant qu'il réparerait quelque chose que rhythmbox ne faisait pas comme je voulais. Je sais que c'est discutable, mais après tout, c'est mon application et je peux lui donner le nom que je veux.

Étant prêt à commencer mon tout premier programme en Python, j'ai lancé l'éditeur de texte de Gnome qui me semblait plus que suffisant pour la tâche à accomplir.

The logic of the program is very simple. As input it requires the name of the artist, the name of the album, the sorting name of the artist and the sorting name of the album. After writing the basic code I added a fifth parameter which I’ll talk about later. The output would be just some confirmation messages, while the real action is actually manipulating the xml file that rhythmbox uses to store its database. The full code of the final version is available here: https://pastebin.com/zBuhmi1y and I would like to go through it not to teach python or programming but to share the logic and its development. I will stop only on significant parts of the program. Line 14 is: #!/usr/bin/python

La logique du programme est très simple. Vous devez renseigner le nom de l'artiste, le nom de l'album, le nom de tri de l'artiste et le nom de tri de l'album. Après avoir écrit le code de base, j'ai rajouté un cinquième paramètre dont je parlerai plus tard. La sortie sera tout simplement quelques messages de confirmation, tandis que la véritable action manipule en fait le fichier xml où rhythmbox stocke sa base de données. Le code complet de la version finale est disponible ici : https://pastebin.com/zBuhmi1y et je vais vous le présenter, pas pour vous enseigner le Python ou la programmation, mais pour en partager la logique et son développement. Je m'arrêterai uniquement sur les parties significatives du programme.

La ligne 14 est : #!/usr/bin/python

Technically this is needed to execute the code as a Python program. To me it is the statement that I wanted to put in practice something I learned on Full Circle pages, and knowing that if I need help there’ll be a community out there ready to help. I could have used C (or C++) that I already knew, but that would have taken away a bit of the fun. Learning something new it is always challenging and it offers more opportunity of reaching out for others and live in the community. Case in point, I eventually wrote to Greg Walter and Ronnie Tucker (I’ll come back to it later on). Line 18 is: import xml.etree.ElementTree as ET This is required to be able to use the ElementTree library to parse XML files. I could have parsed it myself, but it would have been more work for no good reason. If tomorrow the XML specifications change or evolve you can also count on the library to update along. This means that there are great chances that your code will still work as it is. For that reason note that if you google “python xml” that ElementTree comes at the top in the official Python documentation. That gave me the peace of mind that I was working with a fully supported library. Line 22 is: sys.setdefaultencoding( “UTF-8” )

Techniquement, ceci est nécessaire pour que le code soit exécuté comme un programme en Python. Pour ce qui me concerne, c'est la déclaration que je voulais mettre en pratique, quelque chose que j'ai appris dans les pages du magazine Full Circle, tout en sachant que, si j'ai besoin d'aide, une communauté existe qui serait prête à m'aider. J'aurais pu utiliser C (ou C++) que je connaissais déjà, mais cela aurait enlevé un peu de mon plaisir. Apprendre quelque chose de nouveau est toujours un défi et il offre plus d'occasions de faire appel à d'autres et de participer à la vie de la communauté. Un exemple concret est que j'ai fini par écrire à Greg Walter et à Ronnie Tucker (J'y reviendrai plus tard).

la ligne 18 est : import xml.etree.ElementTree as ET

Ceci est nécessaire pour pouvoir utiliser la bibliothèque ElementTree pour analyser les fichiers XML. J'aurais pu le faire moi-même, mais cela aurait fait plus de travail sans bonne raison. Si demain les spécifications XML changent ou évoluent, vous pouvez compter sur la mise à jour de bibliothèque en conséquence. Cela signifie qu'il y a de grandes chances que votre code continuera à fonctionner tel quel. C'est pour cette raison-là que, si vous recherchez « python xml » sur Google, ElementTree se trouve à la première place dans la documentation Python officielle. Savoir que je travaillais avec une bibliothèque entièrement prise en charge m'a bien tranquillisé l'esprit.

La ligne 22 est : sys.setdefaultencoding( “UTF-8” )

If I recall correctly this became necessary after struggling with accented letters (as in Beyoncé) that are quite common in names. Next I have to check if I have enough information to go on. Remember we said we need 4 pieces of data, 2 to identify an album (artist and album name) and 2 to set their respective sort values. The fifth parameter is option (to override existing sorting information). We do it at line 25: if len(sys.argv) < 5: Note that we check for 5 since the first value is always the script name (check https://docs.python.org/2/library/sys.html). Lines 26 to 30 print out a little help. I wrote them to be consistent with typical Linux programs, although in the beginning this script was mainly for me. I tried to think about others and follow standards everyone could relate to. Moving along you’ll see that in lines 32 to 35 we save the input data and 37-40 are to check if there is the extra flag “force”.

Si je m'en souviens bien, cela est devenu obligatoire après m'être battu avec des lettres accentuées (comme dans Beyoncé) qui paraissent assez souvent dans des noms.

Ensuite, je dois vérifier si j'ai assez d'informations. Rappelez-vous, on a dit qu'il faudrait 4 données, 2 pour identifier un album (l'artiste et le nom de l'album) et 2 pour régler leurs valeurs de tri respectives. Le cinquième paramètre est l'option d'outrepasser les informations de tri existantes. Nous le faisons à la ligne 25 :

if len(sys.argv) < 5:

Notez que nous vérifions pour 5, puisque la première valeur est toujours le nom du script (regardez à https://docs.python.org/2/library/sys.html). Les lignes 26 à 30 affichent de l'aide. Je les ai écrites pour rester cohérent avec les programmes typiques sous Linux, bien que, au départ, ce script était principalement pour moi. J'essayais de penser aux autres et de respecter des normes auxquelles tout le monde pourrait adhérer. Continuez et vous verrez que, dans les lignes 32 à 35, nous sauvegardons les données saisies et les lignes 37-40 vérifient s'il y a un drapeau supplémentaire « force ».

Line 42 and 43 looks like debugging lines: print “Looking for”, lk_alb, “by”,lk_art print “Sorting as”, sr_alb, “by”, sr_art and indeed they were at the beginning. I kept them there because again I thought that tomorrow I (or someone else) could write a GUI program on top of it, and that could help if being parsed. From 45 to 50 we set everything up, loading the Rhytmbox database into a tree (47) and declaring a couple of counting variables. The real processing starts at line 52 where go through every entry in the tree. Here is just a simple sequence of checks, is it a song? Is it the right artist? Is it the right album? if entry.attrib == {'type': 'song'}: if entry.find('artist').text == lk_art: if entry.find('album').text == lk_alb: If any of them is false we can discard that given entry since we don’t need to amend it.

Les lignes 42 et 43 ressemblent à des lignes de débogage : print “Looking for”, lk_alb, “by”,lk_art

print “Sorting as”, sr_alb, “by”, sr_art

et, en fait, elles l'étaient au départ. Je les ai gardées parce que, à nouveau, je pensais que, le lendemain, je (ou quelqu'un d'autre) pourrais écrire un programme GUI par-dessus et qu'elles pourraient aider lors de l'analyse.

De 45 à 50, nous réglons tout, chargeant la base de données de Rhythmbox dans un arbre (47) et déclarant quelques variables de comptage.

Le véritable traitement commence à la ligne 52 ou nous parcourons chaque entrée dans l'arbre. Voici une séquence simple de vérifications. S'agit-il d'une chanson ? Est-ce le bon artiste ? Est-ce le bon album ?

if entry.attrib == {'type': 'song'}:
 
if entry.find('artist').text == lk_art:

if entry.find('album').text == lk_alb:

Si une entrée quelconque est fausse, nous pouvons la supprimer, puisque nous n'avons pas besoin de l'amender.

From line 58 we check if the artist sort information is already there. The reason being that if the field is missing we need to create it new (60), if it is already there we have to change it (or leave it unchanged). Former case we could not modify something not existing, latter case we should not create something that is already there. The rest of the code builds on that, and there is a similar part of the album sort information. The only two points I want to make here are that I put in the code the parameter to choose if to modify existing data or not. Personally I would always change it, but I wanted to write a program that eventually could be used by others, maybe with different needs. Also note that since I couldn’t change the element if existing I destroy and create a new one (68-70). Most of the print commands are there for debug originally and then to be used by GUI as I said before about line 42/43.

À partir de la ligne 58, nous vérifions si l'information de tri de l'artiste existe déjà, parce que, si le champ est manquant, il faut le créer à nouveau (60) et s'il y est déjà, nous devons le changer (ou le laisser tel quel). Dans le premier cas, nous ne pourrions pas modifier quelque chose qui n'existe pas, et dans le dernier, nous ne devrions pas créer quelque chose qui est déjà présent.

Le reste du code s'appuie dessus et il y a une partie similaire pour les informations de tri de l'album. Autrement dit, j'ai mis dans le code le paramètre nécessaire pour choisir si on modifie des données existantes ou pas. Personnellement, je le changerai toujours, mais je voulais écrire un programme qui pourrait finalement être utilisé par d'autres, peut-être avec des besoins différents. Il faut également noter que, puisque je ne pourrais pas changer l'élément s'il existe, je le détruis et j'en crée un nouveau (68-70). La plupart des commandes print étaient là au départ pour le débogage, puis pour être utilisées par le GUI, comme j'ai dit auparavant au sujet des lignes 42/43.

Finally before saving we make a backup copy (always important!): shutil.copy2(filename,backup) and the tree that has been so long in the memory goes in the file. tree.write(filename, xml_declaration=True) I am quite happy with my first Python program ever. It does what it says on the tin but there is room for much improvements. Amongst them surely pass the file position as a parameter (like this you should run the script in the folder with the Rhythmbox DB file). The parsing maybe not the most efficient and fast. The naming could be done automatically, for example a clever program could learn that “The Doors” should be “Doors, The” without entering it every single time. And many more, including having a GUI. And that’s where we are heading to…

Enfin, avant de l'enregistrer, nous créons une copie de sauvegarde (toujours important à faire !) :

shutil.copy2(filename,backup)

et l'arbre qui est en mémoire depuis si longtemps va dans le fichier.

Mon tout premier programme en Python me plaît bien. Il fait ce qu'il est censé faire, mais il y a une marge de progression et il peut être amélioré. Parmi de telles améliorations, on pourrait certainement passer la position du fichier comme un paramètre (car, tel qu'il est, vous devrez exécuter le script dans le dossier contenant le fichier Rhythmbox DB). L'analyse n'est peut-être pas la plus rapide et efficace. Les noms pourraient être attribués automatiquement, par exemple, un programme astucieux pourrait apprendre que « The Doors » devrait être « Doors, The » sans avoir besoin de le saisir chaque fois. Et beaucoup plus encore, y compris un GUI. C'est vers cela que nous nous dirigeons…

issue134/mon_histoire.txt · Dernière modification : 2018/07/25 20:04 de andre_domenech