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.
429 lines
10 KiB
429 lines
10 KiB
8 years ago
|
#!/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()
|
||
|
|
||
|
|
||
|
|