Journal D'une playlist xspf vers des mp3 dans un répétoire

Posté par . Licence CC by-sa.
Tags :
8
30
sept.
2019

Bonjour,

Suite à la perte de mon téléphone, je me suis retrouvé avec un petit nouveau et une carte SD toute vide, sans musique.
Et j'aime la musique! J'ai plein de playlists que j'adore.

Alors évidement j'ai voulue tranférer les mp3 de mes playlists dans la carte sd du petit nouveau.
En gros: Prendre la playlist, et sauvegarder les mp3 présent dans un répertoire 'destination' pour ensuite les copier sur mon télephone.

J'ai beau chercher une option dans vlc, je n'ai rien trouvé. Pareil sur le net, sauf un bout de code en php-cli.

Il y a de superbes dépêches sur python en ce moment, alors je me suis pourquoi pas coder un petit truc!
C'est sans prétention, je ne suis pas un dev python et je n'y ai pas touché depuis 2 ans, mais ca juste marche chez moi.
Je suis ouvert à toutes les critiques, justement j'aimerais bien avoir un retour sur ce code.

J'envisage ensuite de supporter d'autres format(m3u etc)
Enfin, ca y est, j'ai un répértoire à copier sur ma carte SD et je vais pouvoir ecouter mes playlists partout!

Usage: pl2mp3 playlist repertoire
(Les liens doivent être locaux)

Ca donne un truc comme ca

#!/usr/bin/env python3

import argparse
import shutil
import os
import urllib.parse
from bs4 import BeautifulSoup


class Pl2mp3(object):

    args = False


    def __init__(self):
        pass


    def start(self):


        self.argparse()


        #Openning the playlist file
        try: 
            pf1 = open(self.args.playlist)
            pls = pf1.read()
        except IOError:
            print("Can't open playlist ", self.args.playlist)
            return


        #Creating output directory if not exists
        if not os.path.exists(self.args.outputdirectory):
            try:
                os.makedirs(self.args.outputdirectory)
                print("Creating output directory", self.args.outputdirectory)
            except OSError:
                print("Can't create output directory", self.args.outputdirectory)
                return


        xspf = Xspf()


        for location in xspf.parse(pls):
            print(location)
            try: 
                shutil.copy2(location, self.args.outputdirectory)
            except IOError:
                print("Cant' copy ", location)
                pass

            #et la on try except sur la copie c'est tout

        pf1.close()   




    def argparse(self):
        parser = argparse.ArgumentParser()
        parser.add_argument("playlist", help="Path to the playlist file")
        parser.add_argument("outputdirectory", help="Path to the output directory")
        self.args = parser.parse_args()




class Xspf(object):

    def __init__(self):
        pass



    def parse(self, content):
        soup = BeautifulSoup(content,'xml')

        for result in soup.find_all('track'):
            location = urllib.parse.unquote(result.location.string)

            if location.startswith("file://"):
                location = location.replace("file://", '')
                yield location



