2014-04-17 14:25:43 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
2014-04-24 07:33:43 +00:00
# This file is part of chaosc and psychosis
#
# chaosc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# chaosc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with chaosc. If not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2014 Stefan Kögl
from __future__ import absolute_import
import logging
2014-04-27 13:45:00 +00:00
import os
2014-04-24 07:33:43 +00:00
import os . path
2014-04-23 14:31:19 +00:00
import Queue
import re
2014-04-24 07:33:43 +00:00
import select
import socket
import sys
import threading
import time
2014-04-28 05:20:28 +00:00
from datetime import datetime
from BaseHTTPServer import BaseHTTPRequestHandler , HTTPServer
from chaosc . argparser_groups import *
from chaosc . lib import logger , resolve_host
from PyQt4 import QtCore , QtGui
from PyQt4 . QtCore import QBuffer , QByteArray , QIODevice
from dump_grabber . dump_grabber_ui import Ui_MainWindow
2014-04-23 14:31:19 +00:00
try :
from chaosc . c_osc_lib import OSCMessage , decode_osc
except ImportError as e :
from chaosc . osc_lib import OSCMessage , decode_osc
2014-04-27 13:45:00 +00:00
app = QtGui . QApplication ( [ ] )
2014-04-24 07:33:43 +00:00
2014-04-27 13:45:00 +00:00
class TextStorage ( object ) :
def __init__ ( self , columns ) :
super ( TextStorage , self ) . __init__ ( )
self . column_count = columns
self . colors = ( QtCore . Qt . red , QtCore . Qt . green , QtGui . QColor ( 46 , 100 , 254 ) )
def init_columns ( self ) :
raise NotImplementedError ( )
def add_text ( self , column , text ) :
raise NotImplementedError ( )
class ColumnTextStorage ( TextStorage ) :
def __init__ ( self , columns , default_font , column_width , line_height , scene ) :
super ( ColumnTextStorage , self ) . __init__ ( columns )
self . columns = list ( )
self . default_font = default_font
self . column_width = column_width
self . line_height = line_height
self . graphics_scene = scene
self . num_lines , self . offset = divmod ( 775 , self . line_height )
def init_columns ( self ) :
for x in range ( self . column_count ) :
column = list ( )
color = self . colors [ x ]
for y in range ( self . num_lines ) :
text_item = self . graphics_scene . addSimpleText ( " %d : %d " % ( x , y ) , self . default_font )
text_item . setBrush ( color )
text_item . setPos ( x * self . column_width , y * self . line_height )
column . append ( text_item )
self . columns . append ( column )
def add_text ( self , column , text ) :
text_item = self . graphics_scene . addSimpleText ( text , self . default_font )
color = self . colors [ column ]
text_item . setBrush ( color )
old_item = self . columns [ column ] . pop ( 0 )
self . graphics_scene . removeItem ( old_item )
self . columns [ column ] . append ( text_item )
for iy , text_item in enumerate ( self . columns [ column ] ) :
text_item . setPos ( column * self . column_width , iy * self . line_height )
class ExclusiveTextStorage ( TextStorage ) :
def __init__ ( self , columns , default_font , column_width , line_height , scene ) :
super ( ExclusiveTextStorage , self ) . __init__ ( columns )
self . column_count = columns
self . lines = list ( )
self . default_font = default_font
self . column_width = column_width
self . line_height = line_height
self . graphics_scene = scene
self . num_lines , self . offset = divmod ( 775 , self . line_height )
def init_columns ( self ) :
color = self . colors [ 0 ]
for y in range ( self . num_lines ) :
text_item = self . graphics_scene . addSimpleText ( " " , self . default_font )
text_item . setBrush ( color )
text_item . setPos ( 0 , y * self . line_height )
self . lines . append ( text_item )
def add_text ( self , column , text ) :
text_item = self . graphics_scene . addSimpleText ( text , self . default_font )
text_item . setX ( column * self . column_width )
color = self . colors [ column ]
text_item . setBrush ( color )
old_item = self . lines . pop ( 0 )
self . graphics_scene . removeItem ( old_item )
self . lines . append ( text_item )
for iy , text_item in enumerate ( self . lines ) :
text_item . setY ( iy * self . line_height )
class MainWindow ( QtGui . QMainWindow , Ui_MainWindow ) :
def __init__ ( self , parent = None , columns = 3 , column_exclusive = False ) :
2014-04-17 14:25:43 +00:00
super ( MainWindow , self ) . __init__ ( parent )
self . setupUi ( self )
self . graphics_view . setHorizontalScrollBarPolicy ( QtCore . Qt . ScrollBarAlwaysOff )
self . graphics_view . setVerticalScrollBarPolicy ( QtCore . Qt . ScrollBarAlwaysOff )
self . graphics_view . setRenderHint ( QtGui . QPainter . Antialiasing , True )
self . graphics_view . setFrameStyle ( QtGui . QFrame . NoFrame )
self . graphics_scene = QtGui . QGraphicsScene ( self )
self . graphics_scene . setSceneRect ( 0 , 0 , 775 , 580 )
self . graphics_view . setScene ( self . graphics_scene )
2014-04-23 17:19:05 +00:00
self . default_font = QtGui . QFont ( " Monospace " , 14 )
2014-04-17 14:25:43 +00:00
self . default_font . setStyleHint ( QtGui . QFont . Monospace )
self . default_font . setBold ( True )
2014-04-27 13:45:00 +00:00
self . graphics_scene . setFont ( self . default_font )
2014-04-17 14:25:43 +00:00
self . font_metrics = QtGui . QFontMetrics ( self . default_font )
self . line_height = self . font_metrics . height ( )
2014-04-23 15:46:14 +00:00
self . column_width = 775 / columns
2014-04-17 14:25:43 +00:00
2014-04-27 13:45:00 +00:00
self . text_storage = ExclusiveTextStorage ( columns , self . default_font , self . column_width , self . line_height , self . graphics_scene )
#self.text_storage = ColumnTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene)
self . text_storage . init_columns ( )
2014-04-17 14:25:43 +00:00
2014-04-23 14:31:19 +00:00
def add_text ( self , column , text ) :
2014-04-27 13:45:00 +00:00
self . text_storage . add_text ( column , text )
2014-04-23 14:31:19 +00:00
def render ( self ) :
2014-04-27 13:45:00 +00:00
image = QtGui . QImage ( 768 , 576 , QtGui . QImage . Format_ARGB32_Premultiplied )
2014-04-23 14:31:19 +00:00
image . fill ( QtCore . Qt . black )
painter = QtGui . QPainter ( image )
2014-04-27 13:45:00 +00:00
painter . setRenderHints ( QtGui . QPainter . RenderHint ( QtGui . QPainter . Antialiasing | QtGui . QPainter . TextAntialiasing ) , True )
2014-04-23 14:31:19 +00:00
painter . setFont ( self . default_font )
self . graphics_view . render ( painter , target = QtCore . QRectF ( 0 , 0 , 768 , 576 ) , source = QtCore . QRect ( 0 , 0 , 768 , 576 ) )
2014-04-27 13:45:00 +00:00
painter . end ( )
2014-04-23 14:31:19 +00:00
return image
class OSCThread ( threading . Thread ) :
def __init__ ( self , args ) :
super ( OSCThread , self ) . __init__ ( )
self . args = args
self . running = True
self . client_address = resolve_host ( args . client_host , args . client_port , args . address_family )
self . chaosc_address = chaosc_host , chaosc_port = resolve_host ( args . chaosc_host , args . chaosc_port , args . address_family )
self . osc_sock = socket . socket ( args . address_family , 2 , 17 )
self . osc_sock . bind ( self . client_address )
self . osc_sock . setblocking ( 0 )
2014-04-24 07:33:43 +00:00
logger . info ( " starting up osc receiver on ' %s : %d ' " , self . client_address [ 0 ] , self . client_address [ 1 ] )
2014-04-23 14:31:19 +00:00
self . subscribe_me ( )
def subscribe_me ( self ) :
2014-04-24 07:33:43 +00:00
logger . info ( " %s : subscribing to ' %s : %d ' with label %r " , datetime . now ( ) . strftime ( " %x %X " ) , self . chaosc_address [ 0 ] , self . chaosc_address [ 1 ] , self . args . subscriber_label )
2014-04-23 14:31:19 +00:00
msg = OSCMessage ( " /subscribe " )
msg . appendTypedArg ( self . client_address [ 0 ] , " s " )
msg . appendTypedArg ( self . client_address [ 1 ] , " i " )
msg . appendTypedArg ( self . args . authenticate , " s " )
if self . args . subscriber_label is not None :
msg . appendTypedArg ( self . args . subscriber_label , " s " )
self . osc_sock . sendto ( msg . encode_osc ( ) , self . chaosc_address )
def unsubscribe_me ( self ) :
if self . args . keep_subscribed :
return
2014-04-24 07:33:43 +00:00
logger . info ( " unsubscribing from ' %s : %d ' " , self . chaosc_address [ 0 ] , self . chaosc_address [ 1 ] )
2014-04-23 14:31:19 +00:00
msg = OSCMessage ( " /unsubscribe " )
msg . appendTypedArg ( self . client_address [ 0 ] , " s " )
msg . appendTypedArg ( self . client_address [ 1 ] , " i " )
msg . appendTypedArg ( self . args . authenticate , " s " )
self . osc_sock . sendto ( msg . encode_osc ( ) , self . chaosc_address )
def run ( self ) :
while self . running :
try :
2014-04-23 15:46:14 +00:00
reads , writes , errs = select . select ( [ self . osc_sock ] , [ ] , [ ] , 0.01 )
2014-04-23 14:31:19 +00:00
except Exception , e :
pass
else :
if reads :
try :
osc_input , address = self . osc_sock . recvfrom ( 8192 )
osc_address , typetags , messages = decode_osc ( osc_input , 0 , len ( osc_input ) )
2014-04-23 17:19:05 +00:00
queue . put_nowait ( ( osc_address , messages ) )
2014-04-23 14:31:19 +00:00
except Exception , e :
2014-04-24 07:33:43 +00:00
pass
2014-04-23 14:31:19 +00:00
else :
2014-04-23 17:19:05 +00:00
pass
2014-04-23 14:31:19 +00:00
self . unsubscribe_me ( )
2014-04-24 07:33:43 +00:00
logger . info ( " OSCThread is going down " )
2014-04-23 14:31:19 +00:00
queue = Queue . Queue ( )
class MyHandler ( BaseHTTPRequestHandler ) :
def do_GET ( self ) :
try :
self . path = re . sub ( ' [^.a-zA-Z0-9] ' , " " , str ( self . path ) )
if self . path == " " or self . path == None or self . path [ : 1 ] == " . " :
self . send_error ( 403 , ' Forbidden ' )
if self . path . endswith ( " .html " ) :
directory = os . path . dirname ( os . path . abspath ( __file__ ) )
data = open ( os . path . join ( directory , self . path ) , " rb " ) . read ( )
self . send_response ( 200 )
self . send_header ( ' Content-type ' , ' text/html ' )
self . end_headers ( )
self . wfile . write ( data )
elif self . path . endswith ( " .mjpeg " ) :
self . thread = thread = OSCThread ( self . server . args )
thread . daemon = True
thread . start ( )
window = MainWindow ( )
2014-04-23 15:46:14 +00:00
window . hide ( )
2014-04-23 14:31:19 +00:00
self . send_response ( 200 )
self . send_header ( " Content-Type " , " multipart/x-mixed-replace; boundary=--aaboundary " )
self . end_headers ( )
event_loop = QtCore . QEventLoop ( )
2014-04-27 13:45:00 +00:00
last_frame = time . time ( ) - 1.
frame_rate = 16.0
frame_length = 1. / frame_rate
2014-04-28 05:20:28 +00:00
regex = re . compile ( " ^/(uwe|merle|bjoern)/(.*?)$ " )
2014-04-23 14:31:19 +00:00
while 1 :
event_loop . processEvents ( )
app . sendPostedEvents ( None , 0 )
while 1 :
try :
osc_address , args = queue . get_nowait ( )
2014-04-28 05:20:28 +00:00
print osc_address
2014-04-23 14:31:19 +00:00
except Queue . Empty :
break
else :
2014-04-27 13:45:00 +00:00
try :
actor , text = regex . match ( osc_address ) . groups ( )
if actor == " merle " :
window . add_text ( 0 , " %s = %s " % ( text , " , " . join ( [ str ( i ) for i in args ] ) ) )
if actor == " uwe " :
window . add_text ( 1 , " %s = %s " % ( text , " , " . join ( [ str ( i ) for i in args ] ) ) )
if actor == " bjoern " :
window . add_text ( 2 , " %s = %s " % ( text , " , " . join ( [ str ( i ) for i in args ] ) ) )
except AttributeError :
pass
now = time . time ( )
delta = now - last_frame
if delta > frame_length :
last_frame = now
img = window . render ( )
buffer = QBuffer ( )
buffer . open ( QIODevice . WriteOnly )
img . save ( buffer , " JPG " )
JpegData = buffer . data ( )
self . wfile . write ( " --aaboundary \r \n Content-Type: image/jpeg \r \n Content-length: %d \r \n \r \n %s \r \n \r \n \r \n " % ( len ( JpegData ) , JpegData ) )
JpegData = None
buffer = None
img = None
time . sleep ( 0.01 )
2014-04-23 14:31:19 +00:00
return
except ( KeyboardInterrupt , SystemError ) :
if hasattr ( self , " thread " ) and self . thread is not None :
self . thread . running = False
self . thread . join ( )
self . thread = None
except IOError , e :
if e [ 0 ] in ( 32 , 104 ) :
if hasattr ( self , " thread " ) and self . thread is not None :
self . thread . running = False
self . thread . join ( )
self . thread = None
else :
2014-04-24 07:33:43 +00:00
pass
2014-04-23 14:31:19 +00:00
class JustAHTTPServer ( HTTPServer ) :
pass
2014-04-17 14:25:43 +00:00
def main ( ) :
2014-04-23 15:58:50 +00:00
arg_parser = ArgParser ( " dump_grabber " )
2014-04-23 14:31:19 +00:00
arg_parser . add_global_group ( )
client_group = arg_parser . add_client_group ( )
arg_parser . add_argument ( client_group , ' -x ' , " --http_host " , default = " :: " ,
help = ' my host, defaults to " :: " ' )
2014-04-24 07:33:43 +00:00
arg_parser . add_argument ( client_group , ' -X ' , " --http_port " , default = 9001 ,
type = int , help = ' my port, defaults to 9001 ' )
2014-04-23 14:31:19 +00:00
arg_parser . add_chaosc_group ( )
arg_parser . add_subscriber_group ( )
args = arg_parser . finalize ( )
http_host , http_port = resolve_host ( args . http_host , args . http_port , args . address_family )
server = JustAHTTPServer ( ( http_host , http_port ) , MyHandler )
server . address_family = args . address_family
server . args = args
2014-04-24 07:33:43 +00:00
logger . info ( " starting up http server on ' %s : %d ' " , http_host , http_port )
2014-04-17 14:25:43 +00:00
2014-04-24 07:33:43 +00:00
server . serve_forever ( )
2014-04-17 14:25:43 +00:00
if ( __name__ == ' __main__ ' ) :
main ( )