lastfm scrobbler for icecast

Soumettre à Last.fm les chansons d’un flux Icecast

Voici le script python que j’ai écrit pour soumettre automatiquement à Last.fm les chansons qui sont diffusées sur ma radio Icecast.

Au fil du temps, j’ai publié dans ces colonnes différents tutoriels pour gérer une radio avec Icecast, à l’aide de Winamp, VirtualDJ, et SAM Broadcaster dont on pouvait créer un fichier texte qui pouvait ensuite être filtré puis utilisé avec un script PHP pour soumettre les chansons à Last.fm.

Il y a quelques semaines, Stéphan m’a contacté pour m’informer que le script PHP que j’utilisais jusqu’alors pour scrobbler les titres était maintenant incompatible avec PHP8, et qu’il lui fallait installer PHP7.4 spécifiquement pour le faire tourner.

Le script n’utilisait pas les dernières API de Last.fm non plus, donc c’était le bon moment de moderniser tout cela. J’en ai d’ailleurs profité pour servir le serveur Icecast en HTTPS.

Le choix de Python

Après de multiples essais, je me suis vite rendu compte que PHP n’était pas du tout adapté au scrobbling : la plupart des librairies PHP sont maintenant obsolètes (PHP8) ou ne sont plus maintenues car Last.fm est en perte de vitesse (par rapport à ce que c’était il y a quelques années).

Python, au contraire, est très simple à utiliser et il existe une librairie spécifique dédiée à l’API Last.fm qui nous permet de soumettre les titres simplement: pylast.

Enfin, l’avantage de python est qu’il est installé et mis à jour sur toutes les distributions Linux et MacOS nativement et donc cela rend le code portable et facile à transporter.

Outre Python 3, vous avez besoin du gestionnaire de paquets pip :

apt install python3-pip

Vous avez également besoin de quelques modules supplémentaires, que l’on installe avec pip :

python3 -m pip install --upgrade pip
pip3 install pylast requests

C’est tout ce qu’il vous faut au niveau des dépendances. Plus besoin de PHP ni de ses librairies, plus besoin de stocker le nom des titres dans un fichier texte, tout va se faire en direct depuis le serveur Icecast.

Last.FM Scrobbler for Icecast

Le script Last.FM Scrobbler for Icecast vérifie le statut du server Icecast toutes les 30 secondes. Si le serveur est démarré et que la radio joue, il récupère les informations de la chanson et les soumet à Last.fm, tout en vérifiant bien que la chanson n’est pas la même que la précédente. Cela évite tout doublon, ce qui fausserait les soumissions du compte.

Si le serveur Icecast n’est pas démarré, le script vous avertit et attend 5 minutes.

Si le serveur Icecast est démarré mais que le fichier status-json.xsl ne contient pas les informations nécessaires, le script vous avertit et vous recommande de redémarrer votre webradio car la connexion avec Icecast est perdue.

On crée notre script:

nano /home/scripts/lastfm-scrobbler-icecast.py

Et on y ajoute:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------
# Script name : Last.FM Scrobbler for Icecast
# Created By  : Matt Biscay
# Created Date: 2023/01/24
# Author URI  : https://www.skyminds.net
# Version     : 1.0.0
# Copyright   : 2023 SkyMinds.Net
# ---------------------------------------------------------------------- 
"""
This script will check the status of the icecast server every 30 sec and if the server is running, it will check for the currently playing track and scrobble it to LastFM if it's different from the last scrobbled track.
If the server is not running, the script will wait for 5 minutes before retrying.
It uses the pylast library to interact with the LastFM API.

@param API_KEY: the LastFM API key
@param API_SECRET: the LastFM API secret
@param username: the LastFM username
@param password: the LastFM password
@param icecast_url: the URL of the icecast server status page
"""

import pylast
import time
import requests
import json
# --------------------------------------------------
# --------- EDIT CONFIGURATION HERE ----------------
# --------------------------------------------------
# Your LastFM API key and secret
API_KEY = "LASTFM_API_KEY"
API_SECRET = "LASTFM_API_SECRET"

# The username and password of the user you want to authenticate 
# and scrobble the track for
username = "LASTFM_USER"
password = "LASTFM_PWD"

# The URL of the Icecast server's status-json.xsl page
icecast_url = "http://icecast.example.com:8000/status-json.xsl"

# ------------------------------------------------
# ---------- END OF CONFIGURATION  ---------------
# ------------------------------------------------

# Create a new LastFM network object
network = pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET,
                               username=username, password_hash=pylast.md5(password))

# The previously scrobbled track name
previous_track = ""

