Permalink
Browse files

hello repo

  • Loading branch information...
unbracketed committed Jul 10, 2010
0 parents commit c32f72ae5b6eade8fb018502d8ac0dca47e19940
@@ -0,0 +1,9 @@
+Django 1.0
+
+django-command-extensions r188
+
+Twisted 8.2
+
+
+python-musicbrainz 2.0.6.0: http://ftp.musicbrainz.org/pub/musicbrainz/python-musicbrainz2/python-musicbrainz2-0.6.0.tar.gz
+ svn checkout http://svn.musicbrainz.org/python-musicbrainz2/trunk python-musicbrainz2
No changes.
No changes.
@@ -0,0 +1,19 @@
+FETCH_STATUS_SUCCESS = 1
+FETCH_STATUS_CONNECTION_ERROR = 2
+FETCH_STATUS_NO_METADATA_RECEIVED = 3
+FETCH_STATUS_NO_METADATA_MATCH = 3
+FETCH_STATUS_LOW_RELEVANCY_MATCH = 4
+FETCH_STATUS_UNKNOWN_ERROR = 999
+FETCH_STATUS_CHOICES = (
+ (FETCH_STATUS_SUCCESS,'Success',),
+ (FETCH_STATUS_CONNECTION_ERROR,'Connection Error',),
+ (FETCH_STATUS_NO_METADATA_RECEIVED,'No Metadata',),
+ (FETCH_STATUS_NO_METADATA_RECEIVED,'No Match for Metadata',),
+ (FETCH_STATUS_LOW_RELEVANCY_MATCH,'Low Relevancy Match',),
+ (FETCH_STATUS_UNKNOWN_ERROR,'Unknown Error'),
+)
+
+#TODO: what do the scores represent?
+MUSICBRAINZ_TRACK_RESULT_SCORE_LOW = 80
+
+
No changes.
No changes.
No changes.
No changes.
@@ -0,0 +1,59 @@
+import re
+from twisted.internet import protocol, reactor
+from twisted.internet import task
+from django_extensions.management.jobs import BaseJob
+
+from radiospy.fetcher.shoutcast import ShoutcastMetadataPeeker
+from radiospy.fetcher.streams import StreamHandler
+from radiospy.playlist.models import Channel
+
+#TODO:
+# try all streams until one succeeds
+
+
+class Job(BaseJob):
+ help = "run the net station metadata fetcher"
+
+ def execute(self):
+
+ def update_channels():
+ print "updating channels"
+ update_channels = Channel.objects.filter(active=True)
+
+ #grab their stream address
+ for i,channel in enumerate(update_channels):
+ print "handling channel %s" % str(channel)
+ sh = StreamHandler(channel.stream_url)
+
+ #TODO: handle no valid URLs
+
+ for host,port,path in sh.urls:
+ mo = re.compile(r'^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$').match(host)
+ if mo:
+ print 'detected IP, going to next'
+ continue
+
+ else:
+
+ #TODO handle timeout
+
+ #TODO - shoutcast client doesn't like IP addr?
+
+ #TODO record if a valid connect addr is not found
+
+ #TODO: cache the actual connect address, try it first next time
+
+ #TODO: how to catch connection failure and try other stream addresses
+
+ #provide a valid default path to the Shoutcast client
+ if not len(path):
+ path = '/'
+ print "connecting %s %s %s" % (host,port,path)
+ protocol.ClientCreator(reactor, ShoutcastMetadataPeeker, channel, path=path).connectTCP(host,int(port))
+
+ break
+
+ print "starting reactor"
+ loop = task.LoopingCall(update_channels)
+ loop.start(60)
+ reactor.run()
No changes.
@@ -0,0 +1,27 @@
+import datetime
+from django.db import models
+
+from radiospy.fetcher.constants import FETCH_STATUS_CHOICES, FETCH_STATUS_SUCCESS
+from radiospy.playlist.models import Channel
+
+class FetchResult(models.Model):
+ """
+ Records that an attempt was made to fetch metadata about the current playing
+ audio on a Channel
+ """
+ channel = models.ForeignKey(Channel,db_index=True)
+ timestamp = models.DateTimeField(default=datetime.datetime.now)
+ status = models.BooleanField()
+ status_flag = models.IntegerField(choices=FETCH_STATUS_CHOICES,default=FETCH_STATUS_SUCCESS)
+
+class FailedFetch(models.Model):
+ """
+ Whenever a metadata fetch appears to fail an instance will be created to hold
+ any data about what might have gone wrong.
+
+ data will be a pickled python object
+ """
+ fetch = models.ForeignKey(FetchResult)
+ #TODO: find pickle field
+ data = models.TextField()
+
@@ -0,0 +1,62 @@
+#
+# "@(#) $Id: PLS.py,v 1.1.1.1 2006-07-27 03:16:08 gioshe Exp $"
+#
+# This work is released under the GNU GPL, version 2 or later.
+#
+# handle .pls files
+#
+import httplib, urllib, re
+
+def http_head(host,port,fileName):
+ h = httplib.HTTP(host,port)
+ h.putrequest('HEAD',fileName)
+ h.putheader('Accept','text/html')
+ h.putheader('Accept','text/plain')
+ h.putheader('Accept','*/*')
+ h.endheaders()
+ return h.getreply()
+
+def http_get(host,port,fileName):
+ h = httplib.HTTP(host,port)
+ h.putrequest('GET',fileName)
+ h.putheader('Accept','text/html')
+ h.putheader('Accept','text/plain')
+ h.putheader('Accept','*/*')
+ h.endheaders()
+ errCode, errMessage, headers = h.getreply()
+ if errCode==200 or errCode==302:
+ f = h.getfile()
+ return f.read()
+ return None
+
+def fetchAndParsePLS(url):
+ (type,path) = urllib.splittype(url)
+ (hostport,path) = urllib.splithost(path)
+ (host,port) = urllib.splitport(hostport)
+ if port==None: port = 80
+ s = http_get(host,port,path)
+ if s!=None:
+ #print s
+ return parsePLS(s)
+ return None
+
+def readAndParsePLS(url):
+ pls = open(url).read()
+ return parsePLS(pls)
+
+def parsePLS(pls):
+ flags = re.MULTILINE | re.IGNORECASE
+ mo = re.search(r"^numberofentries\=(\d+)$",pls,flags)
+ if mo:
+ numberOfEntries = int(mo.group(1))
+ streams = []
+ for i in range(1,numberOfEntries+1):
+ mo = re.search(r"File%d\=(.+)$" % i,pls,flags)
+ if mo:
+ streams.append(mo.group(1))
+ return streams
+ return None
+
+if __name__=='__main__':
+ print fetchAndParsePLS("http://somafm.com/secretagent.pls")
+
@@ -0,0 +1,187 @@
+import os
+if not 'DJANGO_SETTINGS_MODULE' in os.environ:
+ os.environ['DJANGO_SETTINGS_MODULE'] = 'radiospy.settings'
+
+import datetime
+import logging
+from urllib2 import HTTPError
+
+import musicbrainz2.webservice as ws
+from musicbrainz2.webservice import TrackFilter
+from twisted.protocols.shoutcast import ShoutcastClient
+
+
+from django.core.exceptions import ObjectDoesNotExist
+
+from radiospy.fetcher.constants import *
+from radiospy.fetcher.models import FailedFetch, FetchResult
+from radiospy.playlist.models import Track, Playlist
+
+logging.basicConfig(filename='shoutcast_reader',level=logging.DEBUG)
+
+
+class ShoutcastMetadataPeeker(ShoutcastClient):
+ """
+ Utilizies the Twisted Shoutcast Client for connecting to
+ a net radio stream, identifying metadata and parsing it into
+ name,value pairs. Once the meta is handed off this class then attempts
+ to extract a relevant artist and title for use in a lookup to the
+ MusicBrainz music data respository.
+ """
+
+ def __init__(self,channel,path="/"):
+
+ self.channel = channel
+ self.path = path
+ self.got_metadata = False
+ self.metaint = None
+ self.metamode = "mp3"
+ self.databuffer = ""
+
+ def gotMetaData(self, data):
+
+ q = ws.Query()
+
+
+ #metadata track Brewer & Shipley - Witchi-Tai-To
+ #[(u'Brewer & Shipley - Witchi', u'Tai-To'), (u'Brewer & Shipley - Witchi-Tai', u'To')]
+ #No valid MB results
+ #=== EXCEPTION local variable 'track' referenced before assignment
+ #meta: [('StreamTitle', 'Brewer & Shipley - Witchi-Tai-To'), ('StreamUrl', 'http://www.radioparadise.com/graphics/covers/m/B00005MJYP.jpg')]
+
+
+ try:
+ try_list = self.track_from_metadata(data)
+ print try_list
+ if len(try_list):
+
+ #TODO: there should be a "best bet" match. if something
+ #fails in the loop, default to the best bet match
+ track = None
+ for artist,title in try_list:
+
+ #TODO - match track from local data first before
+ # doing MB lookup
+
+ try:
+ res = q.getTracks(TrackFilter(title=title,artistName=artist))
+ except HttpError, e:
+ logging.warning("MusicBrainz getTracks : %s : %s : %s" % (e,artist,title,))
+ except Exception, e:
+ logging.error("MusicBrainz getTracks : %s : %s : %s" % (e,artist,title,))
+
+ if len(res) == 0 or max([r.score for r in res]) < MUSICBRAINZ_TRACK_RESULT_SCORE_LOW:
+
+ #if MB lookup fails, try others?
+
+ logging.info("MusicBrainz track not found: %s %s" % (artist,title,))
+
+ #we can be reasonably sure about artist,title
+
+ if len(try_list) == 1 or len(res):
+ track,created = Track.objects.get_or_create(artist=artist,name=title)
+
+ if created:
+ track.save()
+ else:
+ continue
+
+ else:
+ #we seem to have good MB results
+ try:
+
+ #TODO: try track from cache, no need for playlist lookup if track in cache
+
+ track = Track.objects.get(MBID=res[0].track.id)
+ except ObjectDoesNotExist:
+
+ track = Track(MBID = res[0].track.id,
+ artist = res[0].track.artist.name,
+ name = res[0].track.title)
+
+ track.save()
+
+ #TODO cache track
+
+ print "stored track %s" % (track.title_display)
+
+ #TODO fix dates ***********
+ #TODO assume playlist instance already exists if track was in cache
+ if track:
+ pl,created = Playlist.objects.get_or_create(channel=self.channel,track=track,time__lte=datetime.datetime.now())
+ if created:
+ pl.save()
+ else:
+ print "could not resolve track"
+
+ else:
+ #TODO: no valid track data in metadata
+ raise Exception
+
+
+ except Exception, e:
+ #TODO: record metadata error
+ #differentiate between malformed and no match?
+ print "=== EXCEPTION %s" % e
+
+ finally:
+ print "meta:", data
+ self.transport.loseConnection()
+
+ def gotMP3Data(self, data): pass
+
+ def track_from_metadata(self,data):
+ #sample from RadioParadise stream
+ #[('StreamTitle', 'Cocteau Twins - Road, River And Rail'), ('StreamUrl', 'http://www.radioparadise.com/graphics/covers/m/B00000DRAX.jpg')]
+
+ #TODO: handle encoding properly
+ #metadata track Sigur R?s - Med Sud I Eyrum
+ #['Sigur R\xffs ', ' Med Sud I Eyrum']
+ #=== EXCEPTION 'ascii' codec can't decode byte 0xff in position 7: ordinal not in range(128)
+ #meta: [('StreamTitle', 'Sigur R\xffs - Med Sud I Eyrum'), ('StreamUrl', '')]
+
+
+ #TODO: look for station tags
+ #ex: meta: [('StreamTitle', '~+ [P] Hot 108 Jamz Promo +~ - ~+ [P] Leads All +~'), ('StreamUrl', '')]
+ #ex: Big Url (soma frm)
+
+ track = ''
+ for item in data:
+ if item[0] == 'StreamTitle':
+ track = item[1]
+ break
+ print 'metadata track %s' % track
+
+
+ #TODO: try swapping ` for '
+
+
+ # detect multiple hyphens
+ if len(track):
+ parts = track.split('-')
+ if len(parts) < 2:
+ #TODO
+ return []
+ elif len(parts) == 2:
+ print parts
+ return ((unicode(parts[0].strip()),unicode(parts[1].strip()),),)
+ else:
+ return [ (unicode('-'.join(parts[:i]).strip()),unicode('-'.join(parts[i:]).strip()),) for i in range(1,len(parts)) ]
+ return []
+
+ def handleResponse(self,response):
+ print response
+
+
+if __name__ == '__main__':
+ class Test(ShoutcastClient):
+ def gotMetaData(self, data): print "meta:", data
+ def gotMP3Data(self, data): pass
+
+ from twisted.internet import protocol, reactor
+ import sys
+
+ pc = protocol.ClientCreator(reactor, ShoutcastMetadataPeeker, None, path=sys.argv[3])
+ pc.connectTCP(sys.argv[1], int(sys.argv[2]))
+ reactor.run()
+
Oops, something went wrong.

0 comments on commit c32f72a

Please sign in to comment.