bot.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import argparse
  2. import audioop
  3. import logging
  4. import math
  5. import sys
  6. import struct
  7. import time
  8. import threading
  9. from functools import partial
  10. import pymumble_py3 as pymumble
  11. from pymumble_py3.constants import PYMUMBLE_CLBK_TEXTMESSAGERECEIVED
  12. is_streaming = False
  13. silence_time = 0
  14. SHORT_NORMALIZE = (1.0/32768.0)
  15. ## peak detection
  16. def get_rms( block ):
  17. # RMS amplitude is defined as the square root of the
  18. # mean over time of the square of the amplitude.
  19. # so we need to convert this string of bytes into
  20. # a string of 16-bit samples...
  21. # we will get one short out for each
  22. # two chars in the string.
  23. count = len(block)/2
  24. format = "%dh"%(count)
  25. shorts = struct.unpack( format, block )
  26. # iterate over the block.
  27. sum_squares = 0.0
  28. for sample in shorts:
  29. # sample is a signed short in +/- 32768.
  30. # normalize it to 1.0
  31. n = sample * SHORT_NORMALIZE
  32. sum_squares += n*n
  33. return math.sqrt( sum_squares / count )
  34. def message_received(mumble, message):
  35. global is_streaming
  36. global silence_time
  37. command = message.message
  38. if command == "/start":
  39. is_streaming = True
  40. silence_time = 0
  41. mumble.my_channel().send_text_message("Diretta iniziata")
  42. logging.info("Diretta iniziata")
  43. mumble.users.myself.recording()
  44. elif command == "/stop":
  45. is_streaming = False
  46. mumble.my_channel().send_text_message("Diretta terminata")
  47. logging.info("Diretta terminata")
  48. mumble.users.myself.unrecording()
  49. def get_parser():
  50. parser = argparse.ArgumentParser(description="Regia pienamente automatizzata")
  51. parser.add_argument("--channel", help="Set channel", default="")
  52. parser.add_argument("--name", help="Set bot nickname", default="RadioRobbot")
  53. parser.add_argument("--server", help="Set server", default="mumble.esiliati.org")
  54. parser.add_argument("--password", help="Set password", default="")
  55. parser.add_argument("--port", help="Set port", type=int, default=64738)
  56. parser.add_argument(
  57. "--stream",
  58. action="store_true",
  59. help="Ignore commands in chat and stream everything",
  60. )
  61. parser.add_argument(
  62. "--transmit",
  63. action="store_true",
  64. help="Transmit from your stdin",
  65. )
  66. parser.add_argument(
  67. "--auto-suspend-stream",
  68. action="store_true",
  69. help="Ignore commands in chat and stream everything",
  70. )
  71. parser.add_argument(
  72. "--max-silence-time", type=int, help="max silence time in seconds", default=30
  73. )
  74. parser.add_argument(
  75. "--tokens",
  76. help="Set tokens list",
  77. nargs="*"
  78. )
  79. parser.add_argument(
  80. "--ignore",
  81. help="List of ignored users",
  82. nargs="*",
  83. default=[]
  84. )
  85. return parser
  86. def transmit(name, mumble, chunk_size):
  87. buffer = 0
  88. while True:
  89. try:
  90. data = sys.stdin.buffer.read(int(chunk_size))
  91. if(get_rms(data) > 0.09):
  92. mumble.sound_output.add_sound(data)
  93. buffer = 1000
  94. else:
  95. # keep the audio open for one second
  96. if(buffer >= 0):
  97. mumble.sound_output.add_sound(data)
  98. buffer -= 10
  99. except IOError as e:
  100. print( " Error recording: %s"3%e 0)
  101. def main():
  102. global is_streaming
  103. global silence_time
  104. args = get_parser().parse_args()
  105. logging.basicConfig(level=logging.DEBUG)
  106. pwd = args.password
  107. server = args.server
  108. nick = args.name
  109. channel = args.channel
  110. port = args.port
  111. tokens = args.tokens
  112. is_streaming = False
  113. stream_always = args.stream
  114. ignored_users = args.ignore
  115. # Spin up a client and connect to mumble server
  116. mumble = pymumble.Mumble(server, nick, password=pwd, port=port, tokens=tokens)
  117. mumble.callbacks.set_callback(
  118. PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, partial(message_received, mumble)
  119. )
  120. mumble.set_receive_sound(1) # Enable receiving sound from mumble server
  121. mumble.start()
  122. mumble.is_ready() # Wait for client is ready
  123. mumble.channels.find_by_tree(channel.split('/')).move_in()
  124. if not args.transmit:
  125. mumble.users.myself.mute()
  126. if is_streaming:
  127. mumble.users.myself.recording()
  128. BUFFER = 0.1
  129. BITRATE = 48000
  130. RESOLUTION = 10 # in ms
  131. FLOAT_RESOLUTION = float(RESOLUTION) / 1000
  132. MONO_CHUNK_SIZE = BITRATE * 2 * RESOLUTION / 1000
  133. STEREO_CHUNK_SIZE = MONO_CHUNK_SIZE * 2
  134. silent = b"\x00" * int(STEREO_CHUNK_SIZE)
  135. cursor_time = time.time() - BUFFER
  136. if args.transmit:
  137. transmit_thread = threading.Thread(target=transmit, args=(1,mumble, MONO_CHUNK_SIZE))
  138. transmit_thread.start()
  139. while mumble.is_alive():
  140. if cursor_time < time.time() - BUFFER:
  141. base_sound = None
  142. try:
  143. for user in mumble.users.values(): # check the audio queue of each user
  144. if user.sound.is_sound() and user["name"] not in ignored_users:
  145. # available sound is to be treated now and not later
  146. sound = user.sound.get_sound(FLOAT_RESOLUTION)
  147. stereo_pcm = audioop.tostereo(sound.pcm, 2, 1, 1)
  148. if base_sound is None:
  149. base_sound = stereo_pcm
  150. else:
  151. base_sound = audioop.add(base_sound, stereo_pcm, 2)
  152. except RuntimeError:
  153. print("ignored exception in stderr...", file=sys.stderr)
  154. if is_streaming or stream_always:
  155. if base_sound:
  156. silence_time = 0
  157. sys.stdout.buffer.write(base_sound)
  158. else:
  159. silence_time += RESOLUTION
  160. sys.stdout.buffer.write(silent)
  161. if (
  162. args.auto_suspend_stream
  163. and (silence_time >= args.max_silence_time * 1000)
  164. and is_streaming
  165. ):
  166. is_streaming = False
  167. logging.info("max-silence-time reached")
  168. mumble.my_channel().send_text_message(
  169. "Diretta terminata in automatico dopo %d secondi circa di silenzio"
  170. % args.max_silence_time
  171. )
  172. mumble.users.myself.unrecording()
  173. cursor_time += FLOAT_RESOLUTION
  174. else:
  175. time.sleep(FLOAT_RESOLUTION)
  176. if __name__ == "__main__":
  177. main()