bot.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import argparse
  2. import audioop
  3. import logging
  4. import sys
  5. import time
  6. from functools import partial
  7. import pymumble_py3 as pymumble
  8. from pymumble_py3.constants import PYMUMBLE_CLBK_TEXTMESSAGERECEIVED
  9. is_streaming = False
  10. silence_time = 0
  11. def message_received(mumble, message):
  12. global is_streaming
  13. global silence_time
  14. command = message.message
  15. if command == "/start":
  16. is_streaming = True
  17. silence_time = 0
  18. mumble.my_channel().send_text_message("Diretta iniziata")
  19. logging.info("Diretta iniziata")
  20. mumble.users.myself.recording()
  21. elif command == "/stop":
  22. is_streaming = False
  23. mumble.my_channel().send_text_message("Diretta terminata")
  24. logging.info("Diretta terminata")
  25. mumble.users.myself.unrecording()
  26. def get_parser():
  27. parser = argparse.ArgumentParser(description="Regia pienamente automatizzata")
  28. parser.add_argument("--channel", help="Set channel", default="")
  29. parser.add_argument("--name", help="Set bot nickname", default="RadioRobbot")
  30. parser.add_argument("--server", help="Set server", default="mumble.esiliati.org")
  31. parser.add_argument("--password", help="Set password", default="")
  32. parser.add_argument("--port", help="Set port", type=int, default=64738)
  33. parser.add_argument(
  34. "--stream",
  35. action="store_true",
  36. help="Ignore commands in chat and stream everything",
  37. )
  38. parser.add_argument(
  39. "--auto-suspend-stream",
  40. action="store_true",
  41. help="Ignore commands in chat and stream everything",
  42. )
  43. parser.add_argument(
  44. "--max-silence-time", type=int, help="max silence time in seconds", default=30
  45. )
  46. parser.add_argument(
  47. "--tokens",
  48. help="Set tokens list",
  49. nargs="*"
  50. )
  51. parser.add_argument(
  52. "--ignore",
  53. help="List of ignored users",
  54. nargs="*",
  55. default=[]
  56. )
  57. return parser
  58. def main():
  59. global is_streaming
  60. global silence_time
  61. args = get_parser().parse_args()
  62. logging.basicConfig(level=logging.DEBUG)
  63. pwd = args.password
  64. server = args.server
  65. nick = args.name
  66. channel = args.channel
  67. port = args.port
  68. tokens = args.tokens
  69. is_streaming = False
  70. stream_always = args.stream
  71. ignored_users = args.ignore
  72. # Spin up a client and connect to mumble server
  73. mumble = pymumble.Mumble(server, nick, password=pwd, port=port, tokens=tokens)
  74. mumble.callbacks.set_callback(
  75. PYMUMBLE_CLBK_TEXTMESSAGERECEIVED, partial(message_received, mumble)
  76. )
  77. mumble.set_receive_sound(1) # Enable receiving sound from mumble server
  78. mumble.start()
  79. mumble.is_ready() # Wait for client is ready
  80. mumble.channels.find_by_tree(channel.split('/')).move_in()
  81. mumble.users.myself.mute()
  82. if is_streaming:
  83. mumble.users.myself.recording()
  84. BUFFER = 0.1
  85. BITRATE = 48000
  86. RESOLUTION = 10 # in ms
  87. FLOAT_RESOLUTION = float(RESOLUTION) / 1000
  88. MONO_CHUNK_SIZE = BITRATE * 2 * RESOLUTION / 1000
  89. STEREO_CHUNK_SIZE = MONO_CHUNK_SIZE * 2
  90. silent = b"\x00" * int(STEREO_CHUNK_SIZE)
  91. cursor_time = time.time() - BUFFER
  92. while mumble.is_alive():
  93. if cursor_time < time.time() - BUFFER:
  94. base_sound = None
  95. try:
  96. for user in mumble.users.values(): # check the audio queue of each user
  97. if user.sound.is_sound() and user["name"] not in ignored_users:
  98. # available sound is to be treated now and not later
  99. sound = user.sound.get_sound(FLOAT_RESOLUTION)
  100. stereo_pcm = audioop.tostereo(sound.pcm, 2, 1, 1)
  101. if base_sound is None:
  102. base_sound = stereo_pcm
  103. else:
  104. base_sound = audioop.add(base_sound, stereo_pcm, 2)
  105. except RuntimeError:
  106. print("ignored exception in stderr...", file=sys.stderr)
  107. if is_streaming or stream_always:
  108. if base_sound:
  109. silence_time = 0
  110. sys.stdout.buffer.write(base_sound)
  111. else:
  112. silence_time += RESOLUTION
  113. sys.stdout.buffer.write(silent)
  114. if (
  115. args.auto_suspend_stream
  116. and (silence_time >= args.max_silence_time * 1000)
  117. and is_streaming
  118. ):
  119. is_streaming = False
  120. logging.info("max-silence-time reached")
  121. mumble.my_channel().send_text_message(
  122. "Diretta terminata in automatico dopo %d secondi circa di silenzio"
  123. % args.max_silence_time
  124. )
  125. mumble.users.myself.unrecording()
  126. cursor_time += FLOAT_RESOLUTION
  127. else:
  128. time.sleep(FLOAT_RESOLUTION)
  129. if __name__ == "__main__":
  130. main()