DédéAmp arrive ! – part 4

Maintenant que nous avons un joli (?) formulaire, il reste à voir le coeur du programme : la playlist, les événements (et leur gestion) ainsi que les fonctions ! C’est parti mon kiki !

La playlist

Tout bon lecteur de MP3 permet de lire plusieurs morceaux les uns à la suite des autres : pour cela, il faut évidemment créer une liste de lecture (ou playlist), et la remplir avec nos morceaux relus depuis un répertoire. C’est la fonction scandir de DédéAmp qui va s’en charger. Mais avant tout, nous devons créer deux playlists. Pourquoi ? Parce que la première est carrément dans le désordre : or je voulais absolument lire un album dans l’ordre de ses pistes. La solution que j’ai trouvé (après moult essais infructueux de tri) consiste à créer une playlist temporaire, puis de la trier dans une playlist « définitive » qui sera celle utilisée pour la lecture.

Commençons donc par créer nos objets playlist : il s’agit de listes, c’est-à-dire des tableaux mais un peu particuliers. Quelle est la différence ? C’est assez simple : un tableau a une taille définie lors de sa création, et lorsque vous voulez ajouter des données dedans, Powershell ne veut pas et vous renvoie une erreur. Une liste (ou ArrayList) elle n’a pas de taille fixe : on peut donc lui rajouter des données très facilement après la création.

Voilà comment ça se passe :

$playlisttmp= New-Object System.Collections.ArrayList
$playlist= New-Object System.Collections.ArrayList

$playlisttmp sera donc remplie lors du scan du répertoire, et $playlist sera celle qui sera rejouée par le player.

Tant que nous sommes à créer des objets, profitons-en pour créer notre objet $MediaPlayer, sur lequel nous appellerons les méthodes qu’il fournir pour jouer, arrêter ou mettre en pause la musique  :

$MediaPlayer = New-Object System.Windows.Media.MediaPlayer

Etudions maintenant la fonction scandir, qui ne prend pas de paramètre : elle va récupérer la valeur (la propriété Text) de $textbox0, qui contient le répertoire à scanner :

function scandir
{
    $textbox7.Text="Scan du répertoire..."
    $textbox7.Refresh()
    $MusicFiles = Get-ChildItem -path $textbox0.Text -include *.mp3 -recurse
    $textbox7.Text=$statusarray[$global:status] 
    $textbox7.Refresh()
    $duration = $null
    if($MusicFiles.Count -eq 0)
    {
        [System.Windows.Forms.MessageBox]::Show("Aucun fichier MP3 n'a été trouvé dans ce répertoire ! :(")
    }
    else
    {
        $global:nbfiles=$MusicFiles.Count-1
        $playlisttmp.Clear()
        $playlist.Clear()
        $global:i=0
        $global:status=0 # 0: idle, 1: playing, 2: paused
        foreach($file in $MusicFiles)
        {
            $media= [TagLib.File]::Create($file)
            $item=New-Object System.Object
            $item | Add-member -MemberType NoteProperty -Name "basename" -value $file.basename
            $item | Add-member -MemberType NoteProperty -Name "extension" -value $file.name.split('.')[1]
            $item | Add-member -MemberType NoteProperty -Name "title" -value $media.tag.title
            $item | Add-member -MemberType NoteProperty -Name "album" -value $media.tag.album
            $item | Add-member -MemberType NoteProperty -Name "performers" -value $media.tag.performers
            $item | Add-member -MemberType NoteProperty -Name "year" -value $media.tag.year
            $item | Add-member -MemberType NoteProperty -Name "genres" -value $media.tag.genres
            $item | Add-member -MemberType NoteProperty -Name "track" -value $media.tag.track
            $item | Add-member -MemberType NoteProperty -Name "trackcount" -value $media.tag.trackcount
            $item | Add-member -MemberType NoteProperty -Name "bitrate" -value $media.properties.audiobitrate
            $item | Add-member -MemberType NoteProperty -Name "fullname" -value $file.FullName
            $item | Add-member -MemberType NoteProperty -Name "pictures" -value $media.tag.pictures.data
            $playlisttmp.Add($item) | Out-Null
        }
        $global:i=0
        # Les quatre lignes ci-dessous ont failli me rendre dingue ^^
        foreach($obj in ($playlisttmp | Sort-Object -property album,track))
        {
            $playlist.Add($obj) | Out-Null
        }
    }
}

Ouhlà, ça a l’air compliqué… Mais ça ne l’est pas vraiment, vous allez voir.

Tout d’abord, $textbox7 est la textbox contenant l’état du player (s’il est en lecture, en pause, en attente, ou en train de scanner le répertoire). On va donc la mettre à jour avec un message de circonstance.

Ensuite, nous créons un tableau $MusicFiles qui appelle la cmdlet Get-ChildItem : celle-ci se charge de parcourir le répertoire indiqué dans la propriété Text de $textbox0 à la recherche de fichiers MP3. Notez la présence du paramètre -recurse qui comme son nom l’indique, va parcourir le répertoire indiqué mais aussi ses sous-répertoires.  Et là je dis attention.

Si vous sélectionnez un répertoire contenant des milliers de MP3, ça risque de faire mal :

  • au niveau du temps : ben ce n’est pas instantané de parcourir des répertoires sur un disque. Alors sur un partage réseau, je ne vous dis pas…
  • au niveau de la RAM : ce n’est pas tant le fait de lister x milliers de fichiers le souci (même si ça va forcément prendre un peu de RAM), mais c’est surtout qu’ensuite nous allons créer pour chaque fichier un objet custom qui contiendra tout un tas d’informations (de propriétés), comme le titre du morceau, l’artiste et… la pochette de l’album si elle est incluse dans les tag ID3. Et comme il s’agit d’images… Un petit exemple ? Lors du développement, je tweetais mon avancement et en scannant TOUT mon NAS (qui fait plusieurs téraoctets…), DédéAmp a fini par manger énormément de RAM. Du coup mon PC s’est mis à ramer comme ce n’est pas permis (pour un Core i7, j’entends), mais, n’en déplaise aux mauvaises langues, Windows n’a absolument pas planté. Mais pendant un quart d’heure, mon PC était vraiment à l’agonie.

Ayayayayayaye !

 

Donc soyez raisonnables, ne scannez pas la racine de votre NAS. Ne faites pas les mêmes idioties que moi.

Une autre solution aurait consisté à ne relire les tags ID3 qu’au moment de la lecture du morceau (c’est ce que faisait DédéAmp à l’origine), après tout c’est très rapide, mais bon… On verra lors d’une hypothétique version 2.0.

Revenons-en à notre code : si Get-ChildItem ne trouve aucun MP3, il faut afficher un petit message. C’est ce que fait ce bout de code :

    if($MusicFiles.Count -eq 0)
    {
        [System.Windows.Forms.MessageBox]::Show("Aucun fichier MP3 n'a été trouvé dans ce répertoire ! :(","DédéAmp v$version")
    }

Ce qui a pour résultat d’afficher cette boîte de dialogue (tout simple, il n’y a qu’un bouton OK) :

Snif… 🙁

Je n’utilise ici que deux paramètres pour la méthode Show de l’objet Windows.Forms.MessageBox : en premier paramètre le message, en second le titre de la boîte de dialogue. Reste le troisième, qui permet d’indiquer les boutons que vous voulez afficher, en choisissant parmi les valeurs suivantes :

0 OK
1 OK Cancel
2 Abort Retry Ignore
3 Yes No Cancel
4 Yes No
5 Retry Cancel

 

Ainsi, pour spécifier ce troisième paramètre, si vous voulez afficher des boutons « Yes » et « No » (la valeur est 4), il suffit de modifier la ligne comme ceci :

[System.Windows.Forms.MessageBox]::Show("Aucun fichier MP3 n'a été trouvé dans ce répertoire ! :(","DédéAmp v$version",4)

La valeur par défaut étant 0, je n’ai pas pris la peine de préciser ce troisième paramètre dans DédéAmp.

Dans la suite du programme, si Get-ChildItem indique avoir trouvé des MP3, je commence par initialiser quelques variables :

        $global:nbfiles=$MusicFiles.Count-1
        $playlisttmp.Clear()
        $playlist.Clear()
        $global:i=0
        $global:status=0 # 0: idle, 1: playing, 2: paused
  • $global:nbfiles : contient le nombre de fichiers MP3 ($MusicFiles.Count) moins un, car le premier objet de ma playlist aura l’index 0 (et non 1).
  • $global:i : il s’agit du numéro d’index du morceau dans ma playlist. Et comme je viens de le dire, je commence à 0. N’oubliez pas qu’à ce stade, la playlist est parfaitement vide. Cette même variable servira dans d’autres fonctions : dans la fonction Play, elle permettra d’indiquer quel morceau de la playlist je dois jouer. Dans les fonctions Next et Prev, j’aurai simplement et respectivement à incrémenter ou décrémenter $global.i pour choisir le morceau à jouer.
  • $global.status : contient l’état du player. 0 s’il est en attente, 1 s’il est en lecture, et 2 s’il est en pause
  • Je vide également mes deux playlists (temporaire et « définitive ») à l’aide de la méthode Clear(). Pourquoi ? Et bien parce que on peut très bien scanner un premier répertoire, jouer quelques morceaux, et vouloir écouter autre chose, et donc rescanner un autre répertoire à la recherche de MP3… Si je ne vide pas mes playlists, elles conserveront les morceaux précédents, ce que je ne veux pas !

