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
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() |
|
|
|
|
|
|
|
|