Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ampache resolver #33

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contrib/ampache-api/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is an Ampache resolver which uses Ampache's XML Web-API.
To use it, one only needs a user account on any ampache installation.
It's possible to use multiple Ampache servers/accounts.
4 changes: 4 additions & 0 deletions contrib/ampache-api/ampache-resolver.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[localhost] # section name is used to form source name
url = http://localhost/ampache
user = John
password = Doe
92 changes: 92 additions & 0 deletions contrib/ampache-api/ampache-resolver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-
#
# Ampache resolver for playdar
# using Ampache's Web API
#
# author: Remo Giermann <mo@liberejo.de>
# created: 2011/04/03
#

import os, sys
from ConfigParser import ConfigParser

import ampache
import playdar_resolver
from playdar_resolver import soundex

configuration = os.path.join(os.path.dirname(sys.argv[0]), "ampache-resolver.cfg")


class AmpacheResolver(playdar_resolver.PlaydarResolver):

def __init__(self):

self.ampaches = []

config = ConfigParser()
if not config.read(configuration):
return
else:
for section in config.sections():
if config.options(section) == ['url', 'password', 'user']:
url = config.get(section, 'url')
un = config.get(section, 'user')
pw = config.get(section, 'password')

amp = ampache.Ampache(url)
if amp.handshake(un, pw) is True:
self.ampaches.append((amp, "Ampache (%s)" % section))


def resolver_settings(self):
"""My settings"""
return {'name':"Ampache (API) Resolver", 'targettime':5000, 'weight':60}


def results(self, query):
results = []

for amp, name in self.ampaches:

songs = amp.search_songs(u'{artist} {track}'.format(**query))

# first item in search results is best match, last item is worst,
# so scoring is simple:
score = 1.0
for s in songs:

if s.artist['name'].lower() == query['artist'].lower() \
and score > 0.5:

results.append(
{'artist': s.artist['name'],
'track': s.title,
'album': s.album['name'],
'duration': int(s.time),
'url': s.url,
'score': score,
'extension': s.url[-3:],
'art': s.art,
#'size': int(s.size),
'source': name
})
# decrement score for next match
score -= .1

else:
break


return results



if __name__ == "__main__":
#q = {'qid':'test', 'artist': 'iron maiden', 'track': 'aces high'}
#r = AmpacheResolver()
#playdar_resolver.print_result(r.resolve(q))
try:
AmpacheResolver.start_static()
except:
traceback.print_exc(file=sys.stderr)
180 changes: 180 additions & 0 deletions contrib/ampache-api/ampache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# -*- coding: utf8 -*-
# This class speaks to the Ampache XML API
#
# author: Remo Giermann <mo@liberejo.de>
# created: 2011/03/31
#

import time, urllib
from hashlib import sha256
from xml.etree.ElementTree import ElementTree

def passphrase(password):
"""
Generates the PASSPRHASE needed for a handshake.
"""
now = int(time.time())
key = sha256(password).hexdigest()
phrase = sha256(str(now)+key).hexdigest()

return (now, phrase)


class AmpacheArtist(object):
"""
Simple container class corresponding to
Ampache's Artist XML.
"""
def __init__(self, element):
self.id = element.attrib['id']
self.tags = element.findall('tag')
self.name = element.find('name').text

self.albums = element.find('albums').text
self.songs = element.find('songs').text

self.preciserating = element.find('preciserating').text
self.rating = element.find('rating').text

class AmpacheAlbum(object):
"""
Simple container class corresponding to
Ampache's Album XML.
"""
def __init__(self, element):
self.id = element.attrib['id']
self.tags = element.findall('tag')
self.name = element.find('name').text
artist = element.find('artist')
self.artist = {'id': artist.attrib['id'], 'name': artist.text}

self.year = element.find('year').text
self.tracks = element.find('tracks').text
self.disk = element.find('disk').text
self.art = element.find('art').text

self.preciserating = element.find('preciserating').text
self.rating = element.find('rating').text

class AmpacheSong(object):
"""
Simple container class corresponding to
Ampache's Song XML.
"""
def __init__(self, element):
self.id = element.attrib['id']
self.tags = element.findall('tag')
self.title = element.find('title').text
artist = element.find('artist')
self.artist = {'id': artist.attrib['id'], 'name': artist.text}
album = element.find('album')
self.album = {'id': album.attrib['id'], 'name': album.text}

self.track = element.find('track').text
self.time = element.find('time').text
self.url = element.find('url').text
self.size = element.find('size').text
self.art = element.find('art').text

self.preciserating = element.find('preciserating').text
self.rating = element.find('rating').text


class Ampache(object):
"""
This class speaks to an Ampache server through its
XML API. Only a few methods are implemented, but
every method of the API could be added quickly.

See http://ampache.org/wiki/dev:xmlapi for info.
"""

def __init__(self, url):
self.auth = ''
self.serverurl = url + u"/server/xml.server.php?"


def __query(self, **kwargs):
for k,v in kwargs.items():
if type(v) is unicode:
kwargs[k] = v.encode('utf8')
query = urllib.urlencode(kwargs)

try:
fd = urllib.urlopen(self.serverurl+query)
except IOError:
self.auth = ''
return None
else:
tree = ElementTree()
tree.parse(fd)
return tree


def __action(self, action, **kwargs):
if not self.auth:
return False

return self.__query(action=action, auth=self.auth, **kwargs)


def handshake(self, username, password):

timestamp, phrase = passphrase(password)
tree = self.__query(action='handshake', auth=phrase, timestamp=timestamp, version=350001, user=username)

if tree is None:
auth = None
else:
auth = tree.find('auth')


if auth is None:
self.auth = ''
return False
else:
self.auth = auth.text
return True


# API method 'artist'
def artists(self, filter, **kwargs):
tree = self.__action('artists', filter=filter, **kwargs)
artists = tree.findall('artist')
return [AmpacheArtist(a) for a in artists]

# API method 'artist_songs'
def artist_songs(self, artist_uid):
tree = self.__action('artist_songs', filter=artist_uid)
songs = tree.findall('song')
return [AmpacheSong(a) for a in songs]

# API method 'artist_albums'
def artist_albums(self, artist_uid):
tree = self.__action('artist_albums', filter=artist_uid)
albums = tree.findall('album')
return [AmpacheAlbum(a) for a in albums]

# API method 'albums'
def albums(self, filter, **kwargs):
tree = self.__action('albums', filter=filter, **kwargs)
albums = tree.findall('albums')
return [AmpacheAlbums(a) for a in albums]

# API method 'album_songs'
def album_songs(self, album_uid):
tree = self.__action('album_songs', filter=album_uid)
songs = tree.findall('song')
return [AmpacheSong(a) for a in songs]

# API method 'songs'
def songs(self, filter, **kwargs):
tree = self.__action('songs', filter=filter, **kwargs)
songs = tree.findall('song')
return [AmpacheSong(a) for a in songs]

# API method 'search_songs'
def search_songs(self, filter, **kwargs):
tree = self.__action('search_songs', filter=filter, **kwargs)
songs = tree.findall('song')
return [AmpacheSong(a) for a in songs]
1 change: 1 addition & 0 deletions contrib/ampache-api/playdar_resolver.py