#!/usr/bin/python import threading import random import os.path import os import time import socket import asyncore import asynchat import cmd import ConfigParser import sys import blup.animation as Animation import blup.output as BlupOutput import blup.frame as Frame CONFIG_DEFAULTS = { 'output': 'shell', 'playlist': '', 'animation_dir': '.', 'enabled': False, 'shuffle': False, 'autorefresh': False } def find(f, list): for x in list: if f(x): return x class AnimationInfo(): def __init__(self, filename): self.__filename = filename self.__tags = None self.__valid = None @property def filesize(self): try: statinfo = os.stat(self.__filename) return statinfo.st_size except OSError: return -1 @property def mtime(self): try: statinfo = os.stat(self.__filename) return statinfo.st_mtime except OSError: return -1 @property def tags(self): return self.__tags @property def valid(self): if self.__valid == None: try: self.check() except Animation.AnimationFileError: pass return self.__valid @property def filename(self): return self.__filename def check(self): self.__valid = False self.__tags = None anim = Animation.load(self.__filename) self.__valid = True self.__tags = anim.tags class AnimationDatabase(): def __init__(self, animDir): self.__animDir = animDir self.__animInfos = [] self.__lock = threading.Lock() def update(self): with self.__lock: oldAnimInfos = self.__animInfos animInfos = [] for (dirpath, dirnames, filenames) in os.walk(self.__animDir): files = map(lambda f: os.path.join(dirpath, f), filenames) files = map(lambda f: os.path.relpath(os.path.join(dirpath, f), self.__animDir), filenames) animInfos.extend(map(lambda f: AnimationInfo(f), files)) #animInfos = filter(lambda a: a.valid, animInfos) newAnimInfos = [] for animInfo in animInfos: oldAnimInfo = find(lambda a: a.filename == animInfo.filename, oldAnimInfos) if oldAnimInfo is not None and oldAnimInfo.mtime == animInfo.mtime: newAnimInfos.append(oldAnimInfo) else: if animInfo.valid: newAnimInfos.append(animInfo) self.__animInfos = newAnimInfos def findAnimation(self, filename): with self.__lock: animInfo = find(lambda a: a.filename == filename, self.__animInfos) if animInfo is not None: return animInfo else: raise Exception('animation not found!') def containsAnimation(self, filename): with self.__lock: animInfo = find(lambda a: a.filename == filename, self.__animInfos) return (animInfo is not None) def getAllFilenames(self): with self.__lock: return map(lambda a: a.filename, self.__animInfos) #class Playlist(list): # def __init__(self, animationDatabase, filename=None): # self.__animDb = animationDatabase # self.__filename = filename # if filename == None: # self.append(self.__animDb.getAllFilenames()) # else: # f = open(filename, 'r') # lines = f.read().split('\n') # f.close() # # for line in lines: # if self.__animDb.containsAnimation(line): # self.append(line) # # def refresh(self): # #f = open(filename, 'r') # #lines = f.read().split('\n') # #f.close() # pass # # #for line in lines: # # if self.__animDb.containsAnimation(line): # # self.append(line) class PlayerThread(threading.Thread): def __init__(self, database, playlist, output): threading.Thread.__init__(self) self.__playlist = playlist self.__database = database self.__playNext = [] self.maxPlayNext = 3 self.__output = output self.shuffle = False self.autoRefresh = True self.loopTime = 10 self.gap = 800 self.__running = False self.__player = Animation.AnimationPlayer() self.__currentIndex = 0 self.__currentFile = '' @property def currentFile(self): return self.__currentFile def playNext(self, next): if len(self.__playNext) < self.__maxPlayNext: self.__playNext.append(next) else: raise Exception def refreshPlaylist(self): self.__database.update() self.__playlist = self.__database.getAllFilenames() def loadPlaylistFile(self): raise Exception('not yet implemented :(') def terminate(self): self.__player.stop() self.__running = False def run(self): self.__running = True while self.__running: anim = None if self.autoRefresh: # and self.__currentIndex == len(self.__playlist) - 1: self.refreshPlaylist() if len(self.__playNext) == 0: if len(self.__playlist) == 0: print 'busywait!!' continue if self.shuffle: self.__currentFile = self.__playlist[random.randint(0, len(self.__playlist) - 1)] else: if self.__currentIndex >= len(self.__playlist): self.__currentIndex = 0 self.__currentFile = self.__playlist[self.__currentIndex] self.__currentIndex += 1 else: self.__currentFile = self.__playNext.pop(0) anim = Animation.load(self.__currentFile) count = 1 if anim.tags.has_key('loop') and anim.tags['loop'].lower() in ['yes', 'true']: if anim.duration < self.loopTime * 1000: count = (self.loopTime * 1000) / anim.duration print 'playing:', anim print 'tags:', anim.tags self.__player.play(anim, self.__output, count=count) if self.gap > 0 and self.__running: self.__output.sendFrame(Frame.Frame(18,8)) time.sleep(self.gap / 1000.0) class PlayerControlClientHandler(asynchat.async_chat): def __init__(self, sock, player): asynchat.async_chat.__init__(self, sock=sock) self.set_terminator('\n') self.__buffer = '' self.__player = player def collect_incoming_data(self, data): self.__buffer += data def found_terminator(self): args = self.__buffer.split(' ') args = filter(lambda a: len(a) > 0, args) if len(args) == 0: return self.__buffer = '' cmd = args.pop(0) if cmd == 'refresh': self.__playlist.refresh() self.send('ok\n') elif cmd == 'status': self.send('playing %s\n' % (self.__player.getCurrentFile())) elif cmd == 'getplaylist': plist = '\n'.join(self.__playlist.getPlaylist()) self.send(plist + '\n') elif cmd == 'playnext' and len(args) == 1: try: self.__player.playNext(args[0]) self.send('ok\n') except Exception: self.send('error\n') elif cmd == 'loadplaylist' and len(args) == 1: try: if args[0] == '.': self.__playlist.loadDir() else: self.__playlist.loadFile(args[0]) self.send('ok\n') except: self.send('error\n') elif cmd == 'quit': self.send('bye\n') self.close() else: self.send('unknown command\n') class PlayerControlServer(asyncore.dispatcher): def __init__(self, host, port, player, playlist): asyncore.dispatcher.__init__(self) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() self.bind((host, port)) self.listen(5) self.__player = player self.__playlist = playlist def handle_accept(self): pair = self.accept() if pair is not None: sock, addr = pair print 'control connetcion from', addr PlayerControlClientHandler(sock, self.__player, self.__playlist) # # main # cfg = ConfigParser.SafeConfigParser(CONFIG_DEFAULTS) cfg.read('/var/tmp/blup2/blayer.cfg') try: out = BlupOutput.getOutput(cfg.get('output', 'output')) except ConfigParser.NoSectionError: sys.err.write('config error: missing \'output\' section') except ConfigParser.NoOptionError: sys.err.write('config error: missing \'output\' option in \'output\' section') try: animation_dir = cfg.get('player', 'animation_dir') except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): animation_dir = '.' db = AnimationDatabase(animation_dir) print 'updating database...' db.update() print 'done with updating database...' print 'updating database (again)...' db.update() print 'done with updating database...' #print db.getAllFilenames() try: playlist = Playlist(db, cfg.get('player', 'playlist')) print 'file-playluist:', playlist except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): playlist = Playlist(db) print 'db-playluist:', playlist blayer = PlayerThread(db, playlist, out) #sys.exit(0) try: blayer.setLoopTime(cfg.getint('player', 'looptime')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.setShuffle(cfg.getboolean('player', 'shuffle')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.setAutoRefresh(cfg.getboolean('player', 'autorefresh')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.setMaxPlayNext(cfg.getint('player', 'maxplaynext')) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass blayer.start() try: cser = PlayerControlServer(cfg.get('control', 'bind_host'), cfg.getint('control', 'bind_port'), blayer, playlist) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): cser = None try: if cser is not None: asyncore.loop() else: blayer.join() except KeyboardInterrupt: print 'nawk!!' blayer.terminate()