Vient enfin la boucle qui va servir à créer mes objets customs, et à peupler ma playlist temporaire :

        foreach($file in $MusicFiles)
        {
            $media= [TagLib.File]::Create($file)
            $item=New-Object System.Object
            $item | Add-member -MemberType NoteProperty -Name "basename" -value $file.basename
            $item | Add-member -MemberType NoteProperty -Name "extension" -value $file.name.split('.')[1]
            $item | Add-member -MemberType NoteProperty -Name "title" -value $media.tag.title
            $item | Add-member -MemberType NoteProperty -Name "album" -value $media.tag.album
            $item | Add-member -MemberType NoteProperty -Name "performers" -value $media.tag.performers
            $item | Add-member -MemberType NoteProperty -Name "year" -value $media.tag.year
            $item | Add-member -MemberType NoteProperty -Name "genres" -value $media.tag.genres
            $item | Add-member -MemberType NoteProperty -Name "track" -value $media.tag.track
            $item | Add-member -MemberType NoteProperty -Name "trackcount" -value $media.tag.trackcount
            $item | Add-member -MemberType NoteProperty -Name "bitrate" -value $media.properties.audiobitrate
            $item | Add-member -MemberType NoteProperty -Name "fullname" -value $file.FullName
            $item | Add-member -MemberType NoteProperty -Name "pictures" -value $media.tag.pictures.data
            $playlisttmp.Add($item) | Out-Null
        }

Pour chaque fichier $file présent dans mon tableau $MusicFiles (peuplé par Get-ChildItem), je vais :

  • Appeler la méthode Create de taglib-sharp.dll : en réalité, il ne s’agit pas de créer un fichier, mais plutôt de lire ses tags ID3. J’aurai ainsi accès à ces tags via les propriétés de l’objet $media.
  • Créer un objet custom (parfaitement indéfini à sa création puis qu’il s’agit d’un System.object : il n’a à ce moment là aucune propriété, c’est juste un objet !), et lui créer des propriétés (ah !) en reprenant les infos dont j’ai besoin (comme les tags ID3 correspondant aux propriétés de l’objet $media, mais aussi certaines propriétés de l’objet $file, comme son nom, ça va servir…).
  • Ajouter cet objet à ma playlist temporaire $playlisttmp.

Ainsi, pour chaque objet $item, je vais appeler la cmdlet Add-Member pour lui ajouter un membre de type propriété (NoteProperty), qui aura tel nom (-name) et telle valeur (-value). Les noms sont assez simples pour comprendre de quoi il s’agit, mais si vous avez des questions, les commentaires en bas de l’article sont faits pour ça. Il existe plein d’autres tags que ceux que je récupère actuellement, mais vous pouvez vous référer à la doc (hélas incomplète) de taglib (pas sharp mais globalement c’est pareil) disponible sur ce site.

Reste à trier ma playlist temporaire afin d’en faire une playlist définitive… Et bien croyez-moi, cela n’a pas été aussi simple que les quelques lignes suivantes peuvent le laisser paraître… J’y ai gagné une bonne migraine le jour où je me suis penché sur le sujet.

 foreach($obj in ($playlisttmp | Sort-Object -property album,track))
        {
            $playlist.Add($obj) | Out-Null
        }

En gros, j’ajoute chaque objet $obj de ma playlist temporaire $playlisttmp que je trie par album puis par numéro de piste à ma playlist définitive $playlist. Pfiou.

Voilà, nous avons une playlist, c’est bien mais pour l’instant c’est tout. Toujours pas de musique qui sort par les haut-parleurs. Pas d’inquiétude, nous verrons ça dans la prochaine partie de notre passionnante série ! D’ici là, portez-vous bien et tchüss !

Montre ton amour pour Dédé en partageant cet article !

Laisser un commentaire