print("# ------------------------------------------ #")
print("#  Script: Last.FM Scrobbler for Icecast     #")
print("#  Author: Matt Biscay                       #")
print("#  URL   : https://www.skyminds.net          #")
print("# ------------------------------------------ #")
print("")

while True:

    try:
        # Make a GET request to the status-json.xsl page
        response = requests.get(icecast_url)
    except requests.exceptions.ConnectionError as e:
        print("Error: Icecast server is down.")
        # Wait for 5 minutes before retrying
        time.sleep(5*60)
        continue

    if response.status_code != 200:
        print("Error: Icecast server is not running or there is some other error")
        # Wait for 5 minutes before retrying
        time.sleep(5*60)
        continue

    # Parse the JSON response
    data = json.loads(response.text)

    # Get the currently playing track information
    if "icestats" in data and "source" in data["icestats"] and "artist" in data["icestats"]["source"] and "title" in data["icestats"]["source"]:
        artist = data["icestats"]["source"]["artist"]
        track = data["icestats"]["source"]["title"]
        current_track = f'{artist} - {track}'
        if current_track != previous_track:
            # Scrobble the track
            try:
                timestamp = int(time.time())
                network.scrobble(artist=artist, title=track, timestamp=timestamp)
                print(f"Track scrobbled successfully: {current_track}")
                previous_track = current_track
            except pylast.WSError as e:
                print("Error:", e)
        # Wait for 30 seconds before checking for new tracks
        time.sleep(30)
    else:
        print("Error: Icecast XML does not contain the required information. Restart your webradio.")
        # Wait for 1 minutes before retrying
        time.sleep(60)
        continueCode language: PHP (php)

Editez les variables dans la partie EDIT YOUR CONFIGURATION: vous avez besoin de votre clé API last.fm ainsi que la clé API secret, de votre nom d’utilisateur et mot de passe last.fm, et enfin l’adresse complète du fichier XML d’Icecast, status-json.xsl.

Lire la suite

Python : résoudre l'erreur "ImportError: cannot import name main" photo

Python : résoudre l’erreur “ImportError: cannot import name main”

Pip écrasé par une nouvelle version

Suite à une mauvaise manipulation, j’ai malencontreusement écrasé ma version de pip installée par APT en faisant un pip install pip.

Après cela, toute commande lancée par pip donne cette erreur:

Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    from pip import main
ImportError: cannot import name mainCode language: JavaScript (javascript)

Pas glop! Si cela vous arrive un jour, voici comment retrouver la version pip initiale, installée par votre gestionnaire de paquet APT.

Retrouver la version pip initiale (apt)

On marque python2.7 comme binaire Python par défault en le sélectionnant dans cette liste (si besoin):

update-alternatives --config python

On désinstalle pip, dans APT et dans PIP :

python -m pip uninstall pip
sudo apt purge --autoremove python3-pip

On réinstalle pip avec APT:

apt install python3-pip

Et on vérifie la version de pip :

pip3 --version

qui nous donne:

pip 24.0 from /usr/lib/python3/dist-packages/pip (python 3.12)Code language: JavaScript (javascript)

Et voilà, nous venons de retrouver une version de pip utilisable sur notre système.

Note : ne pas tenter de mettre à jour pip manuellement, il vaut mieux laisser le gestionnaire de paquet gérer les mises à jour de ce paquet lorsque cela est nécessaire, au fil des mises à jour.

Subliminal : résoudre l'erreur "AttributeError: list object has no attribute lower" photo

Subliminal : résoudre l’erreur “AttributeError: list object has no attribute lower”

Dernièrement, le script python que j’ai écrit pour télécharger les sous-titres automatiquement avec Subliminal a renvoyé le message d’erreur suivant :

AttributeError: 'list' object has no attribute 'lower'Code language: JavaScript (javascript)

Il se trouve que l’attribut lower ne peut-être appliqué qu’à des variables (type string) et non pour des objets (type array).

Nous allons donc éditer le code source de subliminal pour corriger le problème.

Ajout de nouvelles directives à subtitle.py

1. On se connecte au Synology en SSH:

ssh admin@SYNOLOGYCode language: CSS (css)

2. On passe root:

sudo -i

3. On recherche le fichier subtitle.py :

find / -type f -name "subtitle.py"Code language: JavaScript (javascript)

Résultat, 3 fichiers trouvés sur le NAS:

/usr/lib/python2.7/site-packages/subliminal/subtitle.py
/volume1/@appstore/VideoStation/subtitle_plugins/syno_subscene/subtitle.py
/volume1/@appstore/subliminal/env/lib/python2.7/site-packages/subliminal/subtitle.py

4. Le fichier qui nous intéresse se trouve sous /usr/lib donc nous l’éditons:

nano /usr/lib/python2.7/site-packages/subliminal/subtitle.py

5. Faites une recherche avec le terme lower (avec Ctrl+W sous nano). Vous trouver cette ligne:

    # format
    if video.format and 'format' in guess and guess['format'].lower() == video.format.lower():
       matches.add('format')Code language: PHP (php)

Nous allons commenter ces lignes et ajouter des conditions pour que lower ne soit appliqué qu’aux chaînes (type string):

    # format
    #if video.format and 'format' in guess and guess['format'].lower() == video.format.lower():
       #matches.add('format')
    if video.format and 'format' in guess:
       guess_format = guess['format'] if isinstance(guess['format'], list) else [guess['format']]
       if any(gf.lower() == video.format.lower() for gf in guess_format):
            matches.add('format')Code language: PHP (php)
Attention à bien respecter le nombre d’espaces pour l’indentation : il s’agit d’un script python donc très tatillon à ce sujet !

6. Sauvegardez le fichier.

7. Relancez la recherche automatique des sous-titres : plus d’erreurs relatives à l’attribut lower :)

Enjoy!

Synology : installer PIP, le gestionnaire de paquets Python photo

Synology : installer PIP, le gestionnaire de paquets Python

Si l’on souhaite rajouter pas mal de fonctionnalités à un NAS Synology, on est vite limité par les applications officielles.

On peut passer par IPKG ou alors tout simplement installer pip, le gestionnaire de paquets Python.

Voici comment installer pip sur votre NAS Synology en moins de 3 minutes.

Installation de pip sur votre Synology

Pré-requis : vous devez avoir installé Python (version 2.7 chez moi) depuis le gestionnaire de paquets du DSM.

1. On commence par ouvrir une session SSH avec l’utilisateur root sur le port 22 de l’IP de notre NAS :

ssh -l admin 192.168.IP.NAS -p22
sudo -iCode language: CSS (css)

Depuis DSM6, on ne peut plus ouvrir de session SSH avec l’utilisateur root. Il faut donc ruser en ouvrant une session avec un autre utilisateur (admin par exemple) puis passer root avec sudo -i

2. Une fois connecté en tant que root, on récupère le paquet pip :

wget https://bootstrap.pypa.io/get-pip.pyCode language: JavaScript (javascript)

3. Et on installe pip :

python get-pip.pyCode language: JavaScript (javascript)

Cela prend un peu de temps. Voici ce que vous devriez obtenir :

Collecting pip
  Downloading pip-8.1.2-py2.py3-none-any.whl (1.2MB)
    100% |████████████████████████████████| 1.2MB 103kB/s 
Collecting setuptools
  Downloading setuptools-27.1.2-py2.py3-none-any.whl (464kB)
    100% |████████████████████████████████| 471kB 99kB/s 
Collecting wheel
  Downloading wheel-0.29.0-py2.py3-none-any.whl (66kB)
    100% |████████████████████████████████| 71kB 535kB/s 
Installing collected packages: pip, setuptools, wheel
Successfully installed pip-8.1.2 setuptools-27.1.2 wheel-0.29.0

pip est maintenant installé. Il ne vous reste plus qu’à installer les paquets de votre choix, à la manière d’un apt, apt-get ou aptitude :

Usage:   
  pip  [options]

Commands:
  install                     Install packages.
  download                    Download packages.
  uninstall                   Uninstall packages.
  freeze                      Output installed packages in requirements format.
  list                        List installed packages.
  show                        Show information about installed packages.
  search                      Search PyPI for packages.
  wheel                       Build wheels from your requirements.
  hash                        Compute hashes of package archives.
  help                        Show help for commands.Code language: PHP (php)

À vous les paquets Python sur votre Synology !

Installer la dernière version de Firefox, Thunderbird ou Seamonkey sous Ubuntu de manière automatique avec UbuntuZilla photo

Installer la dernière version de Firefox, Thunderbird ou Seamonkey sous Ubuntu de manière automatique avec UbuntuZilla

ubuntuzilla

Cela fait quelques semaines que je me demande comment mettre à jour Firefox sous Ubuntu alors qu’aucune mise à jour ne semble voir le jour dans les dépôts officiels.

Et voici que je tombe sur UbuntuZilla, un script Python qui permet d’installer les dernières versions de Firefox, SeaMonkey et Thunderbird sur les plateformes Ubuntu et dérivées, et cela automatiquement !

Téléchargement d’UbuntuZilla

Commencez par télécharger la version d’UbuntuZilla correspondant à votre système. Le fichier est un paquet .deb donc directement installable par Ubuntu.

Lire la suite