Commit 2c19562d authored by Frank Scholz's avatar Frank Scholz
Browse files

adding a pyglet based Canvas - reflection and fade-transition still missing,

and make the grafics backend configurable via a config option
''<grafics>clutter|pyglet</grafics>''
parent d8bfa602
......@@ -273,12 +273,16 @@ if __name__ == '__main__':
if options['logfile'] == None:
config.get('logging').get('level','none')
try:
from twisted.internet import glib2reactor
glib2reactor.install()
except AssertionError:
print "error installing glib2reactor"
grafics = config.get('grafics')
if grafics == 'pyglet':
from cadre.extern import pygletreactor
pygletreactor.install()
else:
try:
from twisted.internet import glib2reactor
glib2reactor.install()
except AssertionError:
print "error installing glib2reactor"
from twisted.internet import reactor
......
......@@ -16,9 +16,10 @@ import mimetypes
mimetypes.init()
# Twisted
from twisted.internet import reactor
from twisted.internet import defer, reactor
from twisted.python.filepath import FilePath
# Coherence
from coherence.base import Coherence
from coherence.upnp.devices.media_renderer import MediaRenderer
......@@ -28,26 +29,25 @@ from coherence import log
from coherence.upnp.core.utils import means_true
from cadre.scribbling import Canvas
from cadre.renderer import CadreRenderer
def xmeans_true(value):
if isinstance(value,basestring):
value = value.lower()
return value in [True,1,'1','true','yes','ok']
class Cadre(log.Loggable):
logCategory = 'renderer'
def __init__(self,config):
self.config = config
fullscreen = 0
fullscreen = False
try:
if means_true(self.config['fullscreen']):
fullscreen = 1
fullscreen = True
except:
pass
grafics = self.config.get('grafics')
if grafics == 'pyglet':
from cadre.scribbling.pyglet_backend import Canvas
else:
from cadre.scribbling.clutter_backend import Canvas
self.canvas = Canvas(fullscreen)
try:
......@@ -144,13 +144,23 @@ class Cadre(log.Loggable):
self.content = []
if not isinstance( self.content, list):
self.content = [self.content]
self.content = set(map(os.path.abspath,self.content))
self.files = []
tmp_l = []
for path in self.content:
if path.startswith('http://'):
tmp_l.append(path)
else:
tmp_l.append(os.path.abspath(path))
self.content = tmp_l
self.items = []
self.playlist = []
self.warning("checking for files...")
self.warning("checking for items...")
for path in self.content:
self.walk(path)
if path.startswith('http://'):
self.items.append(path)
else:
self.walk(path)
self.warning("done")
self.renderer.av_transport_server.get_variable('AVTransportURI').subscribe(self.state_variable_change)
......@@ -160,23 +170,18 @@ class Cadre(log.Loggable):
#self.renderer.av_transport_server.get_variable('LastChange').subscribe(self.state_variable_change)
try:
if means_true(self.config['autostart']):
self.playlist = self.files[:]
if means_true(self.config['shuffle']):
random.shuffle(self.playlist)
file = self.playlist.pop()
try:
uri = "file://" + file.path
except:
uri = file
self.renderer.backend.stop()
self.renderer.backend.load(uri,'')
self.renderer.backend.play()
file = self.playlist.pop()
try:
uri = "file://" + file.path
except:
uri = file
self.renderer.backend.upnp_SetNextAVTransportURI(InstanceID='0',NextURI=uri,NextURIMetaData='')
d = defer.maybeDeferred(self.get_next_item)
d.addCallback(lambda result: self.set_renderer_uri(self.renderer,result[0],result[1]))
d.addErrback(self.got_error)
def get_next():
d = defer.maybeDeferred(self.get_next_item)
d.addCallback(lambda result: self.set_renderer_next_uri(self.renderer,result[0],result[1]))
d.addErrback(self.got_error)
d.addCallback(lambda result: get_next())
d.addErrback(self.got_error)
except KeyError:
pass
except:
......@@ -185,6 +190,10 @@ class Cadre(log.Loggable):
def quit(self):
reactor.stop()
def got_error(self,error):
self.warning("error %r" % error)
error.printTraceback()
def get_available_transitions(self):
try:
return self.canvas.get_available_transitions()
......@@ -195,13 +204,38 @@ class Cadre(log.Loggable):
if transition in self.get_available_transitions():
self.canvas.transition = transition
def get_next_item(self):
try:
uri = self.playlist.pop()
except IndexError:
self.playlist = self.items[:]
if means_true(self.config['shuffle']):
random.shuffle(self.playlist)
uri = self.playlist.pop()
try:
uri = "file://" + uri.path
return uri,''
except:
if uri.startswith('http://'):
pass
raise
def set_renderer_uri(self,renderer,uri,meta=''):
renderer.backend.stop()
renderer.backend.load(uri,meta)
renderer.backend.play()
def set_renderer_next_uri(self,renderer,uri,meta=''):
renderer.backend.upnp_SetNextAVTransportURI(InstanceID='0',NextURI=uri,NextURIMetaData=meta)
def walk(self, path):
containers = []
filepath = FilePath(path)
if filepath.isdir():
containers.append(filepath)
elif filepath.isfile():
self.files.append(FilePath(path))
self.items.append(FilePath(path))
while len(containers)>0:
container = containers.pop()
try:
......@@ -211,7 +245,7 @@ class Cadre(log.Loggable):
elif child.isfile() or child.islink():
mimetype,_ = mimetypes.guess_type(child.path, strict=False)
if mimetype and mimetype.startswith("image/"):
self.files.append(child)
self.items.append(child)
except UnicodeDecodeError:
self.warning("UnicodeDecodeError - there is something wrong with a file located in %r", container.get_path())
......@@ -222,18 +256,9 @@ class Cadre(log.Loggable):
print "media_server_removed", udn
def state_variable_change(self,variable):
print "state_variable %r changed from %r -> %r" % (variable.name,variable.old_value,variable.value)
self.warning("state_variable %r changed from %r -> %r" % (variable.name,variable.old_value,variable.value))
if variable.name == 'NextAVTransportURI':
if variable.value == '' and self.renderer.av_transport_server.get_variable('TransportState').value == 'TRANSITIONING':
try:
file = self.playlist.pop()
except IndexError:
self.playlist = self.files[:]
if means_true(self.config['shuffle']):
random.shuffle(self.playlist)
file = self.playlist.pop()
try:
uri = "file://" + file.path
except:
uri = file
self.renderer.backend.upnp_SetNextAVTransportURI(InstanceID='0',NextURI=uri,NextURIMetaData='')
d = defer.maybeDeferred(self.get_next_item)
d.addCallback(lambda result: self.set_renderer_next_uri(self.renderer,result[0],result[1]))
d.addErrback(self.got_error)
"""
This module provides Pyglet/Twisted integration using
the new (Pyglet v1.1) pyglet.app event loop.
To use this reactor, include the following statements
_before_ importing the standard Twisted reactor:
import pygletreactor
pygletreactor.install()
Then, just import reactor and call run() to start both
Pyglet and Twisted:
from twisted.internet import reactor
reactor.run()
There is no need to call pyglet.app.run().
If you want to subclass pyglet.app.EventLoop (Pyglet 1.1)
or pyglet.app.base.EventLoop (Pyglet 1.1.2), don't! Subclass
pygletreactor.EventLoop instead, which contains logic
to schedule Twisted events to run from Pyglet. Then,
register your new event loop as follows:
from twisted.internet import reactor
reactor.registerPygletEventLoop(yourEventLoop)
reactor.run()
Twisted function calls are scheduled within the Pyglet event
loop. By default, pending calls are dealt with every 0.1 secs.
This frequency can be altered by passing a different 'call_interval'
to reactor.run(), e.g. the following:
reactor.run(call_interval=1/20.)
will result in Twisted function calls being dealt with every
0.05 secs within the Pyglet event loop. If your code results in
a large number of Twisted calls that need to be processed as
quickly as possible, decreasing the call_interval will help.
Based on the wxPython reactor (wxreactor.py) that ships with Twisted.
Padraig Kitterick <p.kitterick@psych.york.ac.uk>
"""
import Queue
import pyglet
from twisted.python import log, runtime
from twisted.internet import _threadedselect
try:
# Pyglet 1.1.2
from pyglet.app.base import EventLoop
pyglet_event_loop = pyglet.app.base.EventLoop
except ImportError:
# Pyglet 1.1
pyglet_event_loop = pyglet.app.EventLoop
class EventLoop(pyglet_event_loop):
def __init__(self, twisted_queue=None, call_interval=1/10.):
"""Set up extra cruft to integrate Twisted calls."""
pyglet_event_loop.__init__(self)
if not hasattr(self, "clock"):
# This is not defined in Pyglet 1.1
self.clock = pyglet.clock.get_default()
if not twisted_queue is None:
self.register_twisted_queue(twisted_queue, call_interval)
def register_twisted_queue(self, twisted_queue, call_interval):
# The queue containing Twisted function references to call
self._twisted_call_queue = twisted_queue
# Schedule a method to deal with Twisted calls
self.clock.schedule_interval_soft(self._make_twisted_calls, call_interval)
def _make_twisted_calls(self, dt):
"""Check if we need to make function calls for Twisted."""
try:
# Deal with the next function call in the queue
f = self._twisted_call_queue.get(False)
f()
except Queue.Empty:
pass
class PygletReactor(_threadedselect.ThreadedSelectReactor):
"""
Pyglet reactor.
Twisted events are integrated into the Pyglet event loop.
"""
_stopping = False
def registerPygletEventLoop(self, eventloop):
"""Register the pygletreactor.EventLoop instance
if necessary, i.e. if you need to subclass it.
"""
self.pygletEventLoop = eventloop
def stop(self):
"""Stop Twisted."""
if self._stopping:
return
self._stopping = True
_threadedselect.ThreadedSelectReactor.stop(self)
def _runInMainThread(self, f):
"""Schedule Twisted calls within the Pyglet event loop."""
if hasattr(self, "pygletEventLoop"):
# Add the function to a queue which is called as part
# of the Pyglet event loop (see EventLoop above)
self._twistedQueue.put(f)
else:
# If Pyglet has stopped, add the events to a queue which
# is processed prior to shutting Twisted down.
self._postQueue.put(f)
def _stopPyglet(self):
"""Stop the pyglet event loop."""
if hasattr(self, "pygletEventLoop"):
self.pygletEventLoop.exit()
def run(self, call_interval=1/10., installSignalHandlers=True):
"""Start the Pyglet event loop and Twisted reactor."""
# Create a queue to hold Twisted events that will be executed
# before stopping Twisted in the event that Pyglet has been stopped.
self._postQueue = Queue.Queue()
self._twistedQueue = Queue.Queue()
if not hasattr(self, "pygletEventLoop"):
log.msg("No Pyglet event loop registered. Using the default.")
self.registerPygletEventLoop(EventLoop(self._twistedQueue, call_interval))
else:
self.pygletEventLoop.register_twisted_queue(self._twistedQueue, call_interval)
# Start the Twisted thread.
self.interleave(self._runInMainThread,
installSignalHandlers=installSignalHandlers)
# Add events to handle Pyglet/Twisted shutdown events
self.addSystemEventTrigger("after", "shutdown", self._stopPyglet)
self.addSystemEventTrigger("after", "shutdown",
lambda: self._postQueue.put(None))
self.pygletEventLoop.run()
# Now that the event loop has finished, remove
# it so that further Twisted events are added to
# the shutdown queue, and are dealt with below.
del self.pygletEventLoop
if not self._stopping:
# The Pyglet event loop is no longer running, so we monitor the
# queue containing Twisted events until all events are dealt with.
self.stop()
while 1:
try:
f = self._postQueue.get(timeout=0.01)
except Queue.Empty:
continue
else:
# 'None' on the queue signifies the last Twisted event.
if f is None:
break
try:
f()
except:
log.err()
def install():
"""
Setup Twisted+Pyglet integration based on the Pyglet event loop.
"""
reactor = PygletReactor()
from twisted.internet.main import installReactor
installReactor(reactor)
return reactor
__all__ = ['install']
......@@ -95,12 +95,12 @@ class Canvas(log.Loggable):
logCategory = 'canvas'
def __init__(self, fullscreen=1):
def __init__(self, fullscreen=True):
self.fullscreen = fullscreen
self.transition = 'FADE'
self.stage = clutter.Stage()
if self.fullscreen == 1:
if self.fullscreen == True:
self.stage.set_fullscreen(True)
else:
self.stage.set_size(1200, 800)
......@@ -112,7 +112,7 @@ class Canvas(log.Loggable):
display_height = size[1]*0.7
self.stage.set_color(clutter.Color(0,0,0))
if self.fullscreen == 1:
if self.fullscreen == True:
self.stage.connect('button-press-event', lambda x,y: reactor.stop())
self.stage.connect('destroy', lambda x: reactor.stop())
#self.stage.connect('key-press-event', self.process_key)
......
# Licensed under the MIT license
# http://opensource.org/licenses/mit-license.php
# Copyright 2009 Frank Scholz <coherence@beebits.net>
import os
# Twisted
from twisted.internet import reactor
from coherence import log
# Pyglet
import pyglet
class Actor(object):
def __init__(self,drawable=None):
self.drawable = drawable
def replace(self,drawable):
self.drawable = drawable
class Canvas(log.Loggable):
logCategory = 'canvas'
def __init__(self, fullscreen=True):
self.fullscreen = fullscreen
self.transition = 'FADE'
self.parts = []
self.stage = pyglet.window.Window()
if self.fullscreen == True:
self.stage.set_fullscreen(True)
else:
self.stage.set_size(1200, 800)
self.width,self.height = self.stage.get_size()
self.display_width = int(self.width*0.7)
self.display_height = int(self.height*0.7)
self.display_pos_x = float((self.width - self.display_width) / 2)
self.in_texture = Actor(None)
self.out_texture = Actor(None)
self.parts.append(self.in_texture)
self.parts.append(self.out_texture)
@self.stage.event
def on_draw():
self.stage.clear()
for part in self.parts:
if part.drawable != None:
part.drawable.draw()
def set_title(self,title):
self.stage.set_caption(title)
def process_key(self,stage,event):
print "process_key", stage,event
def get_available_transitions(self):
return [str(x.replace('_transition_','')) for x in dir(self) if x.startswith('_transition_')]
def _transition_NONE(self):
self.out_texture.replace(None)
self.out_texture,self.in_texture = self.in_texture,self.out_texture
def load_the_new_one(self,image,title):
self.warning("show image %r" % title)
if image.startswith("file://"):
filename = image[7:]
else:
#FIXME - we have the image as data already, there has to be
# a better way to get it into the texture
from tempfile import mkstemp
fp,filename = mkstemp()
os.write(fp,image)
os.close(fp)
remove_file_after_loading = True
self.warning("loading image from file %r" % filename)
pic = pyglet.image.load(filename)
new_in_texture = pyglet.sprite.Sprite( pic, self.display_pos_x,self.height-self.display_height-20)
#print "sprite",new_in_texture.width,new_in_texture.height,new_in_texture.scale
pic_width,pic_height = new_in_texture.width,new_in_texture.height
new_in_texture.scale = float(self.display_height)/float(pic.height)
#print "after h_scale",new_in_texture.width,new_in_texture.height,new_in_texture.scale
if new_in_texture.width > self.display_width:
new_in_texture.scale = float(self.display_width)/float(pic.width)
#print "after w_scale",new_in_texture.width,new_in_texture.height,new_in_texture.scale
new_in_texture.set_position((self.width-new_in_texture.width)/2,self.height-new_in_texture.height-20)
self.in_texture.replace(new_in_texture)
self.set_title(title)
try:
if remove_file_after_loading:
os.unlink(filename)
except:
pass
def show_image(self,image,title=''):
self.load_the_new_one(image,title)
function = getattr(self, "_transition_%s" % self.transition, None)
if function:
function()
return
self._transition_NONE()
def add_overlay(self,overlay):
pass
\ No newline at end of file
......@@ -2,6 +2,7 @@
<logging level="warning" />
<name>Cadre - Coherence Picture-Frame</name>
<content>/path/to/images/here</content>
<grafics>clutter</grafics>
<autostart>yes</autostart>
<shuffle>yes</shuffle>
<fullscreen>no</fullscreen>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment