forked from Blinkenbunt/blup
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.
380 lines
8.8 KiB
380 lines
8.8 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 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()
|
||
|
|
||
|
|
||
|
|