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.

379 lines
8.8 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 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()