much rewrite, very kane :)
This commit is contained in:
parent
05745e3c52
commit
80d6aea666
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="icon" type="image/png" href="/icon.png" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<img src="/texter_%d.mjpeg" alt="Smiley face" />
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -23,8 +23,9 @@ from __future__ import absolute_import
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from collections import deque
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from chaosc.argparser_groups import *
|
from chaosc.argparser_groups import *
|
||||||
from chaosc.lib import logger, resolve_host
|
from chaosc.lib import logger, resolve_host
|
||||||
|
@ -33,7 +34,8 @@ from PyQt4.QtCore import QBuffer, QByteArray, QIODevice
|
||||||
from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress
|
from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress
|
||||||
|
|
||||||
from dump_grabber.dump_grabber_ui import Ui_MainWindow
|
from dump_grabber.dump_grabber_ui import Ui_MainWindow
|
||||||
from psylib.mjpeg_streaming_server import MjpegStreamingServer
|
from psylib.mjpeg_streaming_server import *
|
||||||
|
from psylib.psyqt_base import PsyQtChaoscClientBase
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from chaosc.c_osc_lib import OSCMessage, decode_osc
|
from chaosc.c_osc_lib import OSCMessage, decode_osc
|
||||||
|
@ -42,62 +44,17 @@ except ImportError as e:
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
class TextStorage(object):
|
class ExclusiveTextStorage(object):
|
||||||
def __init__(self, columns):
|
def __init__(self, columns, default_font, column_width, line_height, scene):
|
||||||
super(TextStorage, self).__init__()
|
|
||||||
self.column_count = columns
|
self.column_count = columns
|
||||||
self.colors = (QtCore.Qt.red, QtCore.Qt.green, QtGui.QColor(46, 100, 254))
|
self.colors = (QtCore.Qt.red, QtCore.Qt.green, QtGui.QColor(46, 100, 254))
|
||||||
|
self.lines = deque()
|
||||||
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(768, 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.default_font = default_font
|
||||||
self.column_width = column_width
|
self.column_width = column_width
|
||||||
self.line_height = line_height
|
self.line_height = line_height
|
||||||
self.graphics_scene = scene
|
self.graphics_scene = scene
|
||||||
self.num_lines, self.offset = divmod(576, self.line_height)
|
self.num_lines, self.offset = divmod(576, self.line_height)
|
||||||
|
self.data = deque()
|
||||||
|
|
||||||
def init_columns(self):
|
def init_columns(self):
|
||||||
color = self.colors[0]
|
color = self.colors[0]
|
||||||
|
@ -107,26 +64,38 @@ class ExclusiveTextStorage(TextStorage):
|
||||||
text_item.setPos(0, y * self.line_height)
|
text_item.setPos(0, y * self.line_height)
|
||||||
self.lines.append(text_item)
|
self.lines.append(text_item)
|
||||||
|
|
||||||
def add_text(self, column, text):
|
def __add_text(self, column, text):
|
||||||
text_item = self.graphics_scene.addSimpleText(text, self.default_font)
|
text_item = self.graphics_scene.addSimpleText(text, self.default_font)
|
||||||
text_item.setX(column * self.column_width)
|
text_item.setX(column * self.column_width)
|
||||||
color = self.colors[column]
|
color = self.colors[column]
|
||||||
text_item.setBrush(color)
|
text_item.setBrush(color)
|
||||||
|
|
||||||
old_item = self.lines.pop(0)
|
old_item = self.lines.popleft()
|
||||||
self.graphics_scene.removeItem(old_item)
|
self.graphics_scene.removeItem(old_item)
|
||||||
self.lines.append(text_item)
|
self.lines.append(text_item)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
column, text = self.data.popleft()
|
||||||
|
self.__add_text(column, text)
|
||||||
|
except IndexError, msg:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
for iy, text_item in enumerate(self.lines):
|
for iy, text_item in enumerate(self.lines):
|
||||||
text_item.setY(iy * self.line_height)
|
text_item.setY(iy * self.line_height)
|
||||||
|
|
||||||
|
def add_text(self, column, text):
|
||||||
|
self.data.append((column, text))
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtGui.QMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase):
|
||||||
|
def __init__(self, args, parent=None):
|
||||||
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
|
||||||
def __init__(self, args, parent=None, columns=3, column_exclusive=False):
|
|
||||||
super(MainWindow, self).__init__(parent)
|
|
||||||
self.args = args
|
self.args = args
|
||||||
|
#PsyQtChaoscClientBase.__init__(self)
|
||||||
|
super(MainWindow, self).__init__()
|
||||||
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
|
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self)
|
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self)
|
||||||
|
@ -140,20 +109,16 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
self.graphics_view.setScene(self.graphics_scene)
|
self.graphics_view.setScene(self.graphics_scene)
|
||||||
self.default_font = QtGui.QFont("Monospace", 14)
|
self.default_font = QtGui.QFont("Monospace", 14)
|
||||||
self.default_font.setStyleHint(QtGui.QFont.Monospace)
|
self.default_font.setStyleHint(QtGui.QFont.Monospace)
|
||||||
self.default_font.setBold(True)
|
#self.default_font.setBold(True)
|
||||||
self.graphics_scene.setFont(self.default_font)
|
self.graphics_scene.setFont(self.default_font)
|
||||||
self.font_metrics = QtGui.QFontMetrics(self.default_font)
|
self.font_metrics = QtGui.QFontMetrics(self.default_font)
|
||||||
self.line_height = self.font_metrics.height()
|
self.line_height = self.font_metrics.height()
|
||||||
|
columns = 3
|
||||||
self.column_width = 775 / columns
|
self.column_width = 775 / columns
|
||||||
|
|
||||||
self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene)
|
self.text_storage = ExclusiveTextStorage(columns, self.default_font, self.column_width, self.line_height, self.graphics_scene)
|
||||||
self.text_storage.init_columns()
|
self.text_storage.init_columns()
|
||||||
|
|
||||||
self.osc_sock = QUdpSocket(self)
|
|
||||||
logger.info("osc bind localhost %d", args.client_port)
|
|
||||||
self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port)
|
|
||||||
self.osc_sock.readyRead.connect(self.got_message)
|
|
||||||
self.osc_sock.error.connect(self.handle_osc_error)
|
|
||||||
msg = OSCMessage("/subscribe")
|
msg = OSCMessage("/subscribe")
|
||||||
msg.appendTypedArg("localhost", "s")
|
msg.appendTypedArg("localhost", "s")
|
||||||
msg.appendTypedArg(args.client_port, "i")
|
msg.appendTypedArg(args.client_port, "i")
|
||||||
|
@ -165,6 +130,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$")
|
self.regex = re.compile("^/(uwe|merle|bjoern)/(.*?)$")
|
||||||
|
|
||||||
|
def pubdir(self):
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
msg = OSCMessage("/unsubscribe")
|
msg = OSCMessage("/unsubscribe")
|
||||||
msg.appendTypedArg("localhost", "s")
|
msg.appendTypedArg("localhost", "s")
|
||||||
|
@ -179,6 +147,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
self.text_storage.add_text(column, text)
|
self.text_storage.add_text(column, text)
|
||||||
|
|
||||||
def render_image(self):
|
def render_image(self):
|
||||||
|
self.text_storage.finish()
|
||||||
image = QtGui.QImage(768, 576, QtGui.QImage.Format_ARGB32_Premultiplied)
|
image = QtGui.QImage(768, 576, QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||||
image.fill(QtCore.Qt.black)
|
image.fill(QtCore.Qt.black)
|
||||||
painter = QtGui.QPainter(image)
|
painter = QtGui.QPainter(image)
|
||||||
|
@ -191,7 +160,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
painter.end()
|
painter.end()
|
||||||
buf = QBuffer()
|
buf = QBuffer()
|
||||||
buf.open(QIODevice.WriteOnly)
|
buf.open(QIODevice.WriteOnly)
|
||||||
image.save(buf, "JPG", 80)
|
image.save(buf, "JPG", 85)
|
||||||
image_data = buf.data()
|
image_data = buf.data()
|
||||||
return image_data
|
return image_data
|
||||||
|
|
||||||
|
@ -207,6 +176,8 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
if text == "temperatur":
|
||||||
|
text += "e"
|
||||||
if actor == "merle":
|
if actor == "merle":
|
||||||
self.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args])))
|
self.add_text(0, "%s = %s" % (text, ", ".join([str(i) for i in args])))
|
||||||
elif actor == "uwe":
|
elif actor == "uwe":
|
||||||
|
@ -229,8 +200,11 @@ def main():
|
||||||
args = arg_parser.finalize()
|
args = arg_parser.finalize()
|
||||||
|
|
||||||
http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
http_host, http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
||||||
|
args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family)
|
||||||
|
|
||||||
window = MainWindow(args)
|
window = MainWindow(args)
|
||||||
#window.show()
|
sys.excepthook = window.sigint_handler
|
||||||
|
signal.signal(signal.SIGTERM, window.sigterm_handler)
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<HTML>
|
<HTML>
|
||||||
<BODY>
|
<BODY>
|
||||||
|
|
||||||
<img src="/camera.mjpeg" alt="Smiley face">
|
<img src="/camera_%d.mjpeg" alt="Smiley face">
|
||||||
|
|
||||||
</BODY>
|
</BODY>
|
||||||
</HTML>
|
</HTML>
|
||||||
|
|
|
@ -24,79 +24,82 @@
|
||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from chaosc.argparser_groups import *
|
import atexit
|
||||||
from chaosc.lib import logger, resolve_host
|
import random
|
||||||
from datetime import datetime
|
|
||||||
from operator import attrgetter
|
|
||||||
from PyQt4 import QtGui, QtCore
|
|
||||||
from PyQt4.QtCore import QBuffer, QByteArray, QIODevice
|
|
||||||
from PyQt4.QtNetwork import QTcpServer, QTcpSocket, QUdpSocket, QHostAddress
|
|
||||||
from PyQt4.QtGui import QPixmap
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import numpy as np
|
|
||||||
import os.path
|
import os.path
|
||||||
|
import re
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import exceptions
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from PyQt4.QtCore import QBuffer, QByteArray, QIODevice
|
||||||
|
from PyQt4.QtNetwork import QUdpSocket, QHostAddress
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from pyqtgraph.widgets.PlotWidget import PlotWidget
|
from pyqtgraph.widgets.PlotWidget import PlotWidget
|
||||||
import Queue
|
|
||||||
import re
|
|
||||||
import select
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from psylib.mjpeg_streaming_server import MjpegStreamingServer
|
|
||||||
|
|
||||||
|
from chaosc.argparser_groups import ArgParser
|
||||||
|
from chaosc.lib import logger, resolve_host
|
||||||
|
from psylib.mjpeg_streaming_server import *
|
||||||
|
from psylib.psyqt_base import PsyQtChaoscClientBase
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from chaosc.c_osc_lib import OSCMessage, decode_osc
|
from chaosc.c_osc_lib import OSCMessage, decode_osc
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
logging.exception(e)
|
|
||||||
from chaosc.osc_lib import OSCMessage, decode_osc
|
from chaosc.osc_lib import OSCMessage, decode_osc
|
||||||
|
|
||||||
|
qtapp = QtGui.QApplication([])
|
||||||
|
|
||||||
def get_steps(pulse_rate, rate):
|
|
||||||
beat_length = 60. / pulse_rate
|
def get_steps(pulse, delta_ms):
|
||||||
steps_pre = int(beat_length / rate) + 1
|
beat_length = 60000. / pulse
|
||||||
|
steps_pre = int(beat_length / delta_ms) + 1
|
||||||
used_sleep_time = beat_length / steps_pre
|
used_sleep_time = beat_length / steps_pre
|
||||||
steps = int(beat_length / used_sleep_time)
|
steps = int(beat_length / used_sleep_time)
|
||||||
return steps, used_sleep_time
|
return steps, used_sleep_time
|
||||||
|
|
||||||
|
|
||||||
class Generator(object):
|
class Generator(object):
|
||||||
def __init__(self, pulse=92, delta=0.08):
|
def __init__(self, pulse=92, delta=80):
|
||||||
self.count = 0
|
self.count = 0
|
||||||
self.pulse = 92
|
self.pulse = random.randint(85, 105)
|
||||||
self.delta = delta
|
self.delta = delta
|
||||||
self.steps = get_steps(self.pulse, delta / 2)
|
self.multiplier = 4
|
||||||
|
self.steps, _ = get_steps(self.pulse, delta / self.multiplier)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
while 1:
|
while 1:
|
||||||
value = random.randint(0, steps)
|
if self.count < int(self.steps / 100. * 30):
|
||||||
if self.count < int(steps / 100. * 20):
|
value = random.randint(30, 35)
|
||||||
value = random.randint(0,20)
|
elif self.count == int(self.steps / 100. * 30):
|
||||||
elif self.count < int(steps / 100. * 30):
|
value = random.randint(random.randint(50,60), random.randint(60, 70))
|
||||||
value = random.randint(20, 30)
|
elif self.count < int(self.steps / 100. * 45):
|
||||||
elif self.count < int(steps / 100. * 40):
|
value = random.randint(30, 35)
|
||||||
value = random.randint(30,100)
|
elif self.count < int(self.steps / 2.):
|
||||||
elif self.count < int(steps / 2.):
|
value = random.randint(0, 15)
|
||||||
value = random.randint(100,200)
|
elif self.count == int(self.steps / 2.):
|
||||||
elif self.count == int(steps / 2.):
|
|
||||||
value = 255
|
value = 255
|
||||||
elif self.count < int(steps / 100. * 60):
|
elif self.count < int(self.steps / 100. * 60):
|
||||||
value = random.randint(100, 200)
|
value = random.randint(random.randint(25,30), random.randint(30, 35))
|
||||||
elif self.count < int(steps / 100. * 70):
|
elif self.count < int(self.steps / 100. * 70):
|
||||||
value = random.randint(50, 100)
|
value = random.randint(random.randint(10,25), random.randint(25, 30))
|
||||||
elif self.count < int(steps / 100. * 80):
|
elif self.count < self.steps:
|
||||||
value = random.randint(20, 50)
|
value = random.randint(random.randint(15,25), random.randint(25, 30))
|
||||||
elif self.count <= steps:
|
else:
|
||||||
value = random.randint(0,20)
|
|
||||||
elif self.count >= steps:
|
|
||||||
self.count = 0
|
self.count = 0
|
||||||
|
value = 30
|
||||||
|
|
||||||
self.count += 1
|
self.count += 1
|
||||||
yield value
|
yield value
|
||||||
|
|
||||||
|
def set_pulse(self, pulse):
|
||||||
|
self.pulse = pulse
|
||||||
|
self.steps, _ = get_steps(pulse, self.delta / self.multiplier)
|
||||||
|
|
||||||
|
|
||||||
def retrigger(self):
|
def retrigger(self):
|
||||||
self.count = self.steps / 2
|
self.count = self.steps / 2
|
||||||
|
|
||||||
|
@ -115,11 +118,14 @@ class Actor(object):
|
||||||
self.data = np.array([self.offset] * num_data)
|
self.data = np.array([self.offset] * num_data)
|
||||||
self.head = 0
|
self.head = 0
|
||||||
self.pre_head = 0
|
self.pre_head = 0
|
||||||
self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), name=name)
|
self.plotItem = pg.PlotCurveItem(pen=pg.mkPen(color, width=3), width=4, name=name)
|
||||||
|
#self.plotItem.setShadowPen(pg.mkPen("w", width=5))
|
||||||
self.plotPoint = pg.ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5)
|
self.plotPoint = pg.ScatterPlotItem(pen=pg.mkPen("w", width=5), brush=pg.mkBrush(color), size=5)
|
||||||
|
self.osci = None
|
||||||
|
self.osci_obj = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<Actor name:%r, active=%r, position=%r>" % (self.name, self.active, self.head)
|
return "<Actor name:%r, position=%r>" % (self.name, self.head)
|
||||||
|
|
||||||
__repr__ = __str__
|
__repr__ = __str__
|
||||||
|
|
||||||
|
@ -143,29 +149,20 @@ class Actor(object):
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
self.plotItem.setData(y=self.data, clear=True)
|
self.plotItem.setData(y=self.data, clear=True)
|
||||||
self.plotPoint.setData(x=[self.pre_head], y = [self.data[self.pre_head]])
|
self.plotPoint.setData(x=[self.pre_head], y=[self.data[self.pre_head]])
|
||||||
|
|
||||||
|
|
||||||
class EkgPlotWidget(PlotWidget):
|
class EkgPlotWidget(PlotWidget, MjpegStreamingConsumerInterface, PsyQtChaoscClientBase):
|
||||||
def __init__(self, args, parent=None):
|
def __init__(self, args, parent=None):
|
||||||
super(EkgPlotWidget, self).__init__(parent)
|
|
||||||
self.args = args
|
self.args = args
|
||||||
|
PsyQtChaoscClientBase.__init__(self)
|
||||||
|
super(EkgPlotWidget, self).__init__()
|
||||||
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
|
|
||||||
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self)
|
self.fps = 12.5
|
||||||
|
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps)
|
||||||
self.http_server.listen(port=args.http_port)
|
self.http_server.listen(port=args.http_port)
|
||||||
|
|
||||||
self.osc_sock = QUdpSocket(self)
|
|
||||||
logger.info("osc bind localhost %d", args.client_port)
|
|
||||||
self.osc_sock.bind(QHostAddress("127.0.0.1"), args.client_port)
|
|
||||||
self.osc_sock.readyRead.connect(self.got_message)
|
|
||||||
self.osc_sock.error.connect(self.handle_osc_error)
|
|
||||||
msg = OSCMessage("/subscribe")
|
|
||||||
msg.appendTypedArg("localhost", "s")
|
|
||||||
msg.appendTypedArg(args.client_port, "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.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110)
|
|
||||||
self.num_data = 100
|
self.num_data = 100
|
||||||
|
|
||||||
self.hide()
|
self.hide()
|
||||||
|
@ -173,8 +170,6 @@ class EkgPlotWidget(PlotWidget):
|
||||||
self.setYRange(0, 255)
|
self.setYRange(0, 255)
|
||||||
self.setXRange(0, self.num_data)
|
self.setXRange(0, self.num_data)
|
||||||
self.resize(768, 576)
|
self.resize(768, 576)
|
||||||
|
|
||||||
|
|
||||||
colors = ["r", "g", "b"]
|
colors = ["r", "g", "b"]
|
||||||
|
|
||||||
ba = self.getAxis("bottom")
|
ba = self.getAxis("bottom")
|
||||||
|
@ -186,10 +181,9 @@ class EkgPlotWidget(PlotWidget):
|
||||||
self.active_actors = list()
|
self.active_actors = list()
|
||||||
|
|
||||||
self.actors = dict()
|
self.actors = dict()
|
||||||
self.lengths1 = [0]
|
|
||||||
|
|
||||||
self.max_value = 255
|
self.max_value = 255
|
||||||
actor_names = ["merle", "uwe", "bjoern" ]
|
actor_names = ["merle", "uwe", "bjoern"]
|
||||||
self.max_actors = len(actor_names)
|
self.max_actors = len(actor_names)
|
||||||
self.actor_height = self.max_value / self.max_actors
|
self.actor_height = self.max_value / self.max_actors
|
||||||
|
|
||||||
|
@ -197,11 +191,10 @@ class EkgPlotWidget(PlotWidget):
|
||||||
self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height)
|
self.add_actor(actor_name, self.num_data, color, ix, self.max_actors, self.actor_height)
|
||||||
|
|
||||||
self.set_positions()
|
self.set_positions()
|
||||||
|
self.heartbeat_regex = re.compile("^/(.*?)/heartbeat$")
|
||||||
|
|
||||||
self.ekg_regex = re.compile("^/(.*?)/ekg$")
|
def pubdir(self):
|
||||||
self.ctl_regex = re.compile("^/plot/(.*?)$")
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
self.updated_actors = set()
|
|
||||||
self.new_round()
|
|
||||||
|
|
||||||
|
|
||||||
def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height):
|
def add_actor(self, actor_name, num_data, color, ix, max_actors, actor_height):
|
||||||
|
@ -210,7 +203,8 @@ class EkgPlotWidget(PlotWidget):
|
||||||
self.addItem(actor_obj.plotItem)
|
self.addItem(actor_obj.plotItem)
|
||||||
self.addItem(actor_obj.plotPoint)
|
self.addItem(actor_obj.plotPoint)
|
||||||
self.active_actors.append(actor_obj)
|
self.active_actors.append(actor_obj)
|
||||||
|
actor_obj.osci_obj = Generator(delta=self.http_server.timer_delta)
|
||||||
|
actor_obj.osci = actor_obj.osci_obj()
|
||||||
|
|
||||||
def set_positions(self):
|
def set_positions(self):
|
||||||
for ix, actor_obj in enumerate(self.active_actors):
|
for ix, actor_obj in enumerate(self.active_actors):
|
||||||
|
@ -220,51 +214,24 @@ class EkgPlotWidget(PlotWidget):
|
||||||
def active_actor_count(self):
|
def active_actor_count(self):
|
||||||
return self.max_actors
|
return self.max_actors
|
||||||
|
|
||||||
def new_round(self):
|
def update(self, osc_address, args):
|
||||||
for ix, actor in enumerate(self.active_actors):
|
|
||||||
actor.updated = 0
|
|
||||||
|
|
||||||
def update_missing_actors(self):
|
res = self.heartbeat_regex.match(osc_address)
|
||||||
liste = sorted(self.active_actors, key=attrgetter("updated"))
|
|
||||||
max_values = liste[-1].updated
|
|
||||||
if max_values == 0:
|
|
||||||
# handling no signal
|
|
||||||
for actor in self.active_actors:
|
|
||||||
actor.add_value(0)
|
|
||||||
return
|
|
||||||
for ix, actor in enumerate(self.active_actors):
|
|
||||||
diff = max_values - actor.updated
|
|
||||||
if diff > 0:
|
|
||||||
for i in range(diff):
|
|
||||||
actor.add_value(0)
|
|
||||||
|
|
||||||
|
|
||||||
def update(self, osc_address, value):
|
|
||||||
|
|
||||||
res = self.ekg_regex.match(osc_address)
|
|
||||||
if res:
|
if res:
|
||||||
actor_name = res.group(1)
|
actor_name = res.group(1)
|
||||||
actor_obj = self.actors[actor_name]
|
actor_obj = self.actors[actor_name]
|
||||||
actor_obj.add_value(value)
|
#logger.info("actor: %r, %r", actor_name, args)
|
||||||
|
if args[0] == 1:
|
||||||
|
actor_obj.osci_obj.retrigger()
|
||||||
|
actor_obj.osci_obj.set_pulse(args[1])
|
||||||
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
for ix, actor in enumerate(self.active_actors):
|
|
||||||
actor.render()
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
msg = OSCMessage("/unsubscribe")
|
|
||||||
msg.appendTypedArg("localhost", "s")
|
|
||||||
msg.appendTypedArg(self.args.client_port, "i")
|
|
||||||
msg.appendTypedArg(self.args.authenticate, "s")
|
|
||||||
self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress("127.0.0.1"), 7110)
|
|
||||||
|
|
||||||
def handle_osc_error(self, error):
|
|
||||||
logger.info("osc socket error %d", error)
|
|
||||||
|
|
||||||
def render_image(self):
|
def render_image(self):
|
||||||
self.update_missing_actors()
|
for actor_obj in self.active_actors:
|
||||||
self.render()
|
osc = actor_obj.osci
|
||||||
|
for i in range(actor_obj.osci_obj.multiplier):
|
||||||
|
actor_obj.add_value(osc.next())
|
||||||
|
actor_obj.render()
|
||||||
exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem)
|
exporter = pg.exporters.ImageExporter.ImageExporter(self.plotItem)
|
||||||
exporter.parameters()['width'] = 768
|
exporter.parameters()['width'] = 768
|
||||||
img = exporter.export(toBytes=True)
|
img = exporter.export(toBytes=True)
|
||||||
|
@ -272,7 +239,6 @@ class EkgPlotWidget(PlotWidget):
|
||||||
buf.open(QIODevice.WriteOnly)
|
buf.open(QIODevice.WriteOnly)
|
||||||
img.save(buf, "JPG", 75)
|
img.save(buf, "JPG", 75)
|
||||||
JpegData = buf.data()
|
JpegData = buf.data()
|
||||||
self.new_round()
|
|
||||||
return JpegData
|
return JpegData
|
||||||
|
|
||||||
def got_message(self):
|
def got_message(self):
|
||||||
|
@ -282,10 +248,8 @@ class EkgPlotWidget(PlotWidget):
|
||||||
osc_address, typetags, args = decode_osc(data, 0, len(data))
|
osc_address, typetags, args = decode_osc(data, 0, len(data))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self.update(osc_address, args[0])
|
self.update(osc_address, args)
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -293,18 +257,20 @@ def main():
|
||||||
arg_parser = ArgParser("ekgplotter")
|
arg_parser = ArgParser("ekgplotter")
|
||||||
arg_parser.add_global_group()
|
arg_parser.add_global_group()
|
||||||
client_group = arg_parser.add_client_group()
|
client_group = arg_parser.add_client_group()
|
||||||
arg_parser.add_argument(client_group, '-x', "--http_host", default="::",
|
arg_parser.add_argument(client_group, '-x', "--http_host", default='::',
|
||||||
help='my host, defaults to "::"')
|
help='my host, defaults to "::"')
|
||||||
arg_parser.add_argument(client_group, '-X', "--http_port", default=9000,
|
arg_parser.add_argument(client_group, '-X', '--http_port', default=9000,
|
||||||
type=int, help='my port, defaults to 9000')
|
type=int, help='my port, defaults to 9000')
|
||||||
arg_parser.add_chaosc_group()
|
arg_parser.add_chaosc_group()
|
||||||
arg_parser.add_subscriber_group()
|
arg_parser.add_subscriber_group()
|
||||||
args = arg_parser.finalize()
|
args = arg_parser.finalize()
|
||||||
|
|
||||||
args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
||||||
|
args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family)
|
||||||
|
|
||||||
qtapp = QtGui.QApplication([])
|
window = EkgPlotWidget(args)
|
||||||
widget = EkgPlotWidget(args)
|
sys.excepthook = window.sigint_handler
|
||||||
|
signal.signal(signal.SIGTERM, window.sigterm_handler)
|
||||||
qtapp.exec_()
|
qtapp.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# This file is part of chaosc and psychosis
|
# This file is part of chaosc/psylib package
|
||||||
#
|
#
|
||||||
# chaosc/psychosis is free software: you can redistribute it and/or modify
|
# chaosc/psylib is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# chaosc/psychosis is distributed in the hope that it will be useful,
|
# chaosc/psylib is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with chaosc/psychosis. If not, see <http://www.gnu.org/licenses/>.
|
# along with chaosc/psylib. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
# Copyright (C) 2014 Stefan Kögl
|
# Copyright (C) 2014 Stefan Kögl
|
||||||
|
|
||||||
|
@ -23,27 +23,47 @@ from __future__ import absolute_import
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
|
|
||||||
from datetime import datetime
|
from chaosc.lib import logger
|
||||||
from chaosc.argparser_groups import *
|
from PyQt4 import QtCore
|
||||||
from chaosc.lib import logger, resolve_host
|
from PyQt4.QtCore import QByteArray
|
||||||
from PyQt4 import QtCore, QtGui
|
|
||||||
from PyQt4.QtCore import QBuffer, QByteArray, QIODevice
|
|
||||||
from PyQt4.QtNetwork import QTcpServer, QTcpSocket
|
from PyQt4.QtNetwork import QTcpServer, QTcpSocket
|
||||||
|
|
||||||
|
__all__ = ["MjpegStreamingConsumerInterface", "MjpegStreamingServer"]
|
||||||
|
|
||||||
|
class MjpegStreamingConsumerInterface(object):
|
||||||
|
def pubdir(self):
|
||||||
|
""" returns the directory, from where your static files should be served
|
||||||
|
|
||||||
|
fast and dirty implementation e.g:
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def render_image(self):
|
||||||
|
"""returns a QByteArray with the binary date of a jpg image
|
||||||
|
|
||||||
|
this method should implement the actual window/widget grabbing"""
|
||||||
|
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
class MjpegStreamingServer(QTcpServer):
|
class MjpegStreamingServer(QTcpServer):
|
||||||
|
|
||||||
def __init__(self, server_address, parent=None):
|
def __init__(self, server_address, parent=None, fps=12.5):
|
||||||
super(MjpegStreamingServer, self).__init__(parent)
|
super(MjpegStreamingServer, self).__init__(parent)
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
self.newConnection.connect(self.new_connection)
|
self.newConnection.connect(self.new_connection)
|
||||||
|
assert isinstance(parent, MjpegStreamingConsumerInterface)
|
||||||
self.widget = parent
|
self.widget = parent
|
||||||
|
|
||||||
self.sockets = list()
|
self.sockets = list()
|
||||||
self.img_data = None
|
self.img_data = None
|
||||||
|
self.fps = fps
|
||||||
|
self.timer_delta = 1000 / fps
|
||||||
self.timer = QtCore.QTimer()
|
self.timer = QtCore.QTimer()
|
||||||
self.timer.timeout.connect(self.send_image)
|
self.timer.timeout.connect(self.send_image)
|
||||||
self.timer.start(80)
|
self.timer.start(self.timer_delta)
|
||||||
self.stream_clients = list()
|
self.stream_clients = list()
|
||||||
self.get_regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$")
|
self.get_regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$")
|
||||||
self.host_regex = re.compile("^Host: (\w+?):(\d+)$")
|
self.host_regex = re.compile("^Host: (\w+?):(\d+)$")
|
||||||
|
@ -51,8 +71,8 @@ class MjpegStreamingServer(QTcpServer):
|
||||||
|
|
||||||
def handle_request(self):
|
def handle_request(self):
|
||||||
sock = self.sender()
|
sock = self.sender()
|
||||||
logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort())
|
|
||||||
sock_id = id(sock)
|
sock_id = id(sock)
|
||||||
|
logger.info("handle_request: sock_id=%r", sock_id)
|
||||||
if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState):
|
if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState):
|
||||||
logger.info("connection closed")
|
logger.info("connection closed")
|
||||||
self.sockets.remove(sock)
|
self.sockets.remove(sock)
|
||||||
|
@ -79,14 +99,26 @@ class MjpegStreamingServer(QTcpServer):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if ext == "ico":
|
if ext == "ico":
|
||||||
directory = os.path.dirname(os.path.abspath(__file__))
|
directory = self.widget.pubdir()
|
||||||
data = open(os.path.join(directory, "favicon.ico"), "rb").read()
|
try:
|
||||||
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data))
|
data = open(os.path.join(directory, "favicon.ico"), "rb").read()
|
||||||
|
except IOError:
|
||||||
|
logger.error("request not found/handled - sending 404 not found")
|
||||||
|
sock.write("HTTP/1.1 404 Not Found\r\n")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data))
|
||||||
elif ext == "html":
|
elif ext == "html":
|
||||||
directory = os.path.dirname(os.path.abspath(__file__))
|
directory = self.widget.pubdir()
|
||||||
data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id
|
try:
|
||||||
self.html_map[sock_id] = None
|
data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id
|
||||||
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data))
|
self.html_map[sock_id] = None
|
||||||
|
except IOError:
|
||||||
|
logger.error("request not found/handled - sending 404 not found")
|
||||||
|
sock.write("HTTP/1.1 404 Not Found\r\n")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data))
|
||||||
elif ext == "mjpeg":
|
elif ext == "mjpeg":
|
||||||
try:
|
try:
|
||||||
_, html_sock_id = resource.split("_", 1)
|
_, html_sock_id = resource.split("_", 1)
|
||||||
|
@ -117,31 +149,40 @@ class MjpegStreamingServer(QTcpServer):
|
||||||
sock.disconnected.disconnect(self.slot_remove_connection)
|
sock.disconnected.disconnect(self.slot_remove_connection)
|
||||||
sock.close()
|
sock.close()
|
||||||
sock.deleteLater()
|
sock.deleteLater()
|
||||||
self.sockets.remove(sock)
|
try:
|
||||||
logger.info("connection removed: sock=%r, sock_id=%r", sock, sock_id)
|
self.sockets.remove(sock)
|
||||||
|
logger.info("connection %r removed", sock_id)
|
||||||
|
except ValueError, msg:
|
||||||
|
logger.info("connection %r was not stored?", sock_id)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.stream_clients.remove(sock)
|
self.stream_clients.remove(sock)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
logger.info("connection %r was not streaming", sock_id)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream_client = self.html_map.pop(sock_id)
|
stream_client = self.html_map.pop(sock_id)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.info("socket has no child socket")
|
logger.info("socket %r has no child socket", sock_id)
|
||||||
else:
|
else:
|
||||||
stream_client.close()
|
|
||||||
try:
|
try:
|
||||||
self.stream_clients.remove(stream_client)
|
stream_client.close()
|
||||||
logger.info("removed stream_client=%r", id(stream_client))
|
except AttributeError, msg:
|
||||||
except ValueError:
|
logger.info("no stream client")
|
||||||
pass
|
else:
|
||||||
|
try:
|
||||||
|
self.stream_clients.remove(stream_client)
|
||||||
|
logger.info("child connection %r removed from streaming", id(stream_client))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.sockets.remove(stream_client)
|
self.sockets.remove(stream_client)
|
||||||
logger.info("removed child sock_id=%r", id(stream_client))
|
logger.info("child connection %r removed from storage", id(stream_client))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def send_image(self):
|
def send_image(self):
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of chaosc/psylib package
|
||||||
|
#
|
||||||
|
# chaosc/psylib 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/psylib 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/psylib. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014 Stefan Kögl
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
from PyQt4.QtCore import QBuffer, QByteArray
|
||||||
|
from PyQt4.QtNetwork import QUdpSocket, QHostAddress
|
||||||
|
|
||||||
|
|
||||||
|
from chaosc.lib import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
from chaosc.c_osc_lib import OSCMessage, decode_osc
|
||||||
|
except ImportError as e:
|
||||||
|
from chaosc.osc_lib import OSCMessage, decode_osc
|
||||||
|
|
||||||
|
|
||||||
|
class PsyQtClientBase(QtCore.QObject):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PsyQtClientBase, self).__init__()
|
||||||
|
# periodically trap into python interpreter domain to catch signals etc
|
||||||
|
timer = QtCore.QTimer()
|
||||||
|
timer.start(2000)
|
||||||
|
timer.timeout.connect(lambda: None)
|
||||||
|
|
||||||
|
def sigint_handler(self, ex_cls, ex, traceback):
|
||||||
|
"""Handler for the SIGINT signal."""
|
||||||
|
if ex_cls == KeyboardInterrupt:
|
||||||
|
logger.info("found KeyboardInterrupt")
|
||||||
|
QtGui.QApplication.exit()
|
||||||
|
else:
|
||||||
|
logger.critical(''.join(traceback.format_tb(tb)))
|
||||||
|
logger.critical('{0}: {1}'.format(ex_cls, ex))
|
||||||
|
|
||||||
|
class PsyQtChaoscClientBase(PsyQtClientBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(PsyQtChaoscClientBase, self).__init__()
|
||||||
|
self.osc_sock = QUdpSocket(self)
|
||||||
|
logger.info("osc bind localhost %d", self.args.client_port)
|
||||||
|
self.osc_sock.bind(QHostAddress(self.args.client_host), self.args.client_port)
|
||||||
|
self.osc_sock.readyRead.connect(self.got_message)
|
||||||
|
self.osc_sock.error.connect(self.handle_osc_error)
|
||||||
|
self.subscribe()
|
||||||
|
|
||||||
|
def sigint_handler(self, ex_cls, ex, traceback):
|
||||||
|
"""Handler for the SIGINT signal."""
|
||||||
|
if ex_cls == KeyboardInterrupt:
|
||||||
|
logger.info("found KeyboardInterrupt")
|
||||||
|
self.unsubscribe()
|
||||||
|
QtGui.QApplication.exit()
|
||||||
|
else:
|
||||||
|
logger.critical(''.join(traceback.format_tb(traceback)))
|
||||||
|
logger.critical('{0}: {1}'.format(ex_cls, ex))
|
||||||
|
|
||||||
|
def sigterm_handler(self, *args):
|
||||||
|
self.unsubscribe()
|
||||||
|
QtGui.QApplication.exit()
|
||||||
|
|
||||||
|
def subscribe(self):
|
||||||
|
logger.info("subscribe")
|
||||||
|
msg = OSCMessage("/subscribe")
|
||||||
|
msg.appendTypedArg("localhost", "s")
|
||||||
|
msg.appendTypedArg(self.args.client_port, "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.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port)
|
||||||
|
|
||||||
|
def unsubscribe(self):
|
||||||
|
logger.info("unsubscribe")
|
||||||
|
msg = OSCMessage("/unsubscribe")
|
||||||
|
msg.appendTypedArg("localhost", "s")
|
||||||
|
msg.appendTypedArg(self.args.client_port, "i")
|
||||||
|
msg.appendTypedArg(self.args.authenticate, "s")
|
||||||
|
self.osc_sock.writeDatagram(QByteArray(msg.encode_osc()), QHostAddress(self.args.chaosc_host), self.args.chaosc_port)
|
||||||
|
|
||||||
|
def handle_osc_error(self, error):
|
||||||
|
logger.info("osc socket error %d", error)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
logger.info("closeEvent", event)
|
||||||
|
self.unsubscribe()
|
||||||
|
event.accept()
|
|
@ -24,6 +24,7 @@ from __future__ import absolute_import
|
||||||
import cPickle
|
import cPickle
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
|
@ -39,11 +40,14 @@ from PyQt4.QtNetwork import QTcpServer, QTcpSocket
|
||||||
from chaosc.argparser_groups import ArgParser
|
from chaosc.argparser_groups import ArgParser
|
||||||
from chaosc.lib import resolve_host, logger
|
from chaosc.lib import resolve_host, logger
|
||||||
|
|
||||||
|
from psylib.mjpeg_streaming_server import *
|
||||||
|
from psylib.psyqt_base import PsyQtClientBase
|
||||||
|
|
||||||
from texter.texter_ui import Ui_MainWindow, _fromUtf8
|
from texter.texter_ui import Ui_MainWindow, _fromUtf8
|
||||||
from texter.edit_dialog_ui import Ui_EditDialog
|
from texter.edit_dialog_ui import Ui_EditDialog
|
||||||
from texter.text_model import TextModel
|
from texter.text_model import TextModel
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
qtapp = QtGui.QApplication([])
|
||||||
|
|
||||||
|
|
||||||
# NOTE: if the QIcon.fromTheme method does not find any icons, you can use
|
# NOTE: if the QIcon.fromTheme method does not find any icons, you can use
|
||||||
|
@ -54,144 +58,6 @@ app = QtGui.QApplication([])
|
||||||
def get_preview_text(text):
|
def get_preview_text(text):
|
||||||
return re.sub(" +", " ", text.replace("\n", " ")).strip()[:20]
|
return re.sub(" +", " ", text.replace("\n", " ")).strip()[:20]
|
||||||
|
|
||||||
class MjpegStreamingServer(QTcpServer):
|
|
||||||
|
|
||||||
def __init__(self, server_address, parent=None):
|
|
||||||
super(MjpegStreamingServer, self).__init__(parent)
|
|
||||||
self.server_address = server_address
|
|
||||||
self.newConnection.connect(self.start_streaming)
|
|
||||||
self.widget = parent.live_text
|
|
||||||
self.win_id = self.widget.winId()
|
|
||||||
self.sockets = list()
|
|
||||||
self.img_data = None
|
|
||||||
self.timer = QtCore.QTimer()
|
|
||||||
self.timer.timeout.connect(self.render_image)
|
|
||||||
self.timer.start(80)
|
|
||||||
self.stream_clients = list()
|
|
||||||
self.regex = re.compile("^GET /(\w+?)\.(\w+?) HTTP/(\d+\.\d+)$")
|
|
||||||
self.html_map = dict()
|
|
||||||
self.coords = parent.live_text_rect()
|
|
||||||
|
|
||||||
def handle_request(self):
|
|
||||||
print "foo"
|
|
||||||
sock = self.sender()
|
|
||||||
logger.info("handle_request: %s %d", sock.peerAddress(), sock.peerPort())
|
|
||||||
sock_id = id(sock)
|
|
||||||
if sock.state() in (QTcpSocket.UnconnectedState, QTcpSocket.ClosingState):
|
|
||||||
logger.info("connection closed")
|
|
||||||
self.sockets.remove(sock)
|
|
||||||
sock.deleteLater()
|
|
||||||
return
|
|
||||||
|
|
||||||
client_data = str(sock.readAll())
|
|
||||||
logger.info("request %r", client_data)
|
|
||||||
line = client_data.split("\r\n")[0]
|
|
||||||
logger.info("first line: %r", line)
|
|
||||||
try:
|
|
||||||
resource, ext, http_version = self.regex.match(line).groups()
|
|
||||||
logger.info("resource=%r, ext=%r, http_version=%r", resource, ext, http_version)
|
|
||||||
except AttributeError:
|
|
||||||
logger.info("no matching request - sending 404 not found")
|
|
||||||
sock.write("HTTP/1.1 404 Not Found\r\n")
|
|
||||||
else:
|
|
||||||
if ext == "ico":
|
|
||||||
directory = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
data = open(os.path.join(directory, "favicon.ico"), "rb").read()
|
|
||||||
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: image/x-ico\r\n\r\n%s' % data))
|
|
||||||
elif ext == "html":
|
|
||||||
directory = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
data = open(os.path.join(directory, "index.html"), "rb").read() % sock_id
|
|
||||||
self.html_map[sock_id] = None
|
|
||||||
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: text/html;encoding: utf-8\r\n\r\n%s' % data))
|
|
||||||
#sock.close()
|
|
||||||
elif ext == "mjpeg":
|
|
||||||
try:
|
|
||||||
_, html_sock_id = resource.split("_", 1)
|
|
||||||
html_sock_id = int(html_sock_id)
|
|
||||||
except ValueError:
|
|
||||||
html_sock_id = None
|
|
||||||
|
|
||||||
if sock not in self.stream_clients:
|
|
||||||
logger.info("starting streaming...")
|
|
||||||
if html_sock_id is not None:
|
|
||||||
self.html_map[html_sock_id] = sock
|
|
||||||
self.stream_clients.append(sock)
|
|
||||||
sock.write(QByteArray('HTTP/1.1 200 Ok\r\nContent-Type: multipart/x-mixed-replace; boundary=--2342\r\n\r\n'))
|
|
||||||
else:
|
|
||||||
logger.error("request not found/handled - sending 404 not found")
|
|
||||||
sock.write("HTTP/1.1 404 Not Found\r\n")
|
|
||||||
|
|
||||||
|
|
||||||
def remove_stream_client(self):
|
|
||||||
try:
|
|
||||||
sock = self.sender()
|
|
||||||
except RuntimeError:
|
|
||||||
return
|
|
||||||
sock_id = id(sock)
|
|
||||||
logger.info("remove_stream_client: sock=%r, sock_id=%r", sock, sock_id)
|
|
||||||
if sock.state() == QTcpSocket.UnconnectedState:
|
|
||||||
sock.disconnected.disconnect(self.remove_stream_client)
|
|
||||||
self.sockets.remove(sock)
|
|
||||||
logger.info("removed sock_id=%r", sock_id)
|
|
||||||
sock.close()
|
|
||||||
try:
|
|
||||||
self.stream_clients.remove(sock)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
stream_client = self.html_map.pop(sock_id)
|
|
||||||
except KeyError:
|
|
||||||
logger.info("socket has no child socket")
|
|
||||||
else:
|
|
||||||
stream_client.close()
|
|
||||||
try:
|
|
||||||
self.stream_clients.remove(stream_client)
|
|
||||||
logger.info("removed stream_client=%r", id(stream_client))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.sockets.remove(stream_client)
|
|
||||||
logger.info("removed child sock_id=%r", id(stream_client))
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def render_image(self):
|
|
||||||
if not self.stream_clients:
|
|
||||||
return
|
|
||||||
|
|
||||||
#pixmap = QPixmap.grabWidget(self.widget, QtCore.QRect(10, 10, 768, 576))
|
|
||||||
pixmap = QPixmap.grabWindow(self.win_id, 5, 5, 768, 576)
|
|
||||||
buf = QBuffer()
|
|
||||||
buf.open(QIODevice.WriteOnly)
|
|
||||||
pixmap.save(buf, "JPG", 30)
|
|
||||||
self.img_data = buf.data()
|
|
||||||
len_data = len(self.img_data)
|
|
||||||
array = QByteArray("--2342\r\nContent-Type: image/jpeg\r\nContent-length: %d\r\n\r\n%s\r\n\r\n\r\n" % (len_data, self.img_data))
|
|
||||||
for sock in self.stream_clients:
|
|
||||||
sock.write(array)
|
|
||||||
|
|
||||||
def start_streaming(self):
|
|
||||||
while self.hasPendingConnections():
|
|
||||||
sock = self.nextPendingConnection()
|
|
||||||
logger.info("new connection=%r", id(sock))
|
|
||||||
sock.readyRead.connect(self.handle_request)
|
|
||||||
sock.disconnected.connect(self.remove_stream_client)
|
|
||||||
self.sockets.append(sock)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
for sock in self.sockets:
|
|
||||||
sock.close()
|
|
||||||
sock.deleteLater()
|
|
||||||
for sock in self.stream_clients:
|
|
||||||
sock.close()
|
|
||||||
sock.deleteLater()
|
|
||||||
self.stream_clients = list()
|
|
||||||
self.sockets = list()
|
|
||||||
self.html_map = dict()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class EditDialog(QtGui.QWidget, Ui_EditDialog):
|
class EditDialog(QtGui.QWidget, Ui_EditDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
@ -366,10 +232,10 @@ class TextAnimation(QtCore.QObject):
|
||||||
self.count += 1
|
self.count += 1
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(KMainWindow, Ui_MainWindow):
|
class MainWindow(KMainWindow, Ui_MainWindow, MjpegStreamingConsumerInterface, PsyQtClientBase):
|
||||||
def __init__(self, args, parent=None):
|
def __init__(self, args, parent=None):
|
||||||
super(MainWindow, self).__init__(parent)
|
|
||||||
self.args = args
|
self.args = args
|
||||||
|
super(MainWindow, self).__init__()
|
||||||
self.is_streaming = False
|
self.is_streaming = False
|
||||||
|
|
||||||
self.live_center_action = None
|
self.live_center_action = None
|
||||||
|
@ -391,11 +257,15 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
self.dialog = None
|
self.dialog = None
|
||||||
self.current_object = None
|
self.current_object = None
|
||||||
self.current_index = -1
|
self.current_index = -1
|
||||||
|
self.win_id = self.winId()
|
||||||
|
|
||||||
self.is_auto_publish = False
|
self.is_auto_publish = False
|
||||||
|
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self)
|
self.coords = self.live_text_rect()
|
||||||
|
|
||||||
|
self.fps = 12.5
|
||||||
|
self.http_server = MjpegStreamingServer((args.http_host, args.http_port), self, self.fps)
|
||||||
|
|
||||||
self.live_text.setLineWrapMode(QtGui.QTextEdit.LineWrapMode(QtGui.QTextEdit.FixedPixelWidth))
|
self.live_text.setLineWrapMode(QtGui.QTextEdit.LineWrapMode(QtGui.QTextEdit.FixedPixelWidth))
|
||||||
self.live_text.setLineWrapColumnOrWidth(768)
|
self.live_text.setLineWrapColumnOrWidth(768)
|
||||||
|
@ -433,17 +303,29 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
self.create_toolbar()
|
self.create_toolbar()
|
||||||
self.slot_load()
|
self.slot_load()
|
||||||
|
|
||||||
app.focusChanged.connect(self.focusChanged)
|
qtapp.focusChanged.connect(self.focusChanged)
|
||||||
self.start_streaming()
|
self.start_streaming()
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
|
def pubdir(self):
|
||||||
|
return os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
def getPreviewCoords(self):
|
def getPreviewCoords(self):
|
||||||
public_rect = self.preview_text.geometry()
|
public_rect = self.preview_text.geometry()
|
||||||
global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight()))
|
global_rect = QtCore.QRect(self.mapToGlobal(public_rect.topLeft()), self.mapToGlobal(public_rect.bottomRight()))
|
||||||
return global_rect.x(), global_rect.y()
|
return global_rect.x(), global_rect.y()
|
||||||
|
|
||||||
|
|
||||||
|
def render_image(self):
|
||||||
|
pixmap = QPixmap.grabWindow(self.win_id, self.coords.x() + 10, self.coords.y() + 10, 768, 576)
|
||||||
|
buf = QBuffer()
|
||||||
|
buf.open(QIODevice.WriteOnly)
|
||||||
|
pixmap.save(buf, "JPG", 75)
|
||||||
|
return buf.data()
|
||||||
|
|
||||||
|
|
||||||
def filter_editor_actions(self):
|
def filter_editor_actions(self):
|
||||||
disabled_action_names = [
|
disabled_action_names = [
|
||||||
"action_to_plain_text",
|
"action_to_plain_text",
|
||||||
|
@ -631,9 +513,10 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
self.dialog.setButtons(KDialog.ButtonCodes(KDialog.Ok | KDialog.Cancel))
|
self.dialog.setButtons(KDialog.ButtonCodes(KDialog.Ok | KDialog.Cancel))
|
||||||
self.dialog.okClicked.connect(self.slot_save)
|
self.dialog.okClicked.connect(self.slot_save)
|
||||||
self.dialog.exec_()
|
self.dialog.exec_()
|
||||||
|
super(self, MainWindow).closeEvent(event)
|
||||||
|
|
||||||
def live_text_rect(self):
|
def live_text_rect(self):
|
||||||
return 5, 5, 768, 576
|
return self.live_text.geometry()
|
||||||
|
|
||||||
def stop_streaming(self):
|
def stop_streaming(self):
|
||||||
self.is_streaming = False
|
self.is_streaming = False
|
||||||
|
@ -643,6 +526,24 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
self.http_server.listen(port=self.args.http_port)
|
self.http_server.listen(port=self.args.http_port)
|
||||||
self.is_streaming = True
|
self.is_streaming = True
|
||||||
|
|
||||||
|
def fill_combo_box(self):
|
||||||
|
if self.dialog is not None:
|
||||||
|
self.dialog.deleteLater()
|
||||||
|
self.dialog = None
|
||||||
|
|
||||||
|
self.text_combo.clear()
|
||||||
|
current_row = -1
|
||||||
|
for index, list_obj in enumerate(self.model.text_db):
|
||||||
|
preview, text = list_obj
|
||||||
|
self.text_combo.addAction(preview)
|
||||||
|
if list_obj == self.current_object:
|
||||||
|
current_row = index
|
||||||
|
|
||||||
|
if current_row == -1:
|
||||||
|
current_row = self.current_index
|
||||||
|
self.slot_load_preview_text(current_row)
|
||||||
|
self.text_combo.setCurrentItem(current_row)
|
||||||
|
|
||||||
def focusChanged(self, old, new):
|
def focusChanged(self, old, new):
|
||||||
if new == self.preview_text:
|
if new == self.preview_text:
|
||||||
self.live_editor_collection.clearAssociatedWidgets()
|
self.live_editor_collection.clearAssociatedWidgets()
|
||||||
|
@ -651,13 +552,6 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
self.preview_editor_collection.clearAssociatedWidgets()
|
self.preview_editor_collection.clearAssociatedWidgets()
|
||||||
self.live_editor_collection.addAssociatedWidget(self.toolbar)
|
self.live_editor_collection.addAssociatedWidget(self.toolbar)
|
||||||
|
|
||||||
def custom_clear(self, cursor):
|
|
||||||
cursor.beginEditBlock()
|
|
||||||
cursor.movePosition(QtGui.QTextCursor.Start)
|
|
||||||
cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor)
|
|
||||||
cursor.removeSelectedText()
|
|
||||||
cursor.endEditBlock()
|
|
||||||
|
|
||||||
def slot_auto_publish(self, state):
|
def slot_auto_publish(self, state):
|
||||||
self.is_auto_publish = bool(state)
|
self.is_auto_publish = bool(state)
|
||||||
|
|
||||||
|
@ -737,23 +631,6 @@ class MainWindow(KMainWindow, Ui_MainWindow):
|
||||||
if self.fade_animation.timer is None:
|
if self.fade_animation.timer is None:
|
||||||
self.fade_animation.start_animation()
|
self.fade_animation.start_animation()
|
||||||
|
|
||||||
def fill_combo_box(self):
|
|
||||||
if self.dialog is not None:
|
|
||||||
self.dialog.deleteLater()
|
|
||||||
self.dialog = None
|
|
||||||
|
|
||||||
self.text_combo.clear()
|
|
||||||
current_row = -1
|
|
||||||
for index, list_obj in enumerate(self.model.text_db):
|
|
||||||
preview, text = list_obj
|
|
||||||
self.text_combo.addAction(preview)
|
|
||||||
if list_obj == self.current_object:
|
|
||||||
current_row = index
|
|
||||||
|
|
||||||
if current_row == -1:
|
|
||||||
current_row = self.current_index
|
|
||||||
self.slot_load_preview_text(current_row)
|
|
||||||
self.text_combo.setCurrentItem(current_row)
|
|
||||||
|
|
||||||
def slot_load_preview_text(self, index):
|
def slot_load_preview_text(self, index):
|
||||||
try:
|
try:
|
||||||
|
@ -864,9 +741,11 @@ def main():
|
||||||
args = arg_parser.finalize()
|
args = arg_parser.finalize()
|
||||||
|
|
||||||
args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
args.http_host, args.http_port = resolve_host(args.http_host, args.http_port, args.address_family)
|
||||||
|
args.chaosc_host, args.chaosc_port = resolve_host(args.chaosc_host, args.chaosc_port, args.address_family)
|
||||||
|
|
||||||
window = MainWindow(args)
|
window = MainWindow(args, None)
|
||||||
app.exec_()
|
sys.excepthook = window.sigint_handler
|
||||||
|
qtapp.exec_()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in New Issue