You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
10 KiB

#!/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()