summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--example-scripts/voctomidi/README.md10
-rw-r--r--example-scripts/voctomidi/default-config.ini19
-rw-r--r--example-scripts/voctomidi/lib/config.py16
-rwxr-xr-xexample-scripts/voctomidi/voctomidi.py86
-rw-r--r--voctocore/default-config.ini1
-rw-r--r--voctocore/lib/avpreviewoutput.py11
-rw-r--r--voctocore/lib/commands.py67
-rw-r--r--voctocore/lib/config.py9
-rw-r--r--voctocore/lib/videomix.py2
-rwxr-xr-xvoctocore/voctocore.py7
10 files changed, 219 insertions, 9 deletions
diff --git a/example-scripts/voctomidi/README.md b/example-scripts/voctomidi/README.md
new file mode 100644
index 0000000..b171f8a
--- /dev/null
+++ b/example-scripts/voctomidi/README.md
@@ -0,0 +1,10 @@
+# Voctomidi - Control Voctocore from a MIDI controller
+
+## Configuration
+Set the `device` option in the `midi` section to the device
+you want to connect to. This can be either a device name, or port.
+If this is unset or the device can't be found a list of connected devices will
+be provided for you to choose from.
+
+In the `eventmap` section MIDI NOTE ON events are mapped to Voctocore layouts.
+The syntax is `<note>=<srcA> <srcB> <mode>`.
diff --git a/example-scripts/voctomidi/default-config.ini b/example-scripts/voctomidi/default-config.ini
new file mode 100644
index 0000000..8e68bf3
--- /dev/null
+++ b/example-scripts/voctomidi/default-config.ini
@@ -0,0 +1,19 @@
+[server]
+host=localhost
+
+[midi]
+device=nanoPAD
+
+# nanoPAD Layout:
+# Scene 1:
+# 39, 48, 45, 43, 51, 49,
+# 36, 38, 40, 42, 44, 46
+[eventmap]
+39=grabber cam1 side_by_side_preview
+48=grabber cam2 side_by_side_preview
+45=grabber cam1 side_by_side_equal
+43=grabber cam2 side_by_side_equal
+51=cam1 cam2 side_by_side_equal
+36=cam1 * fullscreen
+38=cam2 * fullscreen
+40=grabber * fullscreen
diff --git a/example-scripts/voctomidi/lib/config.py b/example-scripts/voctomidi/lib/config.py
new file mode 100644
index 0000000..44dd8da
--- /dev/null
+++ b/example-scripts/voctomidi/lib/config.py
@@ -0,0 +1,16 @@
+import os.path
+from configparser import SafeConfigParser
+
+__all__ = ['Config']
+
+modulepath = os.path.dirname(os.path.realpath(__file__))
+files = [
+ os.path.join(modulepath, '../default-config.ini'),
+ os.path.join(modulepath, '../config.ini'),
+ '/etc/voctomix/voctomidi.ini',
+ '/etc/voctomidi.ini',
+ os.path.expanduser('~/.voctomidi.ini'),
+]
+
+Config = SafeConfigParser()
+Config.read(files)
diff --git a/example-scripts/voctomidi/voctomidi.py b/example-scripts/voctomidi/voctomidi.py
new file mode 100755
index 0000000..b21f529
--- /dev/null
+++ b/example-scripts/voctomidi/voctomidi.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+import atexit
+import socket
+import sys
+import time
+from rtmidi.midiutil import open_midiport
+
+from lib.config import Config
+
+NOTE_ON = 144
+
+host = Config.get("server", "host")
+port = 9999
+
+device = Config.get("midi", "device")
+
+event_map = dict(map(lambda x: (int(x[0]), x[1]), Config.items("eventmap")))
+
+
+class MidiInputHandler(object):
+ def __init__(self, port):
+ self.port = port
+
+ def __call__(self, event, data=None):
+ global conn
+ message, _deltatime = event
+ if message[0] != NOTE_ON:
+ return
+ if message[1] in event_map:
+ note = message[1]
+ msg = "set_videos_and_composite " + event_map[note]
+ print("Sending: '{}'".format(msg))
+ try:
+ conn.sendall(msg.encode('ascii') + b"\n")
+ except BrokenPipeError:
+ print("voctocore disconnected, trying to reconnect")
+ try:
+ conn = socket.create_connection((host, port))
+ print("Reconnected to voctocore")
+ except:
+ pass
+ else:
+ print("[{}]: Unhandled NOTE ON event {}".format(self.port,
+ message[1]))
+
+
+@atexit.register
+def kthxbye():
+ print("Exit")
+
+conn, midiin = None, None
+
+try:
+ conn = socket.create_connection((host, port))
+except (ConnectionRefusedError, KeyboardInterrupt):
+ print("Could not connect to voctocore")
+ sys.exit()
+
+
+@atexit.register
+def close_conn():
+ global conn
+ conn and conn.close()
+
+try:
+ midiin, port_name = open_midiport(device)
+except (EOFError, KeyboardInterrupt):
+ print("Opening midi port failed")
+ sys.exit()
+
+
+@atexit.register
+def close_midi():
+ global midiin
+ midiin and midiin.close_port()
+ del midiin
+
+midiin.set_callback(MidiInputHandler(port_name))
+
+print("Entering main loop. Press Control-C to exit.")
+try:
+ # just wait for keyboard interrupt in main thread
+ while True:
+ time.sleep(1)
+except KeyboardInterrupt:
+ print("")
diff --git a/voctocore/default-config.ini b/voctocore/default-config.ini
index d5d89fa..31a49ff 100644
--- a/voctocore/default-config.ini
+++ b/voctocore/default-config.ini
@@ -60,6 +60,7 @@ mix_out=10000
[previews]
; disable if ui & server run on the same computer and can exchange uncompressed video frames
enabled=false
+deinterlace=false
; default to mix-videocaps, only applicable if enabled=true
; you can change the framerate and the width/height, but nothing else
diff --git a/voctocore/lib/avpreviewoutput.py b/voctocore/lib/avpreviewoutput.py
index a0bb951..d5c2c66 100644
--- a/voctocore/lib/avpreviewoutput.py
+++ b/voctocore/lib/avpreviewoutput.py
@@ -17,12 +17,14 @@ class AVPreviewOutput(TCPMultiConnection):
else:
vcaps_out = Config.get('mix', 'videocaps')
+ deinterlace = ""
+ if Config.getboolean('previews', 'deinterlace'):
+ deinterlace = "deinterlace mode=interlaced !"
+
pipeline = """
intervideosrc channel=video_{channel} !
{vcaps_in} !
- capssetter caps="video/x-raw,interlace-mode=interlaced" !
- deinterlace !
- video/x-raw,interlace-mode=progressive !
+ {deinterlace}
videoscale !
videorate !
{vcaps_out} !
@@ -49,7 +51,8 @@ class AVPreviewOutput(TCPMultiConnection):
channel=self.channel,
acaps=Config.get('mix', 'audiocaps'),
vcaps_in=Config.get('mix', 'videocaps'),
- vcaps_out=vcaps_out
+ vcaps_out=vcaps_out,
+ deinterlace=deinterlace
)
self.log.debug('Creating Output-Pipeline:\n%s', pipeline)
diff --git a/voctocore/lib/commands.py b/voctocore/lib/commands.py
index bfe3168..92db70e 100644
--- a/voctocore/lib/commands.py
+++ b/voctocore/lib/commands.py
@@ -1,5 +1,6 @@
import logging
import json
+import inspect
from lib.config import Config
from lib.videomix import CompositeModes
@@ -60,8 +61,54 @@ class ControlServerCommands(object):
# exceptions, they will be turned into messages outside.
def message(self, *args):
+ """sends a message through the control-server, which can be received by
+ user-defined scripts. does not change the state of the voctocore."""
return NotifyResponse('message', *args)
+ def help(self):
+ helplines = []
+
+ helplines.append("Commands:")
+ for name, func in ControlServerCommands.__dict__.items():
+ if name[0] == '_':
+ continue
+
+ if not func.__code__:
+ continue
+
+ params = inspect.signature(func).parameters
+ params = [str(info) for name, info in params.items()]
+ params = ', '.join(params[1:])
+
+ command_sig = '\t' + name
+
+ if params:
+ command_sig += ': '+params
+
+ if func.__doc__:
+ command_sig += '\n'+'\n'.join(
+ ['\t\t'+line.strip() for line in func.__doc__.splitlines()])+'\n'
+
+ helplines.append(command_sig)
+
+ helplines.append('\t'+'quit')
+
+ helplines.append("\n")
+ helplines.append("Source-Names:")
+ for source in self.sources:
+ helplines.append("\t"+source)
+
+ helplines.append("\n")
+ helplines.append("Stream-Blanker Sources-Names:")
+ for source in self.blankerSources:
+ helplines.append("\t"+source)
+
+ helplines.append("\n")
+ helplines.append("Composition-Modes:")
+ for mode in CompositeModes:
+ helplines.append("\t"+mode.name)
+
+ return OkResponse("\n".join(helplines))
def _get_video_status(self):
a = encodeName( self.sources, self.pipeline.vmix.getVideoSourceA() )
@@ -69,10 +116,15 @@ class ControlServerCommands(object):
return [a, b]
def get_video(self):
+ """gets the current video-status, consisting of the name of
+ video-source A and video-source B"""
status = self._get_video_status()
return OkResponse('video_status', *status)
def set_video_a(self, src_name_or_id):
+ """sets the video-source A to the supplied source-name or source-id,
+ swapping A and B if the supplied source is currently used as
+ video-source B"""
src_id = decodeName(self.sources, src_name_or_id)
self.pipeline.vmix.setVideoSourceA(src_id)
@@ -80,6 +132,9 @@ class ControlServerCommands(object):
return NotifyResponse('video_status', *status)
def set_video_b(self, src_name_or_id):
+ """sets the video-source B to the supplied source-name or source-id,
+ swapping A and B if the supplied source is currently used as
+ video-source A"""
src_id = decodeName(self.sources, src_name_or_id)
self.pipeline.vmix.setVideoSourceB(src_id)
@@ -92,10 +147,12 @@ class ControlServerCommands(object):
return encodeName(self.sources, src_id)
def get_audio(self):
+ """gets the name of the current audio-source"""
status = self._get_audio_status()
return OkResponse('audio_status', status)
def set_audio(self, src_name_or_id):
+ """sets the audio-source to the supplied source-name or source-id"""
src_id = decodeName(self.sources, src_name_or_id)
self.pipeline.amix.setAudioSource(src_id)
@@ -108,10 +165,12 @@ class ControlServerCommands(object):
return encodeEnumName(CompositeModes, mode)
def get_composite_mode(self):
+ """gets the name of the current composite-mode"""
status = self._get_composite_status()
return OkResponse('composite_mode', status)
def set_composite_mode(self, mode_name_or_id):
+ """sets the name of the id of the composite-mode"""
mode = decodeEnumName(CompositeModes, mode_name_or_id)
self.pipeline.vmix.setCompositeMode(mode)
@@ -122,7 +181,10 @@ class ControlServerCommands(object):
NotifyResponse('video_status', *video_status)
]
+
def set_videos_and_composite(self, src_a_name_or_id, src_b_name_or_id, mode_name_or_id):
+ """sets the A- and the B-source synchronously with the composition-mode
+ all parametets can be set to "*" which will leave them unchanged."""
if src_a_name_or_id != '*':
src_a_id = decodeName(self.sources, src_a_name_or_id)
self.pipeline.vmix.setVideoSourceA(src_a_id)
@@ -152,10 +214,13 @@ class ControlServerCommands(object):
return 'blank', encodeName(self.blankerSources, blankSource)
def get_stream_status(self):
+ """gets the current streamblanker-status"""
status = self._get_stream_status()
return OkResponse('stream_status', *status)
def set_stream_blank(self, source_name_or_id):
+ """sets the streamblanker-status to blank with the specified
+ blanker-source-name or -id"""
src_id = decodeName(self.blankerSources, source_name_or_id)
self.pipeline.streamblanker.setBlankSource(src_id)
@@ -163,6 +228,7 @@ class ControlServerCommands(object):
return NotifyResponse('stream_status', *status)
def set_stream_live(self):
+ """sets the streamblanker-status to live"""
self.pipeline.streamblanker.setBlankSource(None)
status = self._get_stream_status()
@@ -170,5 +236,6 @@ class ControlServerCommands(object):
def get_config(self):
+ """returns the parsed server-config"""
confdict = {header: dict(section) for header, section in dict(Config).items()}
return OkResponse('server_config', json.dumps(confdict))
diff --git a/voctocore/lib/config.py b/voctocore/lib/config.py
index 3075f61..df0ff9a 100644
--- a/voctocore/lib/config.py
+++ b/voctocore/lib/config.py
@@ -1,4 +1,5 @@
import os.path
+import logging
from configparser import SafeConfigParser
from lib.args import Args
@@ -23,4 +24,10 @@ if Args.ini_file is not None:
files.append(Args.ini_file)
Config = SafeConfigParser()
-Config.read(files)
+readfiles = Config.read(files)
+
+log = logging.getLogger('ConfigParser')
+log.debug('considered config-files: \n%s',
+ "\n".join(["\t\t"+os.path.normpath(file) for file in files]) )
+log.debug('successfully parsed config-files: \n%s',
+ "\n".join(["\t\t"+os.path.normpath(file) for file in readfiles]) )
diff --git a/voctocore/lib/videomix.py b/voctocore/lib/videomix.py
index aa2bedc..95d538d 100644
--- a/voctocore/lib/videomix.py
+++ b/voctocore/lib/videomix.py
@@ -325,7 +325,6 @@ class VideoMix(object):
self.setVideoSourceA(self.names.index(defSource))
self.log.info('Changing sourceA to default of Mode %s: %s', compositeModeName, defSource)
except Exception as e:
- print(e)
pass
try:
@@ -333,7 +332,6 @@ class VideoMix(object):
self.setVideoSourceB(self.names.index(defSource))
self.log.info('Changing sourceB to default of Mode %s: %s', compositeModeName, defSource)
except Exception as e:
- print(e)
pass
def on_handoff(self, object, buffer):
diff --git a/voctocore/voctocore.py b/voctocore/voctocore.py
index fa712f7..1d04c4f 100755
--- a/voctocore/voctocore.py
+++ b/voctocore/voctocore.py
@@ -23,13 +23,16 @@ GObject.threads_init()
# import local classes
from lib.args import Args
-from lib.pipeline import Pipeline
-from lib.controlserver import ControlServer
from lib.loghandler import LogHandler
# main class
class Voctocore(object):
def __init__(self):
+ # import local which use the config or the logging system
+ # this is required, so that we can cnfigure logging, before reading the config
+ from lib.pipeline import Pipeline
+ from lib.controlserver import ControlServer
+
self.log = logging.getLogger('Voctocore')
self.log.debug('creating GObject-MainLoop')
self.mainloop = GObject.MainLoop()