J’ai construit un truc qui partage automatiquement ma musique Apple Music actuelle sur mon site web. Rien de fancy, mais ça a résolu mon problème.
Le problème que je voulais résoudre
Je passe beaucoup de temps à écouter de la musique en codant, et je trouvais que ce serait cool d’avoir mon site web qui affiche ce qui joue actuellement en temps réel. J’avais vu des fonctionnalités similaires sur d’autres sites de développeurs et je voulais quelque chose de similaire pour le mien.
Le défi était qu’Apple Music n’a pas d’API publique pour ce genre de chose. Je devais trouver un moyen d’extraire les infos de la musique actuelle depuis l’app et les faire arriver sur mon site web sans aucune intégration officielle.
Voici l’architecture globale avec laquelle j’ai fini :
Récupérer les données d’Apple Music
Le premier obstacle était de récupérer les informations de la musique. Après quelques recherches, j’ai découvert qu’AppleScript pouvait interagir avec l’app Music. Faire fonctionner l’auth était chiant, mais une fois que j’ai compris les bonnes permissions, c’était simple.
Voici l’AppleScript qui récupère la musique actuelle :
tell application "Music"
if it is running then
if player state is playing then
set pState to player state
set pPosition to player position
set cTrack to current track
set trackInfo to "{\"status\": \"playing\", \"persistent ID\": \"" & persistent ID of cTrack & "\", \"name\": \"" & name of cTrack & "\", \"time\": \"" & time of cTrack & "\", \"duration\": \"" & duration of cTrack & "\", \"artist\": \"" & artist of cTrack & "\", \"album\": \"" & album of cTrack & "\" }"
return trackInfo
else
return "{\"status\": \"not playing\"}"
end if
else
return "{\"status\": \"not running\"}"
end if
end tell
Le script retourne du JSON directement parce que gérer les structures de données AppleScript en Python était plus compliqué que ça en valait la peine. De cette façon je peux juste le parser comme du JSON côté Python.
$ osascript export.applescript
{"status": "not playing"}
$ osascript export.applescript
{"status": "playing", "persistent ID": "1A3BB81D89DE1B39", "name": "SAUDADE", "time": "2:21", "duration": "141,429000854492", "artist": "Dexa. & 6minaprès", "album artist": "Dexa.", "composer": "Gael six minutes, 6astardcanrise canr rise & Dexa Dexa Dexa", "album": "Station d'Atlas - EP", "genre": "Pop", "played count": "5", "pState": "playing", "pPosition": "1,957999944687" }
Construire le pont
Le script Python tourne en continu et appelle l’AppleScript toutes les quelques secondes. Quand il détecte une nouvelle chanson (en utilisant l’ID persistant), il récupère des métadonnées supplémentaires depuis l’API iTunes et envoie tout à mon backend Flask :
def get_current_song():
try:
output = subprocess.check_output(['osascript', 'export.applescript']).decode('utf-8').strip()
return output
except subprocess.CalledProcessError as e:
printerr(e)
return e
def main():
persistendId = ''
prevstatus = ''
while True:
currentsong = json.loads(get_current_song())
if currentsong['status'] == 'playing':
if currentsong['persistent ID'] != persistendId:
persistendId = currentsong['persistent ID']
currentsong['timestamp'] = time.time()
# Récupère l'artwork et les URLs iTunes depuis l'API iTunes
(currentsong['artwork_url'], currentsong['itunes_url'], currentsong['artist_url']) = get_track_extras(currentsong['name'], currentsong['artist'], currentsong['album'])
# Extrait la couleur dominante de l'artwork pour le thème UI
currentsong['dominantcolor'] = get_dominant_color_from_url(currentsong['artwork_url'])
printout(f"{post(currentsong)}")
Le script log tout pour que je puisse voir ce qui se passe :
14:23:45 : {"message": "Content set successfully."}
14:27:12 : {"message": "Content set successfully."}
14:30:38 : {"message": "Content set successfully."}
Un défi intéressant était le timing. Je ne voulais pas spammer l’API, alors le script calcule combien de temps il reste jusqu’à la fin de la chanson actuelle et dort pendant cette durée (plus quelques secondes de buffer).
Le backend API
Le backend Flask est super simple - juste deux endpoints et un cache en mémoire :
from flask import Flask, request, jsonify
import json
import hashlib
app = Flask(__name__)
cache = {}
@app.route('/music/set', methods=['POST'])
def set_content():
data = request.get_json()
user = data.get('user')
password = data.get('password')
if user in users and users[user] == hashlib.sha256(password.encode()).hexdigest():
cache.update(data)
cache.pop('user', None) # Ne stocke pas les credentials
cache.pop('password', None)
return jsonify({'message': f'Content set successfully.'})
else:
return jsonify({'message': 'Invalid user or password.'}), 401
@app.route('/music/get', methods=['GET'])
def display_content():
return jsonify(cache)
J’ai ajouté une auth basique parce que je ne voulais pas que des gens random mettent à jour mon statut de lecture. Le cache contient juste les dernières infos de la musique qui sont retournées à quiconque hit l’endpoint GET.
Le faire tourner comme un service
Faire en sorte que ça se lance automatiquement au boot de macOS était une autre petite galère. J’ai fini par utiliser launchd avec un fichier plist :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.music-exp</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/python3</string>
<string>/Users/noham/Documents/export.py</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/noham/Documents</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
Ce avec quoi j’ai fini
Le système marche plutôt bien. Quand je joue une chanson dans Apple Music, en quelques secondes elle apparaît sur mon site web avec l’artwork, les infos de l’artiste, et même une couleur dominante extraite de l’art de l’album pour le thème.
Voici ce que l’API retourne :
{
"album": "Station d'Atlas - EP",
"album artist": "Dexa.",
"artist": "Dexa.",
"artist_url": "https://music.apple.com/us/artist/dexa/1534658078?uo=4",
"artwork_url": "https://is1-ssl.mzstatic.com/image/thumb/Music221/v4/9f/ca/fc/9fcafcb7-a3a1-5c4d-31f0-86dbfcc016ec/artwork.jpg/200x200bb.jpg",
"composer": "Dexa Dexa Dexa & Yanis Yvvid Prod",
"dominantcolor": [132, 69, 13],
"duration": "139,285995483398",
"genre": "Pop",
"itunes_url": "https://music.apple.com/us/album/sadida/1820035593?i=1820035597&uo=4",
"name": "SADIDA",
"pPosition": "81,157997131348",
"pState": "playing",
"persistent ID": "E942F8A52AEAEA1E",
"played count": "5",
"status": "playing",
"time": "2:19",
"timestamp": 1751116587.6413
}
Le JavaScript frontend utilise le timestamp pour calculer le progrès en temps réel, donc la barre de progression bouge même si l’API n’est pas mise à jour chaque seconde.
Si tu veux l’essayer
Le code est sur GitHub si ça t’intéresse. Attention - c’est assez spécifique à mon setup et macOS. Tu devras :
- Mettre en place le backend Flask quelque part
- Configurer le script Python avec tes credentials
- Gérer les permissions macOS pour AppleScript
- Configurer le service launchd
C’est pas la solution la plus élégante, mais ça marche pour ce dont j’avais besoin. La limitation principale est que ça ne marche que quand Apple Music tourne sur mon Mac, donc si j’écoute sur mon téléphone ou ailleurs, le site web n’affiche rien.