lenny.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*-
  3. import audioop
  4. import contextlib
  5. import glob
  6. import io
  7. import os
  8. import subprocess
  9. import syslog
  10. import threading
  11. import time
  12. import wave
  13. from datetime import datetime
  14. import linphone
  15. from enum import Enum
  16. import threading
  17. VOLUME_THRESHOLD = 100
  18. class ConversationStatus(Enum):
  19. READY_TO_TALK = 0
  20. IMTALKING = 1
  21. WAITFORANSWER = 2
  22. class Conversation(object):
  23. def __init__(self):
  24. self._status = ConversationStatus.READY_TO_TALK
  25. @property
  26. def status(self):
  27. return self._status
  28. @status.setter
  29. def status(self, value):
  30. if value != self._status:
  31. print(" New status : " + str(value))
  32. self._status = value
  33. current_dir = os.path.dirname(os.path.realpath(__file__))
  34. replies_seq = glob.glob(current_dir + "/replies/sequence/*.wav")
  35. replies_seq.sort()
  36. replies_generic = glob.glob(current_dir + "/replies/generic/*.wav")
  37. replies_generic.sort()
  38. incoming_stream_file = "/tmp/lenny"
  39. conversation = Conversation()
  40. replies_pos = 0
  41. THREADS_MUST_QUIT = False
  42. def log(msg):
  43. syslog.syslog(msg)
  44. print(msg)
  45. def get_wav_duration(fname):
  46. with contextlib.closing(wave.open(fname, 'r')) as f:
  47. frames = f.getnframes()
  48. rate = f.getframerate()
  49. return frames / float(rate)
  50. def sleep(duration):
  51. dummy_event = threading.Event()
  52. dummy_event.wait(timeout=duration)
  53. def say(core):
  54. global conversation
  55. global replies_pos
  56. if conversation.status is not ConversationStatus.IMTALKING:
  57. conversation.status = ConversationStatus.IMTALKING
  58. # On joue les repliques en sequence, puis quand
  59. # on arrive au bout, on en joue une au hasard
  60. # du groupe 'generic'
  61. voice_filename = replies_seq[replies_pos]
  62. replies_pos = (replies_pos + 1) % len(replies_seq)
  63. if replies_pos == 0:
  64. # On ne rejoue jamais la première réplique "allo"
  65. replies_pos = 1
  66. duration = get_wav_duration(voice_filename)
  67. print("Saying : " + voice_filename + "(duration : " + str(duration) + ")")
  68. core.play_file = voice_filename
  69. sleep(duration)
  70. core.play_file = ""
  71. # On laisse l'autre l'occassion de reparler
  72. conversation.status = ConversationStatus.WAITFORANSWER
  73. def call_state_changed(core, call, state, message):
  74. global conversation
  75. global replies_pos
  76. log("state changed : " + message)
  77. if state == linphone.CallState.Released:
  78. # Let's convert wav to mp3
  79. log("Converting output from wav to mp3")
  80. subprocess.call('lame --quiet --preset insane %s' % call.current_params.record_file, shell=True)
  81. os.remove(call.current_params.record_file)
  82. if state == linphone.CallState.IncomingReceived:
  83. log("Incoming call : {}".format(call.remote_address.username))
  84. replies_pos = 0
  85. if call.remote_address.username == "**620" or call.remote_address.username == "0765757624":
  86. print(" ... " + call.remote_address.username)
  87. #if call.remote_address.username == "0227570500":
  88. # print(" .. from Bernard !")
  89. call_params = core.create_call_params(call)
  90. call_params.record_file = current_dir + "/out/call_" + datetime.now().strftime(
  91. '%Y-%m-%d_%Hh%Mmn%Ss') + ".wav"
  92. # Let ring some time
  93. sleep(5)
  94. core.accept_call_with_params(call, call_params)
  95. call.start_recording()
  96. sleep(2)
  97. t = threading.Thread(target=incoming_stream_worker, args=[core, call])
  98. t.start()
  99. say(core)
  100. def global_state_changed(*args, **kwargs):
  101. log("global_state_changed: %r %r" % (args, kwargs))
  102. def registration_state_changed(core, call, state, message):
  103. log("registration_state_changed: " + str(state) + ", " + message)
  104. def incoming_stream_worker(core, call):
  105. global conversation
  106. print("Worker is starting")
  107. f = open(incoming_stream_file, "rb")
  108. f.seek(0, io.SEEK_END)
  109. p = f.tell()
  110. buf = ''
  111. previous_status = conversation.status
  112. while call.state is not linphone.CallState.End and not THREADS_MUST_QUIT:
  113. if conversation.status is ConversationStatus.IMTALKING:
  114. f.seek(0, io.SEEK_END)
  115. p = f.tell()
  116. else:
  117. if previous_status != conversation.status:
  118. f.seek(0, io.SEEK_END)
  119. p = f.tell()
  120. f.seek(p)
  121. buf += f.read(4096)
  122. p = f.tell()
  123. if len(buf) >= 20000:
  124. volume = audioop.rms(buf, 2)
  125. print("Detected volume : " + str(volume))
  126. #print("State : " + str(conversation.status))
  127. buf = ''
  128. if volume < VOLUME_THRESHOLD:
  129. if conversation.status is ConversationStatus.READY_TO_TALK:
  130. threading.Thread(target=say, args=[core]).start()
  131. else:
  132. conversation.status = ConversationStatus.READY_TO_TALK
  133. # We must sleep a bit to avoid cpu hog
  134. sleep(0.01)
  135. previous_status = conversation.status
  136. print("Worker is quitting")
  137. def main():
  138. log("lenny is starting ...")
  139. callbacks = {
  140. 'call_state_changed': call_state_changed,
  141. 'registration_state_changed': registration_state_changed,
  142. 'global_state_changed': global_state_changed,
  143. }
  144. username = "621"
  145. password = "toto"
  146. port = "5060"
  147. domain = "192.168.1.1"
  148. core = linphone.Core.new(callbacks, None, None)
  149. # On fait le setup pour la capture et analyse du stream entrant
  150. os.system("rm -rf " + incoming_stream_file)
  151. os.system("touch " + incoming_stream_file)
  152. core.use_files = True
  153. core.record_file = incoming_stream_file
  154. proxy_cfg = core.create_proxy_config()
  155. proxy_cfg.identity_address = core.create_address('sip:' + username + '@' + domain + ':' + port)
  156. proxy_cfg.server_addr = 'sip:' + domain + ':' + port
  157. proxy_cfg.register_enabled = True
  158. core.add_proxy_config(proxy_cfg)
  159. auth_info = core.create_auth_info(username, None, password, None, None, domain)
  160. core.add_auth_info(auth_info)
  161. while True:
  162. sleep(0.03)
  163. core.iterate()
  164. log("callblocker quitting.")
  165. if __name__ == "__main__":
  166. try:
  167. main()
  168. except KeyboardInterrupt:
  169. THREADS_MUST_QUIT = True
  170. print "Bye"
  171. # - Hi its lenny
  172. # - Hmmm, sorry, I can barely here you there
  173. # - Yes, yes, yes
  174. # - Oh good, yes, yes, yes
  175. # - Hmm, mais il me semble que quelqu'un a appelé pour ça la semaine passée ... c'était vous ?
  176. # - Yeah, pouvez-vous me rappeler votre nom ?
  177. # - Ca tombe bien que vous m'appeliez, car justement ma nièce me parlait de la même chose l'autre jour.
  178. # j'écoute d'ailleurs toujours attentivement son avis, car vous savez, c'est la première de la famille
  179. # qui est allé à l'Université. Et elle l'a terminé avec mention, nous en sommes très fière.
  180. # et du coup elle m'a dit que je devrais jeter un oeil à ce dont vous me parlez.
  181. # - Hmm, désolé, j'ai pas tout à fait compris, vous pouvez répéter ?
  182. # - Heu, oui, vous pouvez me rappeler de la part de quelle entreprise vous appelez
  183. # - Oui, en fait y'a une chose ... heuu, car j'avais eu un appel comme le votre, j'ai eu pas mal
  184. # de problème avec le personnel ici, qui m'on fait la morale, car je me suis retrouvé avec des affaires
  185. # dont je n'avais pas besoin, et ma nièce justement m'a fait la morale également ... vous savez ce que c'est
  186. # la famille ....
  187. # - Could you say that again please ?
  188. # Idées :
  189. # - En tout cas vous me semblez une personne fort sympathique, c'est toujours agréable de discute
  190. # des personnes sympathique, quand on est une personne seule comme moi ... heuu, en fait on parlait
  191. # de quoi déjà ?
  192. # - Bon,