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 classespread
- 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 !
# J'ai cliqué trop vite sur le bouton d'envoi
Posté par raphj . Évalué à 3.
Quelques corrections :
JSON.stringify
n'est pas présentée comme un code).[^] # Re: J'ai cliqué trop vite sur le bouton d'envoi
Posté par gUI (Mastodon) . Évalué à 2.
Corrigé, dis-moi si ça va.
En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.
[^] # Re: J'ai cliqué trop vite sur le bouton d'envoi
Posté par raphj . Évalué à 1.
Super, merci :-)
[^] # Re: J'ai cliqué trop vite sur le bouton d'envoi
Posté par WrathOfThePixel . Évalué à 1.
"Étant donné une liste → Étant donnée une liste"
En fait c'est facultatif. Il peut s'accorder ou rester invariable au bon vouloir du rédacteur.
# Et sinon, directement récupérer le json fourni par le service ? ^^
Posté par -=[ silmaril ]=- (site web personnel) . Évalué à 3. Dernière modification le 27 mai 2019 à 14:10.
Sinon et beaucoup plus simple:
- https://music.apple.com/us/playlist/playlist-by-me/pl.ae7cd2db31fc4d519725b6747b9074a5
fait une requete vers
- https://amp-api.music.apple.com/v1/catalog/US/playlists?ids=pl.ae7cd2db31fc4d519725b6747b9074a5
Qui est une API qui renvoi du JSON :)
Bon il faut extraire 2/3 truc de la première page pour obtenir l'accès à la seconde (header Authorisation) mais ça doit pas être bien compliqué
[^] # Re: Et sinon, directement récupérer le json fourni par le service ? ^^
Posté par -=[ silmaril ]=- (site web personnel) . Évalué à 2.
ou alors récupérer le json embedded dans le tag script de type "application/ld+json".
[^] # Re: Et sinon, directement récupérer le json fourni par le service ? ^^
Posté par raphj . Évalué à 2. Dernière modification le 27 mai 2019 à 15:41.
Effectivement ! Je n'avais pas pensé à regarder les requêtes et je ne m'attendais simplement pas à avoir du JSON intégré à la page. J'ai simplement travaillé sur le rendu HTML final de la page.
Le code pour récupérer la liste devient alors :
(en ajoutant un tiret si on veut entre l'artiste et le nom)
Le JSON intégré à la page ne semble pas suffisant.
Merci à vous deux !
[^] # Re: Et sinon, directement récupérer le json fourni par le service ? ^^
Posté par anaseto . Évalué à 3.
Ils sont un, à moins que tu parles des Silmarils, auquel cas ils sont trois :-)
[^] # Re: Et sinon, directement récupérer le json fourni par le service ? ^^
Posté par raphj . Évalué à 3.
Ah ah ! C'est une vulnérabilité de type off-by-one dans mon processus de lecture des commentaires. Je vais corriger ça.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.