|
@@ -13,9 +13,12 @@ import threading
|
|
|
import urllib2
|
|
|
import wave
|
|
|
from datetime import datetime
|
|
|
+import tempfile
|
|
|
|
|
|
import linphone
|
|
|
from enum import Enum
|
|
|
+import signal
|
|
|
+import sys
|
|
|
|
|
|
VOLUME_THRESHOLD = 100
|
|
|
|
|
@@ -58,12 +61,6 @@ replies_seq.sort()
|
|
|
replies_generic = glob.glob(current_dir + "/replies/generic/*.wav")
|
|
|
replies_generic.sort()
|
|
|
|
|
|
-incoming_stream_file = "/tmp/lenny"
|
|
|
-
|
|
|
-conversation = Conversation()
|
|
|
-
|
|
|
-replies_pos = 0
|
|
|
-
|
|
|
THREADS_MUST_QUIT = False
|
|
|
|
|
|
KTIP_LOOKUP_URL = "https://www.ktipp.ch/service/warnlisten/detail/?warnliste_id=7&ajax=ajax-search-form&keyword={" \
|
|
@@ -132,161 +129,185 @@ def sleep(duration):
|
|
|
dummy_event.wait(timeout=duration)
|
|
|
|
|
|
|
|
|
-def say(core):
|
|
|
- global conversation
|
|
|
- global replies_pos
|
|
|
- if conversation.status is not ConversationStatus.IMTALKING:
|
|
|
- conversation.status = ConversationStatus.IMTALKING
|
|
|
+class SipConnection(object):
|
|
|
+ def say(self, core):
|
|
|
+ if self._conversation.status is not ConversationStatus.IMTALKING:
|
|
|
+ self._conversation.status = ConversationStatus.IMTALKING
|
|
|
|
|
|
- # On joue les repliques en sequence, puis quand
|
|
|
- # on arrive au bout, on en joue une au hasard
|
|
|
- # du groupe 'generic'
|
|
|
+ # On joue les repliques en sequence, puis quand
|
|
|
+ # on arrive au bout, on en joue une au hasard
|
|
|
+ # du groupe 'generic'
|
|
|
|
|
|
- voice_filename = replies_seq[replies_pos]
|
|
|
- replies_pos = (replies_pos + 1) % len(replies_seq)
|
|
|
- if replies_pos == 0:
|
|
|
- # On ne rejoue jamais la première réplique "allo"
|
|
|
- replies_pos = 1
|
|
|
+ voice_filename = replies_seq[self._replies_pos]
|
|
|
+ self._replies_pos = (self._replies_pos + 1) % len(replies_seq)
|
|
|
+ if self._replies_pos == 0:
|
|
|
+ # On ne rejoue jamais la première réplique "allo"
|
|
|
+ self._replies_pos = 1
|
|
|
|
|
|
- duration = get_wav_duration(voice_filename)
|
|
|
- log("Saying : " + voice_filename + "(duration : " + str(duration) + ")")
|
|
|
+ duration = get_wav_duration(voice_filename)
|
|
|
+ log("Saying : " + voice_filename + "(duration : " + str(duration) + ")")
|
|
|
|
|
|
- core.play_file = voice_filename
|
|
|
+ core.play_file = voice_filename
|
|
|
|
|
|
- sleep(duration)
|
|
|
- core.play_file = ""
|
|
|
+ sleep(duration)
|
|
|
+ core.play_file = ""
|
|
|
|
|
|
- # On laisse l'autre l'occassion de reparler
|
|
|
- conversation.status = ConversationStatus.WAITFORANSWER
|
|
|
+ # On laisse l'autre l'occassion de reparler
|
|
|
+ self._conversation.status = ConversationStatus.WAITFORANSWER
|
|
|
|
|
|
+ def incoming_stream_worker(self, core, call):
|
|
|
+ log("Worker is starting")
|
|
|
|
|
|
-def call_state_changed(core, call, state, message):
|
|
|
- global conversation
|
|
|
- global replies_pos
|
|
|
- log("state changed : " + message)
|
|
|
+ f = open(self._incoming_stream_file, "rb")
|
|
|
+ f.seek(0, io.SEEK_END)
|
|
|
+ p = f.tell()
|
|
|
+ buf = ''
|
|
|
|
|
|
- if state == linphone.CallState.Released:
|
|
|
- # Let's convert wav to mp3
|
|
|
- log("Converting output from wav to mp3")
|
|
|
- if call.current_params.record_file is not None and os.path.isfile(call.current_params.record_file):
|
|
|
- subprocess.call('lame --quiet --preset insane %s' % call.current_params.record_file, shell=True)
|
|
|
- os.remove(call.current_params.record_file)
|
|
|
+ previous_status = self._conversation.status
|
|
|
|
|
|
- if state == linphone.CallState.IncomingReceived:
|
|
|
- log("Incoming call : {}".format(call.remote_address.username))
|
|
|
+ while call.state is not linphone.CallState.End and not self._is_quitting:
|
|
|
+ if self._conversation.status is ConversationStatus.IMTALKING:
|
|
|
+ f.seek(0, io.SEEK_END)
|
|
|
+ p = f.tell()
|
|
|
+ else:
|
|
|
|
|
|
- replies_pos = 0
|
|
|
+ if previous_status != self._conversation.status:
|
|
|
+ f.seek(0, io.SEEK_END)
|
|
|
+ p = f.tell()
|
|
|
|
|
|
- if is_in_blacklists(call.remote_address.username):
|
|
|
- log("telemarketer calling : " + call.remote_address.username)
|
|
|
+ f.seek(p)
|
|
|
+ buf += f.read(4096)
|
|
|
+ p = f.tell()
|
|
|
|
|
|
- call_params = core.create_call_params(call)
|
|
|
- a_file = current_dir + "/out/call_from_" + slugify(call.remote_address.username) + \
|
|
|
- "_" + datetime.now().strftime(
|
|
|
- '%Y-%m-%d_%Hh%Mmn%Ss') + ".wav"
|
|
|
+ if len(buf) >= 20000:
|
|
|
+ volume = audioop.rms(buf, 2)
|
|
|
+ print("Detected volume : " + str(volume))
|
|
|
+ # print("State : " + str(conversation.status))
|
|
|
+ buf = ''
|
|
|
+ if volume < self._volume_threshold:
|
|
|
+ if self._conversation.status is ConversationStatus.READY_TO_TALK:
|
|
|
+ threading.Thread(target=self.say, args=[core]).start()
|
|
|
+ else:
|
|
|
+ self._conversation.status = ConversationStatus.READY_TO_TALK
|
|
|
|
|
|
- log(a_file)
|
|
|
+ # We must sleep a bit to avoid cpu hog
|
|
|
+ sleep(0.01)
|
|
|
+ previous_status = self._conversation.status
|
|
|
|
|
|
- call_params.record_file = a_file
|
|
|
+ log("Worker is quitting")
|
|
|
|
|
|
- # Let ring some time
|
|
|
- sleep(5)
|
|
|
+ def call_state_changed(self, core, call, state, message):
|
|
|
+ log("state changed : " + message)
|
|
|
|
|
|
- core.accept_call_with_params(call, call_params)
|
|
|
- call.start_recording()
|
|
|
+ if state == linphone.CallState.Released:
|
|
|
+ # Let's convert wav to mp3
|
|
|
+ if call.current_params.record_file is not None and os.path.isfile(call.current_params.record_file):
|
|
|
+ log("Converting output from wav to mp3")
|
|
|
+ subprocess.call('lame --quiet --preset insane %s' % call.current_params.record_file, shell=True)
|
|
|
+ os.remove(call.current_params.record_file)
|
|
|
|
|
|
- sleep(2)
|
|
|
+ if state == linphone.CallState.IncomingReceived:
|
|
|
+ log("Incoming call : {}".format(call.remote_address.username))
|
|
|
|
|
|
- t = threading.Thread(target=incoming_stream_worker, args=[core, call])
|
|
|
- t.start()
|
|
|
+ self._replies_pos = 0
|
|
|
|
|
|
- say(core)
|
|
|
+ if is_in_blacklists(call.remote_address.username):
|
|
|
+ log("telemarketer calling : " + call.remote_address.username)
|
|
|
|
|
|
+ call_params = core.create_call_params(call)
|
|
|
+ a_file = current_dir + "/out/call_from_" + slugify(call.remote_address.username) + \
|
|
|
+ "_" + datetime.now().strftime(
|
|
|
+ '%Y-%m-%d_%Hh%Mmn%Ss') + ".wav"
|
|
|
|
|
|
-def incoming_stream_worker(core, call):
|
|
|
- global conversation
|
|
|
- log("Worker is starting")
|
|
|
+ log(a_file)
|
|
|
|
|
|
- f = open(incoming_stream_file, "rb")
|
|
|
- f.seek(0, io.SEEK_END)
|
|
|
- p = f.tell()
|
|
|
- buf = ''
|
|
|
+ call_params.record_file = a_file
|
|
|
|
|
|
- previous_status = conversation.status
|
|
|
+ # Let ring some time
|
|
|
+ sleep(4)
|
|
|
|
|
|
- while call.state is not linphone.CallState.End and not THREADS_MUST_QUIT:
|
|
|
- if conversation.status is ConversationStatus.IMTALKING:
|
|
|
- f.seek(0, io.SEEK_END)
|
|
|
- p = f.tell()
|
|
|
- else:
|
|
|
+ core.accept_call_with_params(call, call_params)
|
|
|
+ call.start_recording()
|
|
|
|
|
|
- if previous_status != conversation.status:
|
|
|
- f.seek(0, io.SEEK_END)
|
|
|
- p = f.tell()
|
|
|
+ sleep(2)
|
|
|
+
|
|
|
+ t = threading.Thread(target=self.incoming_stream_worker, args=[core, call])
|
|
|
+ t.start()
|
|
|
|
|
|
- f.seek(p)
|
|
|
- buf += f.read(4096)
|
|
|
- p = f.tell()
|
|
|
+ self.say(core)
|
|
|
|
|
|
- if len(buf) >= 20000:
|
|
|
- volume = audioop.rms(buf, 2)
|
|
|
- print("Detected volume : " + str(volume))
|
|
|
- # print("State : " + str(conversation.status))
|
|
|
- buf = ''
|
|
|
- if volume < VOLUME_THRESHOLD:
|
|
|
- if conversation.status is ConversationStatus.READY_TO_TALK:
|
|
|
- threading.Thread(target=say, args=[core]).start()
|
|
|
- else:
|
|
|
- conversation.status = ConversationStatus.READY_TO_TALK
|
|
|
+ def __enter__(self):
|
|
|
+ return self
|
|
|
|
|
|
- # We must sleep a bit to avoid cpu hog
|
|
|
- sleep(0.01)
|
|
|
- previous_status = conversation.status
|
|
|
+ def __exit__(self, exc_type, exc_value, traceback):
|
|
|
+ log("Cleaning on exit ...")
|
|
|
+ os.unlink(self._incoming_stream_file)
|
|
|
|
|
|
- log("Worker is quitting")
|
|
|
+ def start(self):
|
|
|
+ print("starting")
|
|
|
+ self._core.use_files = True
|
|
|
+ self._core.record_file = self._incoming_stream_file
|
|
|
|
|
|
+ proxy_cfg = self._core.create_proxy_config()
|
|
|
+ proxy_cfg.identity_address = self._core.create_address('sip:' + self._username + '@' + self._domain + ':5060')
|
|
|
+ proxy_cfg.server_addr = 'sip:' + self._domain + ':5060'
|
|
|
+ proxy_cfg.register_enabled = True
|
|
|
+ self._core.add_proxy_config(proxy_cfg)
|
|
|
+ auth_info = self._core.create_auth_info(self._username, None, self._password, None, None, self._domain)
|
|
|
+ self._core.add_auth_info(auth_info)
|
|
|
|
|
|
-def main():
|
|
|
- log("lenny is starting ...")
|
|
|
- callbacks = {
|
|
|
- 'call_state_changed': call_state_changed
|
|
|
- }
|
|
|
+ while not self._is_quitting:
|
|
|
+ sleep(0.03)
|
|
|
+ self._core.iterate()
|
|
|
|
|
|
- username = "621"
|
|
|
- password = "toto"
|
|
|
- port = "5060"
|
|
|
- domain = "192.168.1.1"
|
|
|
+ def request_quit(self):
|
|
|
+ self._is_quitting = True
|
|
|
+ self.__exit__(None, None, None)
|
|
|
|
|
|
- core = linphone.Core.new(callbacks, None, None)
|
|
|
+ def __init__(self, domain, username, password):
|
|
|
|
|
|
- # On fait le setup pour la capture et analyse du stream entrant
|
|
|
- os.system("rm -rf " + incoming_stream_file)
|
|
|
- os.system("touch " + incoming_stream_file)
|
|
|
- core.use_files = True
|
|
|
- core.record_file = incoming_stream_file
|
|
|
+ print("init")
|
|
|
+ callbacks = {
|
|
|
+ 'call_state_changed': self.call_state_changed
|
|
|
+ }
|
|
|
|
|
|
- proxy_cfg = core.create_proxy_config()
|
|
|
- proxy_cfg.identity_address = core.create_address('sip:' + username + '@' + domain + ':' + port)
|
|
|
- proxy_cfg.server_addr = 'sip:' + domain + ':' + port
|
|
|
- proxy_cfg.register_enabled = True
|
|
|
- core.add_proxy_config(proxy_cfg)
|
|
|
- auth_info = core.create_auth_info(username, None, password, None, None, domain)
|
|
|
- core.add_auth_info(auth_info)
|
|
|
+ self._core = linphone.Core.new(callbacks, None, None)
|
|
|
+ self._domain = domain
|
|
|
+ self._username = username
|
|
|
+ self._password = password
|
|
|
+ self._is_quitting = False
|
|
|
|
|
|
- while True:
|
|
|
- sleep(0.03)
|
|
|
- core.iterate()
|
|
|
+ self._conversation = Conversation()
|
|
|
+ self._replies_pos = 0
|
|
|
+ self._volume_threshold = VOLUME_THRESHOLD
|
|
|
+ self._incoming_stream_file = tempfile.NamedTemporaryFile(delete=False).name
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
- try:
|
|
|
- main()
|
|
|
- except KeyboardInterrupt:
|
|
|
- THREADS_MUST_QUIT = True
|
|
|
- print "Bye"
|
|
|
+
|
|
|
+ connections = []
|
|
|
+
|
|
|
+ def signal_handler(signal, frame):
|
|
|
+ print('External stop request!')
|
|
|
+ for conn in connections:
|
|
|
+ conn.request_quit()
|
|
|
+
|
|
|
+
|
|
|
+ conn1 = SipConnection("192.168.1.1", "621", "toto")
|
|
|
+ connections.append(conn1)
|
|
|
+ threading.Thread(target=conn1.start).start()
|
|
|
+
|
|
|
+ signal.signal(signal.SIGINT, signal_handler)
|
|
|
+ signal.signal(signal.SIGTERM, signal_handler)
|
|
|
+ signal.pause()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
# TODO : créer un fichier de config
|
|
|
# TODO : pouvoir s'enregistrer sur plusieurs comptes SIP
|
|
|
-# TODO : Que le stream entrant utilise un fichier temporaire
|
|
|
# TODO : De pouvoir utiliser des mp3 pour gagner de la place, qu'on convertit au vol en wav, puis qu'on efface.
|
|
|
# TODO : Ecrire le readme
|