aboutsummaryrefslogtreecommitdiff
path: root/voctocore/lib/commands.py
blob: 34590b70ab48be1d26821ace185e71478a90f81a (plain)
  1. import logging
  2. import json
  3. import inspect
  4. from lib.config import Config
  5. from lib.videomix import CompositeModes
  6. from lib.response import NotifyResponse, OkResponse
  7. def decodeName(items, name_or_id):
  8. try:
  9. name_or_id = int(name_or_id)
  10. if name_or_id < 0 or name_or_id >= len(items):
  11. raise IndexError("unknown index %d" % name_or_id)
  12. return name_or_id
  13. except ValueError as e:
  14. try:
  15. return items.index(name_or_id)
  16. except ValueError as e:
  17. raise IndexError("unknown name %s" % name_or_id)
  18. def decodeEnumName(enum, name_or_id):
  19. try:
  20. name_or_id = int(name_or_id)
  21. if name_or_id < 0 or name_or_id >= len(enum):
  22. raise IndexError("unknown index %d" % name_or_id)
  23. return name_or_id
  24. except ValueError as e:
  25. try:
  26. return enum[name_or_id]
  27. except KeyError as e:
  28. raise IndexError("unknown name %s" % name_or_id)
  29. def encodeName(items, id):
  30. try:
  31. return items[id]
  32. except IndexError as e:
  33. raise IndexError("unknown index %d" % id)
  34. def encodeEnumName(enum, id):
  35. try:
  36. return enum(id).name
  37. except ValueError as e:
  38. raise IndexError("unknown index %d" % id)
  39. class ControlServerCommands(object):
  40. def __init__(self, pipeline):
  41. self.log = logging.getLogger('ControlServerCommands')
  42. self.pipeline = pipeline
  43. self.sources = Config.getlist('mix', 'sources')
  44. self.blankerSources = Config.getlist('stream-blanker', 'sources')
  45. # Commands are defined below. Errors are sent to the clients by throwing
  46. # exceptions, they will be turned into messages outside.
  47. def message(self, *args):
  48. """sends a message through the control-server, which can be received by
  49. user-defined scripts. does not change the state of the voctocore."""
  50. return NotifyResponse('message', *args)
  51. def help(self):
  52. helplines = []
  53. helplines.append("Commands:")
  54. for name, func in ControlServerCommands.__dict__.items():
  55. if name[0] == '_':
  56. continue
  57. if not func.__code__:
  58. continue
  59. params = inspect.signature(func).parameters
  60. params = [str(info) for name, info in params.items()]
  61. params = ', '.join(params[1:])
  62. command_sig = '\t' + name
  63. if params:
  64. command_sig += ': ' + params
  65. if func.__doc__:
  66. command_sig += '\n\t\t{}\n'.format('\n\t\t'.join(
  67. [line.strip() for line in func.__doc__.splitlines()]
  68. ))
  69. helplines.append(command_sig)
  70. helplines.append('\t' + 'quit / exit')
  71. helplines.append("\n")
  72. helplines.append("Source-Names:")
  73. for source in self.sources:
  74. helplines.append("\t" + source)
  75. helplines.append("\n")
  76. helplines.append("Stream-Blanker Sources-Names:")
  77. for source in self.blankerSources:
  78. helplines.append("\t" + source)
  79. helplines.append("\n")
  80. helplines.append("Composition-Modes:")
  81. for mode in CompositeModes:
  82. helplines.append("\t" + mode.name)
  83. return OkResponse("\n".join(helplines))
  84. def _get_video_status(self):
  85. a = encodeName(self.sources, self.pipeline.vmix.getVideoSourceA())
  86. b = encodeName(self.sources, self.pipeline.vmix.getVideoSourceB())
  87. return [a, b]
  88. def get_video(self):
  89. """gets the current video-status, consisting of the name of
  90. video-source A and video-source B"""
  91. status = self._get_video_status()
  92. return OkResponse('video_status', *status)
  93. def set_video_a(self, src_name_or_id):
  94. """sets the video-source A to the supplied source-name or source-id,
  95. swapping A and B if the supplied source is currently used as
  96. video-source B"""
  97. src_id = decodeName(self.sources, src_name_or_id)
  98. self.pipeline.vmix.setVideoSourceA(src_id)
  99. status = self._get_video_status()
  100. return NotifyResponse('video_status', *status)
  101. def set_video_b(self, src_name_or_id):
  102. """sets the video-source B to the supplied source-name or source-id,
  103. swapping A and B if the supplied source is currently used as
  104. video-source A"""
  105. src_id = decodeName(self.sources, src_name_or_id)
  106. self.pipeline.vmix.setVideoSourceB(src_id)
  107. status = self._get_video_status()
  108. return NotifyResponse('video_status', *status)
  109. def _get_audio_status(self):
  110. src_id = self.pipeline.amix.getAudioSource()
  111. return encodeName(self.sources, src_id)
  112. def get_audio(self):
  113. """gets the name of the current audio-source"""
  114. status = self._get_audio_status()
  115. return OkResponse('audio_status', status)
  116. def set_audio(self, src_name_or_id):
  117. """sets the audio-source to the supplied source-name or source-id"""
  118. src_id = decodeName(self.sources, src_name_or_id)
  119. self.pipeline.amix.setAudioSource(src_id)
  120. status = self._get_audio_status()
  121. return NotifyResponse('audio_status', status)
  122. def _get_composite_status(self):
  123. mode = self.pipeline.vmix.getCompositeMode()
  124. return encodeEnumName(CompositeModes, mode)
  125. def get_composite_mode(self):
  126. """gets the name of the current composite-mode"""
  127. status = self._get_composite_status()
  128. return OkResponse('composite_mode', status)
  129. def set_composite_mode(self, mode_name_or_id):
  130. """sets the name of the id of the composite-mode"""
  131. mode = decodeEnumName(CompositeModes, mode_name_or_id)
  132. self.pipeline.vmix.setCompositeMode(mode)
  133. composite_status = self._get_composite_status()
  134. video_status = self._get_video_status()
  135. return [
  136. NotifyResponse('composite_mode', composite_status),
  137. NotifyResponse('video_status', *video_status)
  138. ]
  139. def set_videos_and_composite(self, src_a_name_or_id, src_b_name_or_id,
  140. mode_name_or_id):
  141. """sets the A- and the B-source synchronously with the composition-mode
  142. all parametets can be set to "*" which will leave them unchanged."""
  143. if src_a_name_or_id != '*':
  144. src_a_id = decodeName(self.sources, src_a_name_or_id)
  145. self.pipeline.vmix.setVideoSourceA(src_a_id)
  146. if src_b_name_or_id != '*':
  147. src_b_id = decodeName(self.sources, src_b_name_or_id)
  148. self.pipeline.vmix.setVideoSourceB(src_b_id)
  149. if mode_name_or_id != '*':
  150. mode = decodeEnumName(CompositeModes, mode_name_or_id)
  151. self.pipeline.vmix.setCompositeMode(mode)
  152. composite_status = self._get_composite_status()
  153. video_status = self._get_video_status()
  154. return [
  155. NotifyResponse('composite_mode', composite_status),
  156. NotifyResponse('video_status', *video_status)
  157. ]
  158. def _get_stream_status(self):
  159. blankSource = self.pipeline.streamblanker.blankSource
  160. if blankSource is None:
  161. return ('live',)
  162. return 'blank', encodeName(self.blankerSources, blankSource)
  163. def get_stream_status(self):
  164. """gets the current streamblanker-status"""
  165. status = self._get_stream_status()
  166. return OkResponse('stream_status', *status)
  167. def set_stream_blank(self, source_name_or_id):
  168. """sets the streamblanker-status to blank with the specified
  169. blanker-source-name or -id"""
  170. src_id = decodeName(self.blankerSources, source_name_or_id)
  171. self.pipeline.streamblanker.setBlankSource(src_id)
  172. status = self._get_stream_status()
  173. return NotifyResponse('stream_status', *status)
  174. def set_stream_live(self):
  175. """sets the streamblanker-status to live"""
  176. self.pipeline.streamblanker.setBlankSource(None)
  177. status = self._get_stream_status()
  178. return NotifyResponse('stream_status', *status)
  179. def get_config(self):
  180. """returns the parsed server-config"""
  181. confdict = {header: dict(section)
  182. for header, section in dict(Config).items()}
  183. return OkResponse('server_config', json.dumps(confdict))