From da153da0be485aa4b937d58ee209eda7fb342053 Mon Sep 17 00:00:00 2001 From: Linus Nielsen Feltzing Date: Thu, 19 Oct 2006 09:42:58 +0000 Subject: Patch #5166 by Robert Keevil - Last.fm logging git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11269 a1c6a512-1295-4272-9138-f99709370657 --- apps/Makefile | 3 +- apps/SOURCES | 1 + apps/lang/english.lang | 34 +++++- apps/main.c | 6 +- apps/misc.c | 2 + apps/playback.c | 10 ++ apps/scrobbler.c | 263 +++++++++++++++++++++++++++++++++++++++++++ apps/scrobbler.h | 24 ++++ apps/settings.c | 2 + apps/settings.h | 1 + apps/settings_menu.c | 23 +++- apps/tree.c | 3 + firmware/common/timefuncs.c | 35 ++++++ firmware/export/audio.h | 1 + firmware/include/time.h | 3 +- firmware/include/timefuncs.h | 1 + firmware/mpeg.c | 11 ++ 17 files changed, 414 insertions(+), 9 deletions(-) create mode 100755 apps/scrobbler.c create mode 100644 apps/scrobbler.h diff --git a/apps/Makefile b/apps/Makefile index 6214ce138b..815e061d3c 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -55,7 +55,8 @@ ifdef APPEXTRA endif CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(DEFINES) -DTARGET_ID=$(TARGET_ID) \ - -DAPPSVERSION=\"$(VERSION)\" $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE} + -DAPPSVERSION=\"$(VERSION)\" $(EXTRA_DEFINES) -DMEM=${MEMORYSIZE} \ + -DTARGET_NAME=\"$(ARCHOS)\" OBJS2 := $(OBJDIR)/lang.o $(patsubst %.c, $(OBJDIR)/%.o, $(SRC)) OBJS = $(patsubst %.S, $(OBJDIR)/%.o, $(OBJS2)) diff --git a/apps/SOURCES b/apps/SOURCES index dccb6f95ac..d759e49a6f 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -29,6 +29,7 @@ talk.c tree.c tagtree.c filetree.c +scrobbler.c screen_access.c gui/buttonbar.c diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 8a2341b204..6cd5b2d533 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -3861,13 +3861,13 @@ id: LANG_DIRCACHE_REBOOT - desc: when activating directory cache + desc: DEPRECATED user: - *: "Please reboot to enable the cache" + *: "" - *: "Please reboot to enable the cache" + *: "" *: "" @@ -9940,3 +9940,31 @@ *: "Random" + + id: LANG_AUDIOSCROBBLER + desc: "Last.fm Log" in the playback menu + user: + + *: "Last.fm Log" + + + *: "Last.fm Log" + + + *: "Last.fm Log" + + + + id: LANG_PLEASE_REBOOT + desc: when activating an option that requires a reboot + user: + + *: "Please reboot to enable" + + + *: "Please reboot to enable" + + + *: "" + + diff --git a/apps/main.c b/apps/main.c index bd1dd7b03c..838a4859d2 100644 --- a/apps/main.c +++ b/apps/main.c @@ -66,6 +66,7 @@ #include "string.h" #include "splash.h" #include "eeprom_settings.h" +#include "scrobbler.h" #if (CONFIG_CODEC == SWCODEC) #include "playback.h" @@ -252,6 +253,8 @@ void init(void) audio_preinit(); #endif + scrobbler_init(); + /* audio_init must to know the size of voice buffer so init voice first */ #if CONFIG_CODEC == SWCODEC talk_init(); @@ -455,7 +458,8 @@ void init(void) status_init(); playlist_init(); tree_init(); - + scrobbler_init(); + /* No buffer allocation (see buffer.c) may take place after the call to audio_init() since the mpeg thread takes the rest of the buffer space */ mp3_init( global_settings.volume, diff --git a/apps/misc.c b/apps/misc.c index 87fd93513e..4be9e43fd9 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -45,6 +45,7 @@ #include "font.h" #include "splash.h" #include "tagcache.h" +#include "scrobbler.h" #ifdef HAVE_MMC #include "ata_mmc.h" #endif @@ -625,6 +626,7 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame if (!mmc_touched() || (mmc_remove_request() == SYS_MMC_EXTRACTED)) #endif { + scrobbler_flush_cache(); system_flush(); usb_screen(); system_restore(); diff --git a/apps/playback.c b/apps/playback.c index 486edf80b2..495629e5ea 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -272,6 +272,9 @@ struct thread_entry *codec_thread_p; #ifdef PLAYBACK_VOICE extern struct codec_api ci_voice; +/* Play time of the previous track */ +unsigned long prev_track_elapsed; + static volatile bool voice_thread_start; static volatile bool voice_is_playing; static volatile bool voice_codec_loaded; @@ -1630,6 +1633,8 @@ static bool codec_load_next_track(void) { struct event ev; + prev_track_elapsed = CUR_TI->id3.elapsed; + if (ci.seek_time) codec_seek_complete_callback(); @@ -2912,6 +2917,11 @@ void audio_set_track_changed_event(void (*handler)(struct mp3entry *id3)) track_changed_callback = handler; } +unsigned long audio_prev_elapsed(void) +{ + return prev_track_elapsed; +} + static void audio_stop_codec_flush(void) { ci.stop_codec = true; diff --git a/apps/scrobbler.c b/apps/scrobbler.c new file mode 100755 index 0000000000..68c5e9addf --- /dev/null +++ b/apps/scrobbler.c @@ -0,0 +1,263 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Robert Keevil + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +/* +Audioscrobbler spec at: +http://www.audioscrobbler.net/wiki/Portable_Player_Logging +*/ + +#include "file.h" +#include "sprintf.h" +#include "playback.h" +#include "logf.h" +#include "id3.h" +#include "kernel.h" +#include "audio.h" +#include "buffer.h" +#include "settings.h" + +#ifndef SIMULATOR +#include "ata.h" +#endif + +#ifdef CONFIG_RTC +#include "time.h" +#include "timefuncs.h" +#endif + +#include "scrobbler.h" + +#define SCROBBLER_VERSION "1.0" + +#ifdef CONFIG_RTC +#define SCROBBLER_FILE "/.scrobbler.log" +#else +#define SCROBBLER_FILE "/.scrobbler-timeless.log" +#endif + +/* increment this on any code change that effects output */ +/* replace with CVS Revision keyword? */ +#define SCROBBLER_REVISION "1.0" + +#define SCROBBLER_MAX_CACHE 32 +/* longest entry I've had is 323, add a safety margin */ +#define SCROBBLER_CACHE_LEN 512 + +static char* scrobbler_cache; + +static int scrobbler_fd = -1; +static int cache_pos; +static struct mp3entry scrobbler_entry; +static bool pending = false; +static bool scrobbler_initialised = false; +#ifdef CONFIG_RTC +static time_t timestamp; +#else +static unsigned long timestamp; +#endif + +/* Crude work-around for Archos Sims - return a set amount */ +#if (CONFIG_CODEC != SWCODEC) && defined(SIMULATOR) +unsigned long audio_prev_elapsed(void) +{ + return 120000; +} +#endif + +static void write_cache(void) +{ + int i; + + /* If the file doesn't exist, create it. + Check at each write since file may be deleted at any time */ + scrobbler_fd = open(SCROBBLER_FILE, O_RDONLY); + if(scrobbler_fd < 0) + { + scrobbler_fd = open(SCROBBLER_FILE, O_RDWR | O_CREAT); + if(scrobbler_fd >= 0) + { + fdprintf(scrobbler_fd, "#AUDIOSCROBBLER/%s\n", SCROBBLER_VERSION); + fdprintf(scrobbler_fd, "#TZ/UNKNOWN\n"); +#ifdef CONFIG_RTC + fdprintf(scrobbler_fd, "#CLIENT/Rockbox %s %s\n", + TARGET_NAME, SCROBBLER_REVISION); +#else + fdprintf(scrobbler_fd, "#CLIENT/Rockbox %s %s Timeless\n", + TARGET_NAME, SCROBBLER_REVISION); +#endif + close(scrobbler_fd); + } + else + { + logf("SCROBBLER: cannot create log file"); + } + } + close(scrobbler_fd); + scrobbler_fd = -1; + + /* write the cache entries */ + scrobbler_fd = open(SCROBBLER_FILE, O_WRONLY | O_APPEND); + if(scrobbler_fd >= 0) + { + logf("SCROBBLER: writing %d entries", cache_pos); + + for ( i=0; i < cache_pos; i++ ) + { + logf("SCROBBLER: write %d", i); + fdprintf(scrobbler_fd, "%s", scrobbler_cache+(SCROBBLER_CACHE_LEN*i)); + } + close(scrobbler_fd); + } + else + { + logf("SCROBBLER: error writing file"); + } + + /* clear even if unsuccessful - don't want to overflow the buffer */ + cache_pos = 0; + scrobbler_fd = -1; +} + +static void add_to_cache(void) +{ +/* using HAVE_MMC to check for Ondios - anything better to use? */ +#ifndef SIMULATOR +#if defined(IPOD_NANO) || defined(HAVE_MMC) + if ( cache_pos >= SCROBBLER_MAX_CACHE ) +#else + if ( ( cache_pos >= SCROBBLER_MAX_CACHE ) || ( ata_disk_is_active() ) ) +#endif +#endif /* !SIMULATOR */ + write_cache(); + + int ret; + char rating = 'S'; /* Skipped */ + + logf("SCROBBLER: add_to_cache[%d]", cache_pos); + + if ( audio_prev_elapsed() > + (scrobbler_entry.length/2) ) + rating = 'L'; /* Listened */ + + if (scrobbler_entry.tracknum > 0) + { + ret = snprintf(scrobbler_cache+(SCROBBLER_CACHE_LEN*cache_pos), + SCROBBLER_CACHE_LEN, + "%s\t%s\t%s\t%d\t%d\t%c\t%ld\n", + scrobbler_entry.artist, + scrobbler_entry.album?scrobbler_entry.album:"", + scrobbler_entry.title, + scrobbler_entry.tracknum, + (int)scrobbler_entry.length/1000, + rating, + (long)timestamp); + } else { + ret = snprintf(scrobbler_cache+(SCROBBLER_CACHE_LEN*cache_pos), + SCROBBLER_CACHE_LEN, + "%s\t%s\t%s\t\t%d\t%c\t%ld\n", + scrobbler_entry.artist, + scrobbler_entry.album?scrobbler_entry.album:"", + scrobbler_entry.title, + (int)scrobbler_entry.length/1000, + rating, + (long)timestamp); + } + + if ( ret >= SCROBBLER_CACHE_LEN ) + { + logf("SCROBBLER: entry too long:"); + logf("SCROBBLER: %s", scrobbler_entry.path); + } else + cache_pos++; +} + +void scrobbler_change_event(struct mp3entry *id) +{ + /* add entry using the previous scrobbler_entry and timestamp */ + if (pending) + add_to_cache(); + + /* check if track was resumed > %50 played + check for blank artist or track name */ + if ((id->elapsed > (id->length/2)) || + (!id->artist ) || (!id->title ) ) + { + pending = false; + logf("SCROBBLER: skipping file %s", id->path); + } + else + { + logf("SCROBBLER: add pending"); + copy_mp3entry(&scrobbler_entry, id); +#ifdef CONFIG_RTC + timestamp = mktime(get_time()); +#else + timestamp = 0; +#endif + pending = true; + } +} + +int scrobbler_init(void) +{ + logf("SCROBBLER: init %d", global_settings.audioscrobbler); + + if(!global_settings.audioscrobbler) + return -1; + + scrobbler_cache = buffer_alloc(SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN); + + audio_set_track_changed_event(&scrobbler_change_event); + cache_pos = 0; + pending = false; + scrobbler_initialised = true; + + return 1; +} + +void scrobbler_flush_cache(void) +{ + if (scrobbler_initialised) + { + /* Add any pending entries to the cache */ + if(pending) + add_to_cache(); + + /* Write the cache to disk if needed */ + if (cache_pos) + write_cache(); + + pending = false; + } +} + +void scrobbler_shutdown(void) +{ + scrobbler_flush_cache(); + + if (scrobbler_initialised) + { + audio_set_track_changed_event(NULL); + scrobbler_initialised = false; + } +} + +bool scrobbler_is_enabled(void) +{ + return scrobbler_initialised; +} diff --git a/apps/scrobbler.h b/apps/scrobbler.h new file mode 100644 index 0000000000..543a30ed13 --- /dev/null +++ b/apps/scrobbler.h @@ -0,0 +1,24 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2006 Robert Keevil + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +void scrobbler_change_event(struct mp3entry *id); +int scrobbler_init(void); +void scrobbler_flush_cache(void); +void scrobbler_shutdown(void); +bool scrobbler_is_enabled(void); diff --git a/apps/settings.c b/apps/settings.c index 196c432c8f..97945682df 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -665,6 +665,8 @@ static const struct bit_entry hd_bits[] = {2, S_O(fm_region), 0, "fm_region", "eu,us,jp,kr" }, #endif + {1, S_O(audioscrobbler), false, "Last.fm Logging", off_on}, + /* If values are just added to the end, no need to bump the version. */ /* new stuff to be added at the end */ diff --git a/apps/settings.h b/apps/settings.h index 5313fe37b1..5fc8c7e5f5 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -498,6 +498,7 @@ struct user_settings #ifdef CONFIG_TUNER int fm_region; #endif + bool audioscrobbler; /* Audioscrobbler logging */ }; diff --git a/apps/settings_menu.c b/apps/settings_menu.c index eabe153810..0193aa0102 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -56,6 +56,7 @@ #include "yesno.h" #include "list.h" #include "color_picker.h" +#include "scrobbler.h" #ifdef HAVE_LCD_BITMAP #include "peakmeter.h" @@ -1387,6 +1388,23 @@ static bool next_folder(void) INT, names, 3, NULL ); } +static bool audioscrobbler(void) +{ + bool result = set_bool_options(str(LANG_AUDIOSCROBBLER), + &global_settings.audioscrobbler, + STR(LANG_ON), + STR(LANG_OFF), + NULL); + + if (!scrobbler_is_enabled() && global_settings.audioscrobbler) + gui_syncsplash(HZ*2, true, str(LANG_PLEASE_REBOOT)); + + if(!result) + scrobbler_shutdown(); + + return result; +} + static bool codepage_setting(void) { static const struct opt_items names[] = { @@ -1605,7 +1623,7 @@ static bool dircache(void) NULL); if (!dircache_is_enabled() && global_settings.dircache) - gui_syncsplash(HZ*2, true, str(LANG_DIRCACHE_REBOOT)); + gui_syncsplash(HZ*2, true, str(LANG_PLEASE_REBOOT)); if (!result) dircache_disable(); @@ -1747,8 +1765,9 @@ static bool playback_settings_menu(void) { ID2P(LANG_ID3_ORDER), id3_order }, { ID2P(LANG_NEXT_FOLDER), next_folder }, #ifdef HAVE_HEADPHONE_DETECTION - { ID2P(LANG_UNPLUG), unplug_menu } + { ID2P(LANG_UNPLUG), unplug_menu }, #endif + { ID2P(LANG_AUDIOSCROBBLER), audioscrobbler} }; bool old_shuffle = global_settings.playlist_shuffle; diff --git a/apps/tree.c b/apps/tree.c index 70b83f8934..bfb6412bfe 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -65,6 +65,7 @@ #include "yesno.h" #include "gwps-common.h" #include "eeprom_settings.h" +#include "scrobbler.h" /* gui api */ #include "list.h" @@ -1378,6 +1379,7 @@ void ft_play_filename(char *dir, char *file) /* These two functions are called by the USB and shutdown handlers */ void tree_flush(void) { + scrobbler_shutdown(); tagcache_shutdown(); playlist_shutdown(); @@ -1439,4 +1441,5 @@ void tree_restore(void) } #endif tagcache_start_scan(); + scrobbler_init(); } diff --git a/firmware/common/timefuncs.c b/firmware/common/timefuncs.c index fb16f0c253..e48aadd0a2 100644 --- a/firmware/common/timefuncs.c +++ b/firmware/common/timefuncs.c @@ -130,3 +130,38 @@ int set_time(const struct tm *tm) return 0; #endif } + +/* mktime() code taken from lynx-2.8.5 source, written + by Philippe De Muyter */ +time_t mktime(struct tm *t) +{ + short month, year; + time_t result; + static int m_to_d[12] = + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + month = t->tm_mon; + year = t->tm_year + month / 12 + 1900; + month %= 12; + if (month < 0) + { + year -= 1; + month += 12; + } + result = (year - 1970) * 365 + (year - 1969) / 4 + m_to_d[month]; + result = (year - 1970) * 365 + m_to_d[month]; + if (month <= 1) + year -= 1; + result += (year - 1968) / 4; + result -= (year - 1900) / 100; + result += (year - 1600) / 400; + result += t->tm_mday; + result -= 1; + result *= 24; + result += t->tm_hour; + result *= 60; + result += t->tm_min; + result *= 60; + result += t->tm_sec; + return(result); +} diff --git a/firmware/export/audio.h b/firmware/export/audio.h index 58bc1e157d..9099cb3765 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -141,6 +141,7 @@ void audio_set_spdif_power_setting(bool on); #endif #endif unsigned long audio_get_spdif_sample_rate(void); +unsigned long audio_prev_elapsed(void); #if CONFIG_CODEC == SWCODEC /* audio encoder functions (defined in playback.c) */ int audio_get_encoder_id(void); diff --git a/firmware/include/time.h b/firmware/include/time.h index cddec1e708..23f72fd93d 100644 --- a/firmware/include/time.h +++ b/firmware/include/time.h @@ -20,8 +20,7 @@ struct tm int tm_isdst; }; -#if defined(SIMULATOR) && !defined(_TIME_T_DEFINED) && !defined(_TIME_T_DECLARED) -/* for non-win32 simulators */ +#if !defined(_TIME_T_DEFINED) && !defined(_TIME_T_DECLARED) typedef long time_t; /* this define below is used by the mingw headers to prevent duplicate diff --git a/firmware/include/timefuncs.h b/firmware/include/timefuncs.h index 03bb06fb9c..d2f42eb9ff 100644 --- a/firmware/include/timefuncs.h +++ b/firmware/include/timefuncs.h @@ -27,5 +27,6 @@ struct tm *get_time(void); int set_time(const struct tm *tm); bool valid_time(const struct tm *tm); +time_t mktime(struct tm *t); #endif /* _TIMEFUNCS_H_ */ diff --git a/firmware/mpeg.c b/firmware/mpeg.c index 61b0a22d87..9fe3df224c 100644 --- a/firmware/mpeg.c +++ b/firmware/mpeg.c @@ -108,6 +108,9 @@ static struct trackdata trackdata[MAX_TRACK_ENTRIES]; static unsigned int current_track_counter = 0; static unsigned int last_track_counter = 0; +/* Play time of the previous track */ +unsigned long prev_track_elapsed; + #ifndef SIMULATOR static int track_read_idx = 0; static int track_write_idx = 0; @@ -1056,6 +1059,9 @@ static void track_change(void) { DEBUGF("Track change\n"); + struct trackdata *track = get_trackdata(0); + prev_track_elapsed = track->id3.elapsed; + #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) /* Reset the AVC */ sound_set_avc(-1); @@ -1072,6 +1078,11 @@ static void track_change(void) current_track_counter++; } +unsigned long audio_prev_elapsed(void) +{ + return prev_track_elapsed; +} + #ifdef DEBUG void hexdump(const unsigned char *buf, int len) { -- cgit v1.2.3