#!/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 json 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, output): threading.Thread.__init__(self) self.__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 = '' self.__paused = False @property def currentFile(self): if not self.__paused: return self.__currentFile else: return "" @property def currentFileInfo(self): if not self.__paused: return self.database.findAnimation(self.__currentFile) else: # TODO: mhh... return None @property def paused(self): return self.__paused def playNext(self, next): if len(self.__playNext) < self.maxPlayNext: self.__playNext.append(next) else: raise Exception def next(self): self.__player.stop() def togglePaused(self): self.__paused = not self.__paused if self.__paused: self.__player.stop() 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) print 'elapsed: ', self.__player.elapsed if self.__paused: # TODO: use correct frame size self.__output.sendFrame(Frame.Frame(18,8)) while self.__paused: time.sleep(1) print 'busywait!!' if self.gap > 0 and self.__running: # TODO: use correct frame size 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 sendPacket(self, data): self.send(json.dumps(data) + '\n') def found_terminator(self): try: data = json.loads(self.__buffer) except ValueError: self.sendPacket({'error': 'invalid json'}) return finally: self.__buffer = '' try: cmd = data['cmd'] except (TypeError, KeyError): self.sendPacket({'error': 'no command given'}) return if cmd == 'refresh': self.__player.refreshPlaylist() self.sendPacket({'refresh': 'ok'}) elif cmd == 'next': self.__player.next() self.sendPacket({'next': 'ok'}) elif cmd == 'togglepaused': self.__player.togglePaused() self.sendPacket({'togglepaused': 'ok', 'paused': self.__player.paused}) elif cmd == 'getpaused': self.sendPacket({'paused': self.__player.paused}) elif cmd == 'playnext': if data.has_key('filename') and type(data['filename']) in [str, unicode]: try: self.__player.playNext(data['filename']) except Exception: self.sendPacket({'error':'too many animations queued'}) else: self.sendPacket({'playnext':'ok'}) else: self.sendPacket({'error': 'no or invalid animation file'}) elif cmd == 'getallfilenames': self.sendPacket({'allfilenames': self.__player.database.getAllFilenames()}) elif cmd == 'getcurrentfileinfo': if not self.__player.paused: info = self.__player.currentFileInfo.tags else: info = {} self.sendPacket({'currentfileinfo': info}) elif cmd == 'getcurrentfilename': self.sendPacket({'currentfilename': self.__player.currentFile}) else: self.sendPacket({'error': 'unknown command'}) class PlayerControlServer(asyncore.dispatcher): def __init__(self, host, port, player): 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 def handle_accept(self): pair = self.accept() if pair is not None: sock, addr = pair print 'control connetcion from', addr PlayerControlClientHandler(sock, self.__player) # # 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, out) blayer.refreshPlaylist() #sys.exit(0) try: blayer.loopTime = cfg.getint('player', 'looptime') except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.shuffle = cfg.getboolean('player', 'shuffle') except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.autoRefresh = cfg.getboolean('player', 'autorefresh') except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): pass try: blayer.maxPlayNext = 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) except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): cser = None try: if cser is not None: asyncore.loop() else: blayer.join() except KeyboardInterrupt: print 'nawk!!' blayer.terminate()