Scrobbler em Python para Last.fm

D.I.Y.  |    06/11/2011   |   5946 hits   |   J. Ricardo Simões Rodrigues

Hoje de manhã iniciei um pequeno programa em Python para fazer o scrobbling das músicas executadas em meu home server (Ubuntu Server) via MPD (Music Player Daemon) para o serviço Last.fm.

O que é esse serviço? Veja o seguinte excerto da página da Last.fm:


Sobre a Last.fm

A Last.fm é um serviço de recomendações musicais. Para usar a Last.fm, é preciso inscrever-se e fazer o download do Scrobbler, que o ajudará a descobrir novas músicas com base nas músicas que você ouve.

O que é Scrobbling?

Um scrobble é uma pequena mensagem que o Scrobbler envia para a Last.fm para nos informar sobre a música que você está ouvindo.

O scrobbling nos ajuda a saber quais músicas você ouve com mais frequência, de quais músicas você gosta mais, quantas vezes você ouviu um artista em um período específico de tempo, quais de seus amigos têm gostos musicais parecidos… tudo isso e muito mais. Ao focarmos em uma música que você já ouve, podemos ajudá-lo a descobrir outras músicas.

O que você ganha

Através dos scrobbles podemos fazer diariamente recomendações personalizadas para cada ouvinte da Last.fm. Eles nos permitem comparar o que você ouve com os scrobbles de milhões de ouvintes ao redor do mundo, o que significa que as suas recomendações são resultado de mais de 43 bilhões de scrobbles. E esse número não para de crescer!

Existem muitas outras coisas que você pode fazer na Last.fm. Ao participar de nossa comunidade, você pode atribuir tags a faixas, participar de debates, saber o que está bombando e encontrar novas maneiras de descobrir os segredos do seu histórico de músicas.


Esclarecimentos feitos, vamos ao scrobbler.

As duas principais dependências são as bibliotecas seguintes:

python-mpd 0.3.0 Python MPD client library

pylast 0.5.11 A Python interface to Last.fm (and other API compatible social networks)

A instalação dessas bibliotecas é simples com

 sudo easy_install python-mpd 

e

sudo easy_install python-mpd 

Outra dependência é o cadastro na Last.fm e a geração da API Key.

O pequeno programa possui o seguinte código:

Daemon

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2011 Ricardo Simoes
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# By reading this code you agree not to ridicule the author =)

# IMPORTS
import os
import sys
import pprint
import time
import datetime
import pprint
import math
import daemon
import logging

#http://pypi.python.org/pypi/python-mpd/
from mpd import (MPDClient, CommandError)
#http://pypi.python.org/pypi/pylast/
import pylast


__version__="0.1"
__author__="ricardo at ricardosimoes.com"
__doc__="""mpdscrobller v%s (c) 2011 Ricardo Simoes

mpdscrobller is a program that does scrobbling of the songs played by MPD (Music Player Daemon) to last.fm service.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place, Suite 330, Boston, MA 02111-1307, USA.

Please e-mail bugs to: %s""" % (__version__, __author__)


#Consiga a Key em http://www.lastfm.com.br/api/account
API_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaa"
API_SECRET="bbbbbbbbbbbbbbbbbb"
username = "ccccccccccccccccccccccccc"
password_hash = pylast.md5("dddddddddddddddddd")

## Host e porta do servidor MPD
HOST = 'localhost'
PORT = '6600'
PASSWORD = False
##
CON_ID = {'host':HOST, 'port':PORT}
##



def mpdConnect(client, con_id):
"""
Simple wrapper to connect MPD.
"""
try:
client.connect(**con_id)
except SocketError:
return False
return True

def mpdAuth(client, secret):
"""
Authenticate
"""
try:
client.password(secret)
except CommandError:
return False
return True
##

def GetInHMS(seconds):
hours = seconds / 3600
seconds -= 3600*hours
minutes = seconds / 60
seconds -= 60*minutes
if hours == 0:
return "%02d:%02d" % (minutes, seconds)
return "%02d:%02d:%02d" % (hours, minutes, seconds)

def main():


logger = logging.getLogger('mpdscrobbler')
hdlr = logging.FileHandler('mpdscrobbler.log')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.INFO)
logger.info("Started")
## Fancy output
pp = pprint.PrettyPrinter(indent=4)

##Last.fm
network = pylast.LastFMNetwork(api_key = API_KEY, api_secret = API_SECRET, username = username, password_hash = password_hash)


## MPD object instance
client = MPDClient()
if mpdConnect(client, CON_ID):
print 'Got connected!'
logger.info("MPD online")
else:
print 'fail to connect MPD server.'
logger.error("fail to connect MPD server")
sys.exit(1)

