From 371d450b6863c240633626d000c3f03843414b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Reynolds?= Date: Mon, 25 Oct 2010 15:22:02 -0300 Subject: Added libre.fm import/export scripts --- bin/lastexport.py | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ bin/libreimport.py | 53 ++++++++++++++++ bin/scrobble.py | 107 ++++++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+) create mode 100755 bin/lastexport.py create mode 100755 bin/libreimport.py create mode 100755 bin/scrobble.py diff --git a/bin/lastexport.py b/bin/lastexport.py new file mode 100755 index 0000000..3e8c0d8 --- /dev/null +++ b/bin/lastexport.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python2 +#-*- coding: utf-8 -*- + +# 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 3 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, see . +# + +""" +Script for exporting tracks through audioscrobbler API. +Usage: lastexport.py -u USER [-o OUTFILE] [-p STARTPAGE] [-s SERVER] +""" + +import urllib2, urllib, sys, time +from xml.dom import minidom +from optparse import OptionParser + +def get_options(parser): + """ Define command line options.""" + parser.add_option("-u", "--user", dest="username", default=None, + help="User name.") + parser.add_option("-o", "--outfile", dest="outfile", default="exported_tracks.txt", + help="Output file, default is exported_tracks.txt") + parser.add_option("-p", "--page", dest="startpage", type="int", default="1", + help="Page to start fetching tracks from, default is 1") + parser.add_option("-s", "--server", dest="server", default="last.fm", + help="Server to fetch track info from, default is last.fm") + options, args = parser.parse_args() + + if not options.username: + sys.exit("User name not specified, see --help") + + return options.username, options.outfile, options.startpage, options.server + +def connect_server(username, startpage): + """ Connect to server and get a XML page.""" + if server == "libre.fm": + baseurl = 'http://alpha.libre.fm/2.0/?' + urlvars = dict(method='user.getrecenttracks', + api_key='ohaiderthisisthelastexportscript', + user=username, + page=startpage, + limit=200) + + elif server == "last.fm": + baseurl = 'http://ws.audioscrobbler.com/2.0/?' + urlvars = dict(method='user.getrecenttracks', + api_key='e38cc7822bd7476fe4083e36ee69748e', + user=username, + page=startpage, + limit=50) + else: + sys.exit("No config exist for this server, valid servers are: last.fm, libre.fm") + + + url = baseurl + urllib.urlencode(urlvars) + try: + f = urllib2.urlopen(url) + except: + print "Failed to open page %s" % urlvars['page'] + response = None + return response + + response = f.read() + f.close() + return response + +def get_pageinfo(response): + """Check how many pages of tracks the user have.""" + xmlpage = minidom.parseString(response) + totalpages = xmlpage.getElementsByTagName('recenttracks')[0].attributes['totalPages'].value + return int(totalpages) + +def get_tracklist(response): + """Read XML page and get a list of tracks and their info.""" + xmlpage = minidom.parseString(response) + tracklist = xmlpage.getElementsByTagName('track') + return tracklist + +def parse_track(tracklist, i): + """Extract info from every track entry and output to list.""" + track = tracklist[i].getElementsByTagName + try: + artistname = track('artist')[0].childNodes[0].data + except: + artistname = '' + try: + artistmbid = track('artist')[0].attributes['mbid'].value + except: + artistmbid = '' + try: + trackname = track('name')[0].childNodes[0].data + except: + trackname = '' + try: + trackmbid = track('mbid')[0].childNodes[0].data + except: + trackmbid = '' + try: + albumname = track('album')[0].childNodes[0].data + except: + albumname = '' + try: + albummbid = track('album')[0].attributes['mbid'].value + except: + albummbid = '' + try: + date = track('date')[0].attributes['uts'].value + except: + date = '' + + output = [date, trackname, artistname, albumname, trackmbid, artistmbid, albummbid] + + return output + +def write_tracks(trackdict, outfile, startpage, page, totalpages): + """Write dictionary content with all tracks to file.""" + #create a sorted list from track dictionary. + sortlist = [] + for v in trackdict.values(): + sortlist.append(v) + sortlist.sort(reverse=True) + + #open output file and write tracks. + f = open(outfile, 'a') + for i in sortlist: + #sys.stdout.write(("\t".join(trackdict[i]) + "\n").encode('utf-8')) + f.write(("\t".join(i) + "\n").encode('utf-8')) + print "Wrote page %s-%s of %s to file %s, exiting." % (startpage, page, totalpages, outfile) + f.close() + +def main(username, startpage, outfile): + trackdict = dict() + page = startpage + response = connect_server(username, page) + totalpages = get_pageinfo(response) + #totalpages = 2 + + if startpage > totalpages: + sys.exit("First page (%s) is higher than total pages (%s), exiting." % (startpage, totalpages)) + + while page <= totalpages: + #Skip connect if on first page, already have that one stored. + if page > startpage: + response = connect_server(username, page) + #If empty response, something went wrong, write tracks to file and exit. + if not response: + write_tracks(trackdict, outfile, startpage, page-1, totalpages) + sys.exit() + + tracklist = get_tracklist(response) + for i in range(len(tracklist)): + track = parse_track(tracklist, i) + trackdict.setdefault(track[0], track) + + if (page % 10) == 0: + print "Getting page %s of %s.." % (page, totalpages) + + page += 1 + time.sleep(.5) + + + write_tracks(trackdict, outfile, startpage, page-1, totalpages) + +if __name__ == "__main__": + parser = OptionParser() + username, outfile, startpage, server = get_options(parser) + main(username, startpage, outfile) + diff --git a/bin/libreimport.py b/bin/libreimport.py new file mode 100755 index 0000000..e1463e2 --- /dev/null +++ b/bin/libreimport.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python2 + +#modified version of old import.py + +# Lastscrape -- recovers data from libre.fm +# Copyright (C) 2009 Free Software Foundation, Inc +# +# 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 3 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, see . +# + +import os.path +import sys +sys.path.append(os.path.join(sys.path[0], '../scripts')) + +from datetime import datetime +import getpass +from scrobble import get_parser, ScrobbleServer, ScrobbleTrack +import time +from urllib import urlencode +from urllib2 import urlopen + + +if __name__ == '__main__': + usage = "%prog [-s ] " + parser = get_parser(usage=usage) + opts,args = parser.parse_args() + if len(args) != 2: + parser.error("All arguments are required.") + + username,data = args + server = opts.server + password = getpass.getpass() + scrobbler = ScrobbleServer(server, username, password) + + n = 0 + for line in file(data): + n = n + 1 + timestamp, track, artist, album, trackmbid, artistmbid, albummbid = line.strip("\n").split("\t") + #submission protocol doesnt specify artist/album mbid, so we dont send them + scrobbler.add_track(ScrobbleTrack(timestamp, track, artist, album, trackmbid)) + print "%d: Adding to post %s playing %s" % (n, artist, track) + scrobbler.submit() diff --git a/bin/scrobble.py b/bin/scrobble.py new file mode 100755 index 0000000..428e668 --- /dev/null +++ b/bin/scrobble.py @@ -0,0 +1,107 @@ +#modified version of old gobble.py +try: + import hashlib + md5hash = hashlib.md5 +except ImportError: + import md5 + md5hash = md5.new +from optparse import OptionParser +import time +from urllib import urlencode +from urllib2 import urlopen + + +class ScrobbleException(Exception): + + pass + + +class ScrobbleServer(object): + + def __init__(self, server_name, username, password, client_code='imp'): + if server_name[:7] != "http://": + server_name = "http://%s" % (server_name,) + self.client_code = client_code + self.name = server_name + self.password = password + self.post_data = [] + self.session_id = None + self.submit_url = None + self.username = username + self._handshake() + + + def _handshake(self): + timestamp = int(time.time()) + token = (md5hash(md5hash(self.password).hexdigest() + + str(timestamp)).hexdigest()) + auth_url = "%s/?hs=true&p=1.2&u=%s&t=%d&a=%s&c=%s" % (self.name, + self.username, + timestamp, + token, + self.client_code) + response = urlopen(auth_url).read() + lines = response.split("\n") + if lines[0] != "OK": + raise ScrobbleException("Server returned: %s" % (response,)) + self.session_id = lines[1] + self.submit_url = lines[3] + + def submit(self): + if len(self.post_data) == 0: + return + i = 0 + data = [] + for track in self.post_data: + data += track.get_tuples(i) + i += 1 + data += [('s', self.session_id)] + response = urlopen(self.submit_url, urlencode(data)).read() + if response != "OK\n": + raise ScrobbleException("Server returned: %s" % (response,)) + self.post_data = [] + time.sleep(1) + + def add_track(self, scrobble_track): + i = len(self.post_data) + if i > 49: + self.submit() + i = 0 + self.post_data.append(scrobble_track) + + +class ScrobbleTrack(object): + + def __init__(self, timestamp, trackname, artistname, albumname=None, \ + trackmbid=None, tracklength=None, tracknumber=None): + self.timestamp = timestamp + self.trackname = trackname + self.artistname = artistname + self.albumname = albumname + self.trackmbid = trackmbid + self.tracklength = tracklength + self.tracknumber = tracknumber + + def get_tuples(self, i): + #timestamp = str(int(time.mktime(self.timestamp.utctimetuple()))) + data = [] + data += [('i[%d]' % i, self.timestamp), ('t[%d]' % i, self.trackname), + ('a[%d]' % i, self.artistname)] + if self.albumname is not None: + data.append(('b[%d]' % i, self.albumname)) + if self.trackmbid is not None: + data.append(('m[%d]' % i, self.trackmbid)) + if self.tracklength is not None: + data.append(('l[%d]' % i, self.tracklength)) + if self.tracknumber is not None: + data.append(('n[%d]' % i, self.tracknumber)) + return data + + +def get_parser(usage): + parser = OptionParser(usage=usage) + parser.add_option('-s', '--server', + help="Server to submit to. Defaults to" + " 'turtle.libre.fm'.") + parser.set_defaults(server='turtle.libre.fm') + return parser -- cgit v1.2.3