Journal Récupérer une liste de lecture Apple

Posté par  . Licence CC By‑SA.
24
26
mai
2019

Je suis récemment allé à une soirée où le choix musical m'a plu. La personne qui mettait sa musique a découvert qu'elle pouvait me partager sa liste en m'envoyant un lien qui pointe vers https://music.apple.com/fr/playlist/[quelque chose].

Je n'ai pas iTunes. Mon but : récupérer cette liste et en faire un truc que je peux utiliser. Ce journal est aussi un prétexte pour jouer avec jq, un outil pour faire des requêtes sur des données formatées en JSON.

Prenons une liste trouvée au hasard sur internet et travaillons dessus en mode bidouille rapide et sale : https://music.apple.com/us/playlist/playlist-by-me/pl.ae7cd2db31fc4d519725b6747b9074a5

Nous avons besoin de :

  • Firefox et son ardoise Javascript
  • un éditeur de texte
  • un shell
  • jq
  • grep
  • curl

Allons visiter la liste avec Firefox. On est accueilli par un bandeau « Vous devez activer les DRM pour certaines des pistes audio ou vidéo de cette page ». On va pouvoir ignorer ce message, cliquer sur sa croix et continuer notre affaire.

La liste est affichée avec des éléments HTML simples. Avec les outils de développement, on repère vite que :

  • chaque morceau est dans un élément avec la classe tracklist-item__text
  • dans les enfants de cet élément, on trouve le nom du morceau dans l'attribut aria-label d'un élément de classe spread
  • dans les enfants de cet élément, on trouve les artistes dans un attribut data-test-song-artist-url

Sortons l'ardoise Javascript (Maj+F4). Dans cette ardoise, on peut lancer des codes en les sélectionnant et en faisant Ctrl+L.

Pour récupérer la liste des nœuds correspondant aux morceaux :

document.querySelectorAll(".tracklist-item__text")

Ou sa version supposément plus efficace :

document.getElementsByClassName("tracklist-item__text")

On va appliquer une fonction à chacun de ces éléments, pour récupérer le nom et l'artiste de chaque morceau.
Pour cela on va utiliser la fonction map, qui appelle la fonction qu'on lui donne sur chaque élément et qui retourne un tableau contenant tous ces résultats. map est une méthode des tableaux, il faut donc convertir le résultat des expressions précédentes en tableau.

À l'ancienne :

[].slice.call(document.getElementsByClassName("tracklist-item__text"))

Avec la syntaxe de décomposition :

[... document.getElementsByClassName("tracklist-item__text")]

Sur ce tableau, on peut appeler map sur une fonction qui prend l'élément HTML correspondant au morceau, et qui le transforme en un objet contenant son titre et son artiste. On rajoute JSON.stringify pour récupérer le JSON :

JSON.stringify([... document.getElementsByClassName("tracklist-item__text")]).map(
  track => ({
      title: track.querySelector(".spread").getAttribute('aria-label'),
      artist: track.querySelector("[data-test-song-artist-url]").dataset.testSongArtistUrl
  })
));

Avec Ctrl+L, on se retrouve avec un tableau du genre [{'title': 'blah blah', 'artist': 'qqun'}, …].

On enregistre ça dans un fichier liste.json. Et là, on peut faire certaines choses :

Concaténer artiste et titre en vue de faire une recherche :

jq -r 'map(.artist + " " + .title)' liste.json

L'opérateur map applique une transformation à chaque élément. L'opération est ici une concaténation de l'artiste et du nom du morceau. On obtient le résultat au format JSON.

Même chose, mais avec un résultat par ligne (sans JSON) :

jq -r 'map(.artist + " " + .title) | .[]' liste.json

Obtenir les adresses de recherche correspondantes sur YouTube :

jq -r 'map(.artist + " " + .title) | @uri "https://www.youtube.com/results?search_query=\(.[])"' liste.json

Dans une fonction qui lit le fichier json dans son entrée :

youtube_searches() {
    jq -r 'map(.artist + " " + .title) | @uri "https://www.youtube.com/results?search_query=\(.[])"'
}

Étant donnée une liste d'adresse de recherche YouTube, récupérer l'adresse du premier résultat à coup de curl $URL | grep -o '/watch?…'. Attention, la fonction suivante n'est pas très polie, elle fait une requête vers YouTube par morceau. Vous pouvez vous faire kicker par YouTube. Aussi, j'ai rajouté un petit sleep 5 pour limiter les dégâts :

youtube_first_results() {
    while read line; do
        printf "https://www.youtube.com%s\n" "$(curl -s "$line" | grep -o '/watch?v=[^"]\+' | head -1)"
        sleep 5
    done
}

Le script complet :

#!/usr/bin/env sh

youtube_searches() {
    jq -r 'map(.artist + " " + .title) | @uri "https://www.youtube.com/results?search_query=\(.[])"'
}

youtube_first_results() {
    while read line; do
        printf "https://www.youtube.com%s\n" "$(curl -s "$line" | grep -o '/watch?v=[^"]\+' | head -1)"
        sleep 5
    done
}

<"liste.json" youtube_searches | youtube_first_results >> liste-youtube.txt

Si vous souhaitez télécharger les morceaux plutôt qu'avoir leur liste, des outils font ça très bien et la fonction youtube_first_results est redondante avec des fonctionalités de téléchargement à partir d'une chaîne de recherche qui existent déjà.

Bon dimanche, et bon vote pour celles et ceux qui voteront cet après midi !

Suivre le flux des commentaires

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