Commit 246a4e23 authored by Antoine Cellerier's avatar Antoine Cellerier

New audioscrobbler/last.fm plugin by Rafaël Carré and Kenneth Ostby.

parent a972b8b7
......@@ -18,6 +18,9 @@ Interfaces:
- right/ctrl-click menu in video outputs
- support for Apple's remote (courtesy Martin Kahr)
Other plugins:
* Audioscrobbler / last.fm plugin
Nix Port:
* Notification plugin.
......
......@@ -89,6 +89,7 @@ John Paul Lorenti <jpl31 at columbia.edu> - ALSA device selection patch
Jonas Larsen <jonas at vrt.dk> - Danish translation
Julien Blache <jb at technologeek.org> - disc ejection code
kty0ne - WinAmp 5 skin for VLC
Kenneth Ostby <kenneo -at- idi -dot- ntnu -dot- no> - Audioscrobbler plugin
Kevin H. Patterson <kevpatt at khptech dot com> - Theora framesize calculation patch by Kevin H. Patterson (kevpatt at khptech dot com
K. Staring <qdk at quickdekay dot net> - RTSP rewind and fast-forward support
Laurent Jonqueres <laurent_jonqueres at yahoo.fr> - Occitan localization
......@@ -124,6 +125,7 @@ Pavlov Konstantin “thresh” - several Linux build system fixes
Petr Vacek - FTP cleartext authentication
Philippe Van Hecke <philippe at belnet dot be> - SAP header hash patch
Pierre Marc Dumuid <pierre.dumuid at adelaide dot edu dot au> - Playlist patches
Rafaël Carré <rafael -dot- carre -at- wanadoo -dot- fr> - Audioscrobbler plugin
Régis Duchesne <regis at via.ecp.fr> - original VLC code
Remco Poortinga <poortinga at telin.nl> - IPv6 multicast patch
Rene Gollent <rgollent at u.arizona.edu> - BeOS interface fix
......
......@@ -1549,8 +1549,15 @@ AS_IF([test "${enable_notify}" != "no"], [
])
])
dnl
dnl Audioscrobbler plugin
dnl
AC_ARG_ENABLE(audioscrobbler,
[ --enable-audioscrobbler Last.fm submission plugin (default enabled)])
AS_IF([test "${enable_audioscrobbler}" != "no"], [
VLC_ADD_PLUGINS([audioscrobbler])
])
dnl
dnl Input plugins
......
......@@ -11,3 +11,4 @@ SOURCES_vod_rtsp = rtsp.c
SOURCES_gnutls = gnutls.c
SOURCES_svg = svg.c
SOURCES_profile_parser = profile_parser.c
SOURCES_audioscrobbler = audioscrobbler.c
/*****************************************************************************
* audioscrobbler.c : audioscrobbler submission plugin
*****************************************************************************
* Copyright (C) 2006 the VideoLAN team
* $Id$
*
* Authors: Rafal Carr <rafael -dot- carre -at- wanadoo -dot- fr>
* Kenneth Ostby <kenneo -at- idi -dot- ntnu -dot- no>
*
* 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#define _GNU_SOURCE
#include <string.h>
#if !defined( strlwr ) && !defined( WIN32 )
#include <ctype.h>
#endif
#if defined( WIN32 )
#include <time.h>
#endif
/*
* TODO :
* review/replace :
* hexa()
* md5 to char[]
*/
#include <vlc/vlc.h>
#include <vlc/intf.h>
#include <vlc_meta.h>
#include <vlc_md5.h>
#include <vlc_block.h>
#include <vlc_stream.h>
#include <vlc_url.h>
#include <network.h>
/*****************************************************************************
* Local prototypes
*****************************************************************************/
/* Keeps track of metadata to be submitted, and if song has been submitted */
typedef struct audioscrobbler_song_t
{
char *psz_a; /* track artist */
char *psz_t; /* track title */
char *psz_b; /* track album */
int i_l; /* track length */
/* vlc can't retrieve musicbrainz id, so let's ignore it */
/* int i_m; */ /* musicbrainz id */
char *psz_i; /* date */
time_t time_playing; /* date (epoch) */
} audioscrobbler_song_t;
/* Queue to be submitted to server, 10 songs max */
typedef struct audioscrobbler_queue_t
{
audioscrobbler_song_t **p_queue; /* contains up to 10 songs */
int i_songs_nb; /* number of songs */
void *p_next_queue; /* if queue full, pointer to next */
} audioscrobbler_queue_t;
struct intf_sys_t
{
audioscrobbler_queue_t *p_first_queue; /* 1st queue */
vlc_mutex_t lock; /* p_sys mutex */
/* data about audioscrobbler session */
int i_interval; /* last interval recorded */
time_t time_last_interval; /* when was it recorded ? */
char *psz_submit_host; /* where to submit data ? */
int i_submit_port; /* at which port ? */
char *psz_submit_file; /* in which file ? */
char *psz_username; /* last.fm username */
vlc_bool_t b_handshaked; /* did we handshake ? */
int i_post_socket; /* socket for submission */
char *psz_response_md5; /* md5 response to use */
/* data about input elements */
input_thread_t *p_input; /* previous p_input */
audioscrobbler_song_t *p_current_song; /* song being played */
time_t time_pause; /* time when vlc paused */
time_t time_total_pauses; /* sum of time in pause */
vlc_bool_t b_queued; /* has it been queud ? */
vlc_bool_t b_metadata_read; /* did we read metadata ? */
vlc_bool_t b_paused; /* are we playing ? */
};
intf_sys_t *p_sys_global; /* for use same p_sys in all threads */
static int Open ( vlc_object_t * );
static void Close ( vlc_object_t * );
static void Run ( intf_thread_t * );
static int ItemChange ( vlc_object_t *, const char *,
vlc_value_t, vlc_value_t, void * );
static int PlayingChange( vlc_object_t *, const char *,
vlc_value_t, vlc_value_t, void * );
static int AddToQueue ( intf_thread_t *p_this );
static int Handshake ( intf_thread_t *p_sd );
static int ReadMetaData ( intf_thread_t *p_this );
void DeleteQueue( audioscrobbler_queue_t *p_queue );
char *hexa( short int i );
#if !defined(strlwr) && !defined( WIN32 )
char* strlwr(char *psz_string);
#endif
/*****************************************************************************
* Module descriptor
****************************************************************************/
#define APPLICATION_NAME "VLC media player"
#define USERNAME_TEXT N_("Username")
#define USERNAME_LONGTEXT N_("Audioscrobbler username")
#define PASSWORD_TEXT N_("Password")
#define PASSWORD_LONGTEXT N_("Audioscrobbler password")
#define DEFAULT_INTERVAL 60
#define CLIENT_NAME PACKAGE
#define CLIENT_VERSION VERSION
/* HTTP POST request : to submit data */
#define POST_REQUEST "POST /%s HTTP/1.1\n" \
"Accept-Encoding: identity\n" \
"Content-length: %d\n" \
"Connection: close\n" \
"Content-type: application/x-www-form-urlencoded\n" \
"Host: %s\n" \
"User-agent: VLC Media Player/%s\r\n" \
"\r\n" \
"%s\r\n" \
"\r\n"
/* data to submit */
#define POST_DATA "u=%s&s=%s&a%%5B%d%%5D=%s&t%%5B%d%%5D=%s" \
"&b%%5B%d%%5D=%s&m%%5B%d%%5D=&l%%5B%d%%5D=%d&i%%5B%d%%5D=%s"
vlc_module_begin();
set_category( CAT_INTERFACE );
set_subcategory( SUBCAT_INTERFACE_CONTROL );
set_shortname( N_( "Audioscrobbler" ) );
set_description( _("Audioscrobbler submission Plugin") );
add_string( "lastfm-username", "", NULL,
USERNAME_TEXT, USERNAME_LONGTEXT, VLC_TRUE );
add_string( "lastfm-password", "", NULL,
PASSWORD_TEXT, PASSWORD_LONGTEXT, VLC_TRUE );
set_capability( "interface", 0 );
set_callbacks( Open, Close );
vlc_module_end();
/*****************************************************************************
* Open: initialize and create stuff
*****************************************************************************/
static int Open( vlc_object_t *p_this )
{
intf_thread_t *p_intf;
intf_sys_t *p_sys;
playlist_t *p_playlist;
p_intf = ( intf_thread_t* ) p_this;
p_sys = malloc( sizeof( intf_sys_t ) );
if( !p_sys )
{
goto error;
}
vlc_mutex_init( p_this, &p_sys->lock );
p_sys_global = p_sys;
p_sys->psz_submit_host = NULL;
p_sys->psz_submit_file = NULL;
p_sys->b_handshaked = VLC_FALSE;
p_sys->i_interval = 0;
p_sys->time_last_interval = time( NULL );
p_sys->psz_username = NULL;
p_sys->p_input = NULL;
p_sys->b_paused = VLC_FALSE;
/* md5 response is 32 chars, + final \0 */
p_sys->psz_response_md5 = malloc( sizeof( char ) * 33 );
if( !p_sys->psz_response_md5 )
{
vlc_mutex_destroy ( &p_sys->lock );
goto error;
}
p_sys->p_first_queue = malloc( sizeof( audioscrobbler_queue_t ) );
if( !p_sys->p_first_queue )
{
vlc_mutex_destroy( &p_sys->lock );
goto error;
}
p_sys->p_current_song = malloc( sizeof( audioscrobbler_song_t ) );
if( !p_sys->p_current_song )
{
vlc_mutex_destroy( &p_sys->lock );
goto error;
}
/* queues can't contain more than 10 songs */
p_sys->p_first_queue->p_queue =
malloc( 10 * sizeof( audioscrobbler_song_t ) );
if( !p_sys->p_current_song )
{
vlc_mutex_destroy( &p_sys->lock );
goto error;
}
p_sys->p_first_queue->i_songs_nb = 0;
p_sys->p_first_queue->p_next_queue = NULL;
p_playlist = pl_Yield( p_intf );
var_AddCallback( p_playlist, "playlist-current", ItemChange, p_intf );
pl_Release( p_playlist );
p_intf->pf_run = Run;
return VLC_SUCCESS;
error:
free( p_sys->p_current_song );
free( p_sys->p_first_queue );
free( p_sys->psz_response_md5 );
free( p_sys );
return VLC_ENOMEM;
}
/*****************************************************************************
* Close: destroy interface stuff
*****************************************************************************/
static void Close( vlc_object_t *p_this )
{
intf_thread_t *p_intf;
intf_sys_t *p_sys;
audioscrobbler_queue_t *p_current_queue, *p_next_queue;
playlist_t *p_playlist;
p_intf = ( intf_thread_t* ) p_this;
p_sys = p_intf->p_sys;
p_playlist = pl_Yield( p_intf );
var_DelCallback( p_playlist, "playlist-current", ItemChange, p_intf );
pl_Release( p_playlist );
vlc_mutex_lock ( &p_sys->lock );
p_current_queue = p_sys->p_first_queue;
vlc_mutex_unlock ( &p_sys->lock );
while( ( p_current_queue->i_songs_nb == 10 ) &&
( p_current_queue->p_next_queue != NULL ) )
{
p_next_queue = p_current_queue->p_next_queue;
DeleteQueue( p_current_queue );
free( p_current_queue );
p_current_queue = p_next_queue;
}
DeleteQueue( p_current_queue );
free( p_current_queue );
vlc_mutex_lock ( &p_sys->lock );
if ( p_sys->psz_username )
{
free( p_sys->psz_username );
}
free( p_sys->p_current_song );
vlc_mutex_unlock ( &p_sys->lock );
vlc_mutex_destroy( &p_sys->lock );
free( p_sys );
}
/*****************************************************************************
* Run : Handshake with audioscrobbler, then submit songs
*****************************************************************************/
static void Run( intf_thread_t *p_this )
{
char *psz_submit_string = NULL;
int i_handshake;
int i_netprintf;
int i_song;
playlist_t *p_playlist;
uint8_t *p_buffer = NULL;
char *p_buffer_pos = NULL;
time_t played_time;
audioscrobbler_queue_t *p_first_queue;
intf_sys_t *p_sys;
p_this->p_sys = p_sys_global;
p_sys = p_this->p_sys;
while( !p_this->b_die )
{
if( p_sys->b_handshaked == VLC_FALSE )
{
if ( time( NULL ) >=
( p_sys->time_last_interval + p_sys->i_interval ) )
{
msg_Dbg( p_this, "Handshaking with last.fm ..." );
i_handshake = Handshake( p_this );
if( i_handshake == VLC_ENOMEM )
{
msg_Err( p_this, "Out of memory" );
return;
}
else if( i_handshake == VLC_ENOVAR )
/* username not set */
{
msg_Dbg( p_this, "Set an username then restart vlc" );
vlc_mutex_unlock ( &p_sys->lock );
return;
}
else if( i_handshake == VLC_SUCCESS )
{
msg_Dbg( p_this, "Handshake successfull :)" );
vlc_mutex_lock ( &p_sys->lock );
p_sys->b_handshaked = VLC_TRUE;
vlc_mutex_unlock ( &p_sys->lock );
}
else
{
vlc_mutex_lock ( &p_sys->lock );
p_sys->i_interval = DEFAULT_INTERVAL;
time( &p_sys->time_last_interval );
vlc_mutex_unlock ( &p_sys->lock );
}
}
}
else
{
if ( ( p_sys->p_first_queue->i_songs_nb > 0 ) &&
( time( NULL ) >=
( p_sys->time_last_interval + p_sys->i_interval ) ) )
{
msg_Dbg( p_this, "Going to submit some data..." );
vlc_mutex_lock ( &p_sys->lock );
psz_submit_string = malloc( 2048 * sizeof( char ) );
if (!psz_submit_string)
{
msg_Err( p_this, "Out of memory" );
vlc_mutex_unlock ( &p_sys->lock );
return;
}
for (i_song = 0; i_song < p_sys->p_first_queue->i_songs_nb ;
i_song++ )
{
snprintf( psz_submit_string, 2048, POST_DATA,
p_sys->psz_username, p_sys->psz_response_md5,
i_song, p_sys->p_first_queue->p_queue[i_song]->psz_a,
i_song, p_sys->p_first_queue->p_queue[i_song]->psz_t,
i_song, p_sys->p_first_queue->p_queue[i_song]->psz_b,
i_song,
i_song, p_sys->p_first_queue->p_queue[i_song]->i_l,
i_song, p_sys->p_first_queue->p_queue[i_song]->psz_i
);
}
p_sys->i_post_socket = net_ConnectTCP( p_this,
p_sys->psz_submit_host, p_sys->i_submit_port);
i_netprintf = net_Printf(
VLC_OBJECT(p_this), p_sys->i_post_socket, NULL,
POST_REQUEST, p_sys->psz_submit_file,
strlen( psz_submit_string), p_sys->psz_submit_file,
VERSION, psz_submit_string
);
if ( i_netprintf == -1 )
{
/* If connection fails, we assume we must handshake again */
p_sys->i_interval = DEFAULT_INTERVAL;
time( &p_sys->time_last_interval );
p_sys->b_handshaked = VLC_FALSE;
vlc_mutex_unlock ( &p_sys->lock );
return;
}
p_buffer = ( uint8_t* ) calloc( 1, 1024 );
if ( !p_buffer )
{
msg_Err( p_this, "Out of memory" );
vlc_mutex_unlock ( &p_sys->lock );
return;
}
net_Read( p_this, p_sys->i_post_socket, NULL,
p_buffer, 1024, VLC_FALSE );
net_Close( p_sys->i_post_socket );
p_buffer_pos = strstr( ( char * ) p_buffer, "INTERVAL" );
if ( p_buffer_pos )
{
p_sys->i_interval = atoi( p_buffer_pos +
strlen( "INTERVAL " ) );
time( &p_sys->time_last_interval );
}
p_buffer_pos = strstr( ( char * ) p_buffer, "FAILED" );
if ( p_buffer_pos )
{
msg_Err( p_this, p_buffer_pos );
vlc_mutex_unlock ( &p_sys->lock );
return;
}
p_buffer_pos = strstr( ( char * ) p_buffer, "BADAUTH" );
if ( p_buffer_pos )
{
msg_Err( p_this,
"Authentification failed, handshaking again" );
p_sys->b_handshaked = VLC_FALSE;
vlc_mutex_unlock ( &p_sys->lock );
return;
}
p_buffer_pos = strstr( ( char * ) p_buffer, "OK" );
if ( p_buffer_pos )
{
if ( p_sys->p_first_queue->i_songs_nb == 10 )
{
p_first_queue = p_sys->p_first_queue->p_next_queue;
DeleteQueue( p_sys->p_first_queue );
free( p_sys->p_first_queue );
p_sys->p_first_queue = p_first_queue;
}
else
{
DeleteQueue( p_sys->p_first_queue );
p_sys->p_first_queue->i_songs_nb = 0;
}
msg_Dbg( p_this, "Submission successfull!" );
}
vlc_mutex_unlock ( &p_sys->lock );
}
}
msleep( INTF_IDLE_SLEEP );
p_playlist = pl_Yield( p_this );
PL_LOCK;
if( p_playlist->request.i_status == PLAYLIST_STOPPED )
{
PL_UNLOCK;
/* if we stopped, we won't submit playing song */
vlc_mutex_lock( &p_sys->lock );
p_sys->b_queued = VLC_TRUE;
p_sys->b_metadata_read = VLC_TRUE;
vlc_mutex_unlock( &p_sys->lock );
}
pl_Release( p_playlist );
vlc_mutex_lock( &p_sys->lock );
if( p_sys->b_metadata_read == VLC_FALSE )
{
time( &played_time );
played_time -= p_sys->p_current_song->time_playing;
played_time -= p_sys->time_total_pauses;
vlc_mutex_unlock( &p_sys->lock );
/* ok now we can read meta data */
if( played_time > 10 )
{
ReadMetaData( p_this );
}
}
else
{
if( ( p_sys->b_queued == VLC_FALSE )
&& ( p_sys->b_paused == VLC_FALSE ) )
{
vlc_mutex_unlock( &p_sys->lock );
if( AddToQueue( p_this ) == VLC_ENOMEM )
{
msg_Err( p_this, "Out of memory" );
return;
}
}
else
{
vlc_mutex_unlock( &p_sys->lock );
}
}
}
}
/*****************************************************************************
* PlayingChange: Playing status change callback
*****************************************************************************/
static int PlayingChange( vlc_object_t *p_this, const char *psz_var,
vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
intf_thread_t *p_intf;
intf_sys_t *p_sys;
p_intf = ( intf_thread_t* ) p_data;
p_sys = p_intf->p_sys;
(void)p_this; (void)psz_var; (void)oldval;
vlc_mutex_lock( &p_sys->lock );
if( newval.i_int == PAUSE_S )
{
time( &p_sys->time_pause );
p_sys->b_paused = VLC_TRUE;
}
if( newval.i_int == PLAYING_S )
{
p_sys->time_total_pauses += time( NULL ) - p_sys->time_pause;
p_sys->b_paused = VLC_TRUE;
}
vlc_mutex_unlock( &p_sys->lock );
return VLC_SUCCESS;
}
/*****************************************************************************
* ItemChange: Playlist item change callback
*****************************************************************************/
static int ItemChange( vlc_object_t *p_this, const char *psz_var,
vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
playlist_t *p_playlist;
input_thread_t *p_input = NULL;
intf_thread_t *p_intf;
intf_sys_t *p_sys;
time_t epoch;
struct tm *epoch_tm;
char psz_date[20];
(void)p_this; (void)psz_var; (void)oldval; (void)newval;
p_intf = ( intf_thread_t* ) p_data;
p_sys = p_intf->p_sys;
vlc_mutex_lock ( &p_sys->lock );
if ( p_sys->p_input )
{
/* we delete the callback for the old p_input */
var_DelCallback( p_sys->p_input, "state", PlayingChange, p_intf );
}
vlc_mutex_unlock ( &p_sys->lock );
p_playlist = pl_Yield( p_intf );
PL_LOCK;
p_input = p_playlist->p_input;
if( !p_input )
{
PL_UNLOCK;
pl_Release( p_playlist );
vlc_mutex_lock( &p_sys->lock );
/* we won't read p_input */
p_sys->b_queued = VLC_TRUE;
p_sys->b_metadata_read = VLC_TRUE;
vlc_mutex_unlock( &p_sys->lock );
return VLC_SUCCESS;
}
vlc_object_yield( p_input );
PL_UNLOCK;
pl_Release( p_playlist );
var_AddCallback( p_input, "state", PlayingChange, p_intf );
vlc_mutex_lock ( &p_sys->lock );
/* reset pause counter */
p_sys->time_total_pauses = 0;
/* we save the p_input value to delete the callback later */
p_sys->p_input = p_input;
/* we'll read after to be sure it's present */
p_sys->b_metadata_read = VLC_FALSE;
p_sys->b_queued = VLC_TRUE;
time( &epoch );
epoch_tm = gmtime( &epoch );
snprintf( psz_date, 20, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d",
epoch_tm->tm_year+1900, epoch_tm->tm_mon+1, epoch_tm->tm_mday,
epoch_tm->tm_hour, epoch_tm->tm_min, epoch_tm->tm_sec );
p_sys->p_current_song->psz_i = encode_URI_component( psz_date );
p_sys->p_current_song->time_playing = epoch;
p_sys->b_paused = ( p_input->b_dead || !p_input->input.p_item->psz_name )
? VLC_TRUE : VLC_FALSE;
vlc_mutex_unlock( &p_sys->lock );
vlc_object_release( p_input );
return VLC_SUCCESS;
}
/*****************************************************************************
* AddToQueue: Add the played song to the queue to be submitted
*****************************************************************************/
static int AddToQueue ( intf_thread_t *p_this )
{
int i_songs_nb;
time_t played_time;
audioscrobbler_queue_t *p_queue = NULL, *p_next_queue = NULL;
intf_sys_t *p_sys;
p_sys = p_this->p_sys;
/* has it been queued already ? is it long enough ? */
vlc_mutex_lock( &p_sys->lock );
if( p_sys->p_current_song->i_l < 30 )
{
msg_Dbg( p_this, "Song too short (< 30s) -> not submitting" );
p_sys->b_queued = VLC_TRUE;
vlc_mutex_unlock ( &p_sys->lock );
return VLC_SUCCESS;
}
/* wait for the user to listen enough before submitting */
time ( &played_time );
played_time -= p_sys->p_current_song->time_playing;
played_time -= p_sys->time_total_pauses;
if( ( played_time < 240 )
&& ( played_time < ( p_sys->p_current_song->i_l / 2 ) ) )
{
vlc_mutex_unlock ( &p_sys->lock );
return VLC_SUCCESS;
}
msg_Dbg( p_this, "Ok. We'll put it in the queue for submission" );
p_queue = p_sys->p_first_queue;