when i'm biking i listen to music on my ipod (hacked to run rockbox), i like using last.fm so i record a scrobble.log
file
i was going to upload the scrobbles but i noticed some of the dates were from before 2001
$ cat /mnt/.scrobbler.log
...
JPEGMAFIA EP2! FEED HER! 6 176 L 1616925238
Yuzo Koshiro Actraiser Opening 131 S 946994845
...
❯ date -d '@1616925238'
Sun Mar 28 05:53:58 AM EDT 2021
❯ date -d '@946994845'
Tue Jan 4 09:07:25 AM EST 2000
oh yeah oops, i guess my date got reset on the ipod. i checked out rockbox/apps/plugins/lastfm_scrobbler.c
and saw that the AUDIOSCROBBLER/1.1
format they output is well-documented
i wanted to play with parser combinators so i wrote some rust code with nom
to parse the timestamps
running 1 test
[src/main.rs:71] &scrobble = "JPEGMAFIA\tBlack Ben Carson\tDrake Era\t1\t237\tS\t1605758450\t"
[src/main.rs:73] &parsed = Scrobble {
artist: "JPEGMAFIA",
album: "Black Ben Carson",
track: "Drake Era",
track_position: 1,
song_duration: 237,
rating: Skipped,
timestamp: 1605758450,
}
test parse_line ... ok
i picked an arbitrary date in the past and used it as a cutoff - if a scrobble was logged before 2005, it probably needs updated, since i got my ipod in 2020
/// Anything older than this needs an offset applied.
const SCROBBLE_CUTOFF: &str = "2005-01-01T00:00:00Z";
after writing some code to identify suspicious scrobbles, i played around a little bit until i found the right offset, a little over 22 years in the future
/// Number of days to add to the suspicious scrobbles.
const SCROBBLE_DAYS_OFFSET: u64 = (365 * 22) + 215;
i use a bike computer and strava so i had the exact time of my bike ride available to check
i wrote a little Scrobble::fix
function
impl Scrobble {
/// Adjust the timestamps for suspicious scrobbles.
fn fix(self, cutoff: DateTime<FixedOffset>) -> Result<Self, String> {
if self.timestamp > cutoff {
return Ok(self);
}
let updated_timestamp = self
.timestamp
.checked_add_days(Days::new(SCROBBLE_DAYS_OFFSET))
.ok_or("failed to apply offset")?;
Ok(Self {
timestamp: updated_timestamp,
..self
})
}
}
and then i had my scrobbler.log
file all fixed up with reasonable timestamps :)
/// Output scrobbler.log with fixed timestamps.
fn main() -> std::io::Result<()> {
let cutoff =
DateTime::parse_from_rfc3339(SCROBBLE_CUTOFF).expect("failed to parse cutoff date");
let log = std::fs::read_to_string("scrobbler.log")?;
let scrobbles: String = log
.lines()
.skip(3)
.map(|input| {
Scrobble::new(input)
.and_then(|scrobble| scrobble.fix(cutoff).map(|fixed| fixed.to_string()))
})
.intersperse(Ok("\n".to_string()))
.collect::<Result<String, _>>()
.unwrap();
Ok(println!("{HEADER}{scrobbles}"))
}
now i gotta write some code to upload this old scrobbler format to last.fm