diff --git a/libsaria/sources/__init__.py b/libsaria/sources/__init__.py new file mode 100644 index 00000000..f1f621d7 --- /dev/null +++ b/libsaria/sources/__init__.py @@ -0,0 +1,63 @@ +# Bryan Schumaker (8/8/2010) + +__all__ = ["collection"] + +import libsaria +import tagpy +call = libsaria.event.call +exists = libsaria.path.exists +expand = libsaria.path.expand +FileRef = tagpy.FileRef + +import library +import playlist + +file_to_id = library.file_to_id +play_id = library.play_id +get_attrs = library.get_attrs +reset = library.reset +inc_score = library.inc_score +inc_count = library.inc_count + +def init(): + libsaria.init_pref("random", False) +libsaria.event.invite("POSTINIT", init) + +def toggle_rand(): + libsaria.prefs["random"] = not libsaria.prefs["random"] + +cur_lib_id = -1 + +def new_source(path, bg=True): + global library + path = expand(path) + if not exists(path): + return 0 + return call("NEWSOURCE", library.scan, path) + +def lib_get_cur_id(): + global cur_lib_id + return cur_lib_id + +def plist_refresh(): + return call("PLISTREFRESH") + +def change_score(): + prcnt = libsaria.audio.get_progress() + if prcnt > 0.75: + inc_count(cur_lib_id) + inc_score(cur_lib_id) + if prcnt < 0.33: + inc_score(cur_lib_id, -1) + +def plist_next(): + change_score() + playlist.next() + +def catch_eos(*args): + inc_count(cur_lib_id) + inc_score(cur_lib_id) + playlist.next() +libsaria.event.invite("POSTEOS", catch_eos) + + diff --git a/libsaria/sources/index.py b/libsaria/sources/index.py new file mode 100644 index 00000000..e0307149 --- /dev/null +++ b/libsaria/sources/index.py @@ -0,0 +1,70 @@ +# Bryan Schumaker (8/10/2010) + +import re + +translate = unicode.translate +split = unicode.split +space_ord = ord(" ") +stripc = u"\"#$%&'*+<=>@[]^`{|}~.?!" +splitc = u"-\/,:;()_~+" + +ttable = None + +def format_once(text): + import string + global ttable + upper = string.uppercase + lower = string.lowercase + + ttable = dict((ord(c),None) for c in stripc) + splitt = dict((ord(c),space_ord) for c in splitc) + lowert = dict((ord(c),ord(lower[i])) for i,c in enumerate(upper)) + for t in (splitt, lowert): + for c in t: + ttable[c] = t[c] + format = format_rest + return format_rest(text) +def format_rest(text): + return text.translate(ttable).split() +format = format_once + + +class Index(dict): + def __init__(self): + dict.__init__(self) + + def insert(self, id, tags): + get = self.get + for tag in tags: + for word in format(tag): + ids = get(word, None) + if ids == None: + self[word] = set([id]) + else: + ids.add(id) + + def filter(self, text): + text = unicode(text) + terms = format(text) + results = dict() + + search = re.search + get = self.get + + for t in terms: + results[t] = set() + + for key in self.keys(): + for term in terms: + if search(term, key): + results[term].update(get(key)) + + visible = set() + for i,t in enumerate(terms): + if i == 0: + visible.update(results[t]) + else: + visible.intersection_update(results[t]) + return visible + + diff --git a/libsaria/sources/library.py b/libsaria/sources/library.py new file mode 100644 index 00000000..fcf6f69d --- /dev/null +++ b/libsaria/sources/library.py @@ -0,0 +1,195 @@ +# Bryan Schumaker (11/05/2010) + +import os +import tagpy +import libsaria +import string +from track import Track +splitext = libsaria.path.splitext + +fs_tree = None +tag_tree = None +index = None +tracks = None +sources = None +size = None +visible = None + +FileRef = tagpy.FileRef + +filtered = False +badfiles = set() + +ttable = dict() +for s in string.punctuation: + ttable[ord(s)] = u"" +for s in string.lowercase: + ttable[ord(s)] = ord(s) - 32 + +def reset(): + from libsaria.trees import FSTree, DLFSTree, DLValTree + from index import Index + + global index + global tracks + global fs_tree + global tag_tree + global sources + global visible + + sources = FSTree() + + fs_tree = DLFSTree() + tag_tree = DLValTree() + index = Index() + tracks = dict() + size = 0 + visible = set() + +def load(): + global fs_tree + global tag_tree + global index + global tracks + global sources + objects = libsaria.data.load("library", ".lib") + if objects == None or len(objects) != 5: + reset() + return + (sources, tracks, fs_tree, tag_tree, index) = objects + libsaria.event.start("POSTLIBLOAD") + +def save(): + global sources + libsaria.data.save( (sources, tracks, fs_tree, tag_tree, index), + "library", ".lib") + +def walk(): + for tag in tag_tree.walk(): + yield tag[3] + +def file_to_id(file): + return os.stat(file).st_ino + +def get_attrs(id, *attrs): + res = [] + rec = tracks.get(id, None) + if rec == None: + return [0] * len(attrs) + + get = rec.__dict__.get + tags = rec.tags.walk_vals_backwards() + for attr in attrs: + if attr == "id": + res += [id] + elif attr == "filepath": + res += [rec.fs.walk_path_backwards()] + elif attr == "art": + from libsaria import lastfm + res += [lastfm.get_artwork_id(id)] + elif attr == "artist": + res += [tags[0]] + elif attr == "album": + res += [tags[1]] + elif attr == "title": + res += [tags[2]] + else: + res += [get(attr, None)] + return res + +def inc_score(id, amount=1): + rec = tracks.get(id, None) + if rec: + rec.score += amount + save() + +def inc_count(id): + rec = tracks.get(id, None) + if rec: + rec.count += 1 + save() + +def play_id(id): + libsaria.collection.cur_lib_id = id + libsaria.audio.load(get_attrs(id, "filepath")[0]) + libsaria.audio.play() + +def filter(text): + global visible + global index + global filtered + + if len(text) > 0: + visible = index.filter(text) + filtered = True + else: + visible = None + filtered = False + +def test_filter(text): + return index.filter(text) + +def is_visible(id): + global filtered + global visible + if filtered == False: + return True + else: + return id in visible + +def add_source(path): + global sources + sources.insert_path(path) + +def scan(path): + add_source(path) + update() + save() + +def insert_track(path, ref): + global tracks + global ttable + global fs_tree + global tag_tree + global index + tags = ref.tag() + audio = ref.audioProperties() + ino = os.stat(path).st_ino + track = Track(tags, audio) + artist = tags.artist or u"Unknown Artist" + album = tags.album or u"Unknown Album" + title = tags.title or u"Unknown Title" + fs = fs_tree.insert_path(path) + tag = tag_tree.insert( [artist.translate(ttable), + album.translate(ttable), + title.translate(ttable), ino], + [artist, album, title, ino] + ) + + index.insert(ino, (artist, album, title)) + track.fs = fs + track.tags = tag + tracks[ino] = track + +def update_path(path): + global badfiles + tree = libsaria.path.make_tree(path) + for path in tree.walk_paths(): + ext = splitext(path)[1] + if ext in badfiles: + continue + try: + ref = FileRef(path) + except: + badfiles.add(ext) + continue + try: + insert_track(path, ref) + except Exception, e: + print e + +def update(): + global sources + sep = libsaria.path.sep + for path in sources.walk_paths(): + update_path(path) diff --git a/libsaria/sources/playlist.py b/libsaria/sources/playlist.py new file mode 100644 index 00000000..e8668078 --- /dev/null +++ b/libsaria/sources/playlist.py @@ -0,0 +1,115 @@ +# Bryan Schumaker (11/07/2010) + +import random as rand + +import libsaria +from libsaria.sources import library +call = libsaria.event.call + +song_list = None +song_set = None + +filtered = False +visible = None +recent = [] +recent_msg = "Skipping %s by %s because it has played recently" +skip_msg = "Skipping %s by %s because I don't think you like it" +cur_index = -1 + +def add_id(id): + global song_list + global song_set + song_list.append(id) + song_set.add(id) + +def reset(): + global song_list + global song_set + song_list = [] + song_set = set() + +def load(): + global song_list + global song_set + global visible + song_list = libsaria.data.load("playlist", ".list") + if song_list == None: + reset() + return + song_set = set(song_list) + +def save(): + libsaria.data.save(song_list, "playlist", ".list") + +def walk(): + global song_list + for id in song_list: + yield id + +def filter(text): + global visible + global song_set + global filtered + if len(text) > 0: + visible = song_set.intersection(library.test_filter(text)) + filtered = True + else: + visible = song_set + filtered = False + +def is_visible(id): + global visible + global filtered + if filtered == True: + return id in visible + return True + +def num_visible(): + if filtered == True: + return len(visible) + return len(song_list) + +def seq_next(): + global cur_index + global song_list + cur_index += 1 + while is_visible(song_list[cur_index]) == False: + cur_index += 1 + return song_list[cur_index] + +def rand_candidate(max): + index = rand.randint(0, max-1) + if filtered == False: + return song_list[index] + return list(visible)[index] + +def rand_next(): + n = num_visible() + if n == 0: + return + get_attrs = library.get_attrs + for i in xrange(15): + id = rand_candidate(n) + (artist, title, score) = get_attrs(id, "artist", "title", "score") + if (artist, title) in recent: + print recent_msg % (artist, title) + continue + if score < 0: + do_play = rand.randint(0, 100) + if do_play < ((20 * score) + 100): + print skip_msg % (artist, title) + continue + recent.append((artist, title)) + if len(recent) > 50: + recent.pop(0) + if i > 0: + print "Picking a song took %s iterations" % i + return id + +def next(): + if libsaria.prefs["random"] == True: + id = rand_next() + else: + id = seq_next() + if id != None: + return call("NEXT", library.play_id, id) diff --git a/libsaria/sources/table.py b/libsaria/sources/table.py new file mode 100644 index 00000000..1a2ff56b --- /dev/null +++ b/libsaria/sources/table.py @@ -0,0 +1,13 @@ +# Bryan Schumaker (8/8/2010) + +class Table(dict): + def __init__(self): + dict.__init__(self) + self.next_id = 0 + + + def insert(self, tags): + id = self.next_id + self[id] = tags + self.next_id += 1 + return id diff --git a/libsaria/sources/track.py b/libsaria/sources/track.py new file mode 100644 index 00000000..f3de3970 --- /dev/null +++ b/libsaria/sources/track.py @@ -0,0 +1,24 @@ +# Bryan Schumaker (11/09/2010) + +import datetime +timedelta = datetime.timedelta + +class Track: + def __init__(self, tags, audio): + self.score = 0 + self.count = 0 + self.genre = tags.genre or u"Unknown Genre" + self.track = tags.track + self.year = tags.year + self.rate = audio.bitrate + self.channel = audio.channels + self.seconds = audio.length + self.sample = audio.sampleRate + lenstr = "%s" % timedelta(seconds=self.seconds) + if lenstr[0] == "0" and lenstr[1] == ":": + self.lenstr = lenstr[2:] + else: + self.lenstr = lenstr + + self.fs = None + self.tags = None