# Auth if password is set non False
if PASSWORD:
if mpdAuth(client, PASSWORD):
print 'Pass auth!'
logger.info("Pass auth!")
else:
print 'Error trying to pass auth.'
logger.error("Error trying to pass auth")
client.disconnect()
sys.exit(2)



status = dict()
stats = dict()
currentsong = dict()
infosong = dict()

lastsong = ""

status.update(client.status())
##pp.pprint(status)
while True:
if status['state'] == 'play':
stats.update(client.stats())
currentsong['title'] = "Unknown Track"
currentsong['artist'] = "Unknown Artist"
currentsong['album'] = "Unknown Album"


currentsong.update(client.currentsong())
#pp.pprint(stats)
#pp.pprint(currentsong)
if lastsong == currentsong['id']:
pp.pprint(".")
else:
lastsong = currentsong['id']
now = datetime.datetime.utcnow()
starttimestamp=int(time.mktime(now.timetuple()))
pp.pprint(currentsong)
logger.info("Scrobbling: Artist: " + currentsong['artist'] + ", Song: " + currentsong['title'] + ", Album: " + currentsong['album'])

scr = network.scrobble(artist=currentsong['artist'], title=currentsong['title'], album=currentsong['album'], timestamp=starttimestamp)
pp.pprint(scr)
logger.info("Last.fm response: " + str(scr))



time.sleep(5)

client.disconnect()
logger.info("Stoped")
logger.removeHandler(hdlr)
hdlr.close()

sys.exit(0)

# Script starts here
if __name__ == "__main__":
procParams = "%s" % (os.getpid())
with daemon.DaemonContext():
pidfile = "/var/run/mpdscrobbler-daemon.pid"
if os.path.exists(pidfile):
sys.exit(1)
else:
open(pidfile, "w").write(procParams + "\n")
main()







Como se trata de um daemon, eu o inicio com o seguinte script init:

Init script

#!/bin/sh
### BEGIN INIT INFO
# Provides: mpdscrobbler-daemon
# Required-Start: $local_fs $remote_fs
# Required-Stop: $local_fs $remote_fs
# Should-Start: $network
# Should-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Inicia mpdscrobbler-daemon
# Description: Inicia mpdscrobbler-daemon - Faz scrobbling das músicas executadas por MPD (Music Player Daemon)

### END INIT INFO
# Author: Ricardo Simoes

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DESC="mpdscrobbler-daemon"
NAME1="mpdscrobbler-daemon"
DAEMON1="/usr/bin/python /home/ricardo/scripts/py/mpdscrobbler/mpdscrobbler.py"
DAEMON1_ARGS=""
PIDFILE1=/var/run/$NAME1.pid
PKGNAME=mpdscrobbler-daemon
SCRIPTNAME=/etc/init.d/$PKGNAME


# Read configuration variable file if it is present
[ -r /etc/default/$PKGNAME ] && . /etc/default/$PKGNAME

# Load the VERBOSE setting and other rcS variables
[ -f /etc/default/rcS ] && . /etc/default/rcS

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions


#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
log_daemon_msg "Starting " "$NAME1"

start-stop-daemon --start --background --quiet --pidfile $PIDFILE1 --exec $DAEMON1> /dev/null
RETVAL1="$?"

[ "$RETVAL1" = 0 ] && log_daemon_msg " " "OK"
[ "$RETVAL1" = 1 ] && log_daemon_msg " " "Already running"
[ "$RETVAL1" = 2 ] && log_daemon_msg " " "FAIL"

}

#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
log_daemon_msg "Stoping " "$NAME1"
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE1
RETVAL1="$?"

[ "$RETVAL1" = 0 ] && log_daemon_msg " " "STOPPED"
[ "$RETVAL1" = 1 ] && log_daemon_msg " " "Already stopped"
[ "$RETVAL1" = 2 ] && log_daemon_msg " " "FAIL"

rm -f $PIDFILE1

}

case "$1" in
start)
do_start
;;

stop)
do_stop

;;

restart|force-reload)
log_daemon_msg "Restarting " "$NAME1"
do_stop
do_start
;;

*)
echo "Usage: $SCRIPTNAME {start|stop|restart}" >&2
exit 3
;;
esac

:


Note que o script do daemon python está no seguinte caminho (no caso de minha máquina):

/home/ricardo/scripts/py/mpdscrobbler/mpdscrobbler.py  

Basta salvar o script init no seguinte caminho

/etc/init.d/mpdscrobbler-daemon

dar permissões de execução e iniciar o daemon.

sudo /etc/init.d/mpdscrobbler-daemon start

Ambos arquivos estão disponíveis para dowload em mpdscrobbler-daemon e mpdscrobbler.py.

Última edição concluída em 06/11/2011 por J. Ricardo Simões Rodrigues.