if __name__ == '__main__':
    main = Pl2mp3()
    main.start()
  • # .

    Posté par . Évalué à 6 (+4/-0).

    Je vois, dans ce code, une belle illustration de l'utilisation de argparse, de BeautifulSoup (que je ne connais que de nom) pour traiter du XML, et des itérateurs avec yield.

    • [^] # Re: .

      Posté par . Évalué à 1 (+1/-0). Dernière modification le 30/09/19 à 20:29.

      Merci :)

  • # Urllib

    Posté par . Évalué à 1 (+1/-0).

    Je suis en train de regarder sans avoir vraiement le temps!

    Il n'y aurait pas un AttributeError(de tête je ne sais plus) à attraper sur result.location.string (Xspf.parse)?
    Si result.location.string n'existe pas, je ne sais pas ce que ca donne.

    Je vais m'y pencher ce soir, j'ai bien envie de faire la classe m3u on attaquer les formats exotique.
    Et si je rajoute /dev/random dans ma playlist…

    • [^] # Re: Urllib

      Posté par . Évalué à 3 (+1/-0). Dernière modification le 01/10/19 à 17:14.

      Tu vas effectivement certainement te payer une exception sur result.location.string si location n’existe pas. D’autant que ça a l’air autorisé par la spécification :

      xspf:track elements MAY contain zero or more location elements

      http://xspf.org/xspf-v1.html#rfc.section.4.1.1.2.14.1.1.1.1

      Donc il faut gérer le cas. Quoi faire ? Afficher un avertissement ? Que faire s’il y en a plusieurs ?

      Bon courage pour M3U, je crois que c’est beaucoup moins bien spécifié que XSPF et de mémoire il y a de légères incompatibilités entre lecteurs. Il y a peut-être des bibliothèques pour ça. Pour XSPF aussi d’ailleurs.

      • [^] # Re: Urllib

        Posté par . Évalué à 0 (+0/-0).

        Oui un avertissement, mais il va être noyé dans la masse de l'affichage. Donc un dictionnaire pour afficher les résultats à la fin. Les morceaux copiés, les erreurs, tout.

        Je vois bien le problème pour le m3u.
        Il y à des lecteurs qui apparemment ne se base pas sur le chemin absolu, mais en relatif sur le path qu'ils connaissent, ou ont par default.

        Une option --force-mp3-directory ça pourrait faire l'affaire. S'ils sont en dehors, ils vont utiliser des chemins absolus.

        Je réfléchie, et je m'éclate avec ce truc!

  • # Merci olivier_h

    Posté par . Évalué à 2 (+2/-0). Dernière modification le 01/10/19 à 15:33.

    Ce petit bout de code, c'était plus un exercice de style qu'autre chose.
    Une occasion de s’y remettre un petit peu. Et d’essayer de faire les choses le plus proprement possible.
    J'aurais pu me contenter du bout de code en php-cli mais j'ai eu une envie de python.

    C'est aussi ca coder, tu as juste un besoin perso, mais tu va prendre ton pied à essayer de faire en sorte que ca puisse resservir. Et je me suis régalé, bien loin de ma vie quotidienne. J'en avais presque oublié ce plaisir, coder pour soi(et pour les autres, s'il y à un besoin).

    Et ça, c'est grâce aux superbes dépêches dont olivier_h et les contributeurs nous régalent depuis la rentrée. Et il en a beaucoup en rédaction apparemment.

    Merci à vous!
    (Et éventuellement si vous vous ennuyer malgrès tout ça, l'air de rien, "Gérer son code Python chez gitlab/github")

    PS: Un troll est caché dans les parenteses, sauras-tu le dessiner en suivant les pointilles?

  • # remarques en passant

    Posté par (page perso) . Évalué à 7 (+5/-0).

    • Il n'est plus nécessaire d'hériter d'object en Python 3 ;)
    • le init(self) vide est également inutile
    • Ça manque de passage par black pour homogénéiser le code
    • pf1 = open(self.args.playlist) ; pls = pf1.read(); … pf1.close() pourrait être avantageusement remplacé par with open(self.args.playlist) as pf1; pls = pf1.read()
    • [^] # Re: remarques en passant

      Posté par . Évalué à 0 (+0/-0).

      L'exception il devrait y en avoir une oui! Pas eu le temps hier.

      Alors là, j'apprends des choses:
      -Plus besoin d'hériter d'object en Python 3!
      -C'est noté pour with open()!

      Par contre là je comprends pas: "Ça manque de passage par black pour homogénéiser le code" ?

      • [^] # Re: remarques en passant

        Posté par . Évalué à 2 (+0/-0). Dernière modification le 02/10/19 à 17:38.

        Black est un formateur de code Python: https://github.com/psf/black

        Tu semble preneur de retours, alors parmi les éléments de style que tu peux regarder :

        • les grands espaces entre les lignes : je te suggérerais d'en mettre un peu moins. Je ne sais plus ce qui est recommandé, moi j'ai tendance à espacer les méthodes entre elles par une ligne vide, et les classes entre elles par 2 lignes vides. Plus de deux lignes vides ne me parait en général pas utile. C'est une question de préférence, on peut ne pas être d'accord avec ça.
        • style et langue des commentaires : je te suggérerais d'utiliser la même langue dans tous les commentaires. Quelques petites erreurs ("Openning" → "Opening")
          • les commentaires ne devraient pas trop décrire ce que le code fait, mais pourquoi il le fait. Je mettrais une espace après le # mais tout le monde ne le fait pas.
        • le bloc except IOError: se finit par une instruction "pass", que tu peux simplement enlever.

        Et là, ce qui suit est plus une opinion et un ensemble de suggestions plutôt que des règles.

        Tu fais une classe instanciée qu'une fois : Pl2mp3. Ça peut être utile d'avoir une classe réutilisable, mais dans ce cas je ne ferais pas le traitement des arguments de main dedans. Si la classe n'est pas destinée à être réutilisée, j'aurais tendance à ne pas du tout faire de classe pour simplifier le code : une fonction main remplacerait avantageusement la méthode start de la classe Pl2mp3.

        Ensuite,

            main = Pl2mp3()
            main.start()

        devient :

            main()

        Tu peux garder la méthode argparse et en faire une fonction argparse, et tu peux te débarrasser du membre self.args : la fonction produit le résultat de l'analyse des arguments et retourne ces résultats au lieu de les affecter à un membre. Ensuite, self.argparse() devient args = argparse() et args devient une variable locale de ta fonction main (ou de ta méthode start). Paf, un membre de moins, moins d'état "global" (attaché à la classe), le code devient plus facile à lire parce que les choses sont moins dispersées. Pas besoin de garder les arguments dans l'état de ta classe si tu ne t'en sers pas ailleurs.

        Amuse-toi bien :-)

        • [^] # Re: remarques en passant

          Posté par . Évalué à 1 (+1/-0).

          Beaucoup de bon conseils et de bonnes idées!
          Et tout cas, merci d'avoir pris le temps d'écrire.

          Oui, je fais ça somme un salop. On m'à conseillé d'utiliser black pour formater le code, superbe découverte!

          Bon mes commentaires ils son pour moi, histoire de savoir ou j'en suis. Et ça ne sert pas aux autres. A corriger
          Par contre, là, except IOError, le pass est juste énorme, merci!!!

          Après, au niveau des classes à réutiliser, je ne vais pas m'interesser spécialement à Pl2mp3, plutot à celle qui gère un type de playlist(il devrait il y avoir 4).

          Hier j'ai rajouté les stats, nombres de fichier copié, warnings, errors.

          Là je suis en pleine méditation devant m3u. Et ses chemins relatifs en fonction de l'emplacement de la playlist

          Think twice, code once

          Et vraiement merci, de bonnes critiques constructive.

          • [^] # Re: remarques en passant

            Posté par . Évalué à 3 (+1/-0). Dernière modification le 03/10/19 à 12:07.

            Beaucoup de codes commencent par de l'expérimentation, et son améliorés par la suite. Je pense que la démarche est bonne et en tout cas c'est totalement comme ça que je fonctionne :-)

            Le code que tu présentes dans ton journal n'est pas parfait, mais il a deux qualités importantes :

            • il fait le boulot
            • on comprend rapidement comment il fonctionne

            Ton objectif est de garder ces deux points valides alors que le code grossit et se complexifie.

            Les codes parfaits, c'est rare. Un code pas idéal qui rend service, c'est mieux qu'un code qui n'existe pas. Et puis, on est là pour s'amuser !

            En tout cas bravo pour faire l'effort de présenter ta démarche et ton code, c'est pas forcément facile de s'exposer au public comme ça. Le journal et les commentaires ici peuvent être bénéfiques à qui tombera dessus, ça peut susciter des inspirations, donc ne n'est pas forcément utile que pour toi. Alors, merci !

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.