feat(voicemail): refactor VoicemailManager to use config for settings, add greeting conversion and removal methods
This commit is contained in:
@@ -14,8 +14,9 @@ from LXST.Sources import OpusFileSource
|
||||
|
||||
|
||||
class VoicemailManager:
|
||||
def __init__(self, db, telephone_manager, storage_dir):
|
||||
def __init__(self, db, config, telephone_manager, storage_dir):
|
||||
self.db = db
|
||||
self.config = config
|
||||
self.telephone_manager = telephone_manager
|
||||
self.storage_dir = os.path.join(storage_dir, "voicemails")
|
||||
self.greetings_dir = os.path.join(self.storage_dir, "greetings")
|
||||
@@ -50,6 +51,10 @@ class VoicemailManager:
|
||||
else:
|
||||
RNS.log("Voicemail: ffmpeg not found", RNS.LOG_ERROR)
|
||||
|
||||
def get_name_for_identity_hash(self, identity_hash):
|
||||
"""Default implementation, should be patched by ReticulumMeshChat"""
|
||||
return None
|
||||
|
||||
def _find_espeak(self):
|
||||
# Try standard name first
|
||||
path = shutil.which("espeak-ng")
|
||||
@@ -103,53 +108,82 @@ class VoicemailManager:
|
||||
# espeak-ng to WAV
|
||||
subprocess.run([self.espeak_path, "-w", wav_path, text], check=True)
|
||||
|
||||
# ffmpeg to Opus
|
||||
if os.path.exists(opus_path):
|
||||
os.remove(opus_path)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
self.ffmpeg_path,
|
||||
"-i",
|
||||
wav_path,
|
||||
"-c:a",
|
||||
"libopus",
|
||||
"-b:a",
|
||||
"16k",
|
||||
"-vbr",
|
||||
"on",
|
||||
opus_path,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
return opus_path
|
||||
# Convert WAV to Opus
|
||||
return self.convert_to_greeting(wav_path)
|
||||
finally:
|
||||
if os.path.exists(wav_path):
|
||||
os.remove(wav_path)
|
||||
|
||||
def convert_to_greeting(self, input_path):
|
||||
if not self.has_ffmpeg:
|
||||
msg = "ffmpeg is required for audio conversion"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
opus_path = os.path.join(self.greetings_dir, "greeting.opus")
|
||||
|
||||
if os.path.exists(opus_path):
|
||||
os.remove(opus_path)
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
self.ffmpeg_path,
|
||||
"-i",
|
||||
input_path,
|
||||
"-c:a",
|
||||
"libopus",
|
||||
"-b:a",
|
||||
"16k",
|
||||
"-vbr",
|
||||
"on",
|
||||
opus_path,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
return opus_path
|
||||
|
||||
def remove_greeting(self):
|
||||
opus_path = os.path.join(self.greetings_dir, "greeting.opus")
|
||||
if os.path.exists(opus_path):
|
||||
os.remove(opus_path)
|
||||
return True
|
||||
|
||||
def handle_incoming_call(self, caller_identity):
|
||||
if not self.db.config.voicemail_enabled.get():
|
||||
RNS.log(f"Voicemail: handle_incoming_call from {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
|
||||
if not self.config.voicemail_enabled.get():
|
||||
RNS.log("Voicemail: Voicemail is disabled", RNS.LOG_DEBUG)
|
||||
return
|
||||
|
||||
delay = self.db.config.voicemail_auto_answer_delay_seconds.get()
|
||||
delay = self.config.voicemail_auto_answer_delay_seconds.get()
|
||||
RNS.log(f"Voicemail: Will auto-answer in {delay} seconds", RNS.LOG_DEBUG)
|
||||
|
||||
def voicemail_job():
|
||||
RNS.log(f"Voicemail: Auto-answer timer started for {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
|
||||
time.sleep(delay)
|
||||
|
||||
# Check if still ringing and no other active call
|
||||
telephone = self.telephone_manager.telephone
|
||||
if not telephone:
|
||||
RNS.log("Voicemail: No telephone object", RNS.LOG_ERROR)
|
||||
return
|
||||
|
||||
RNS.log(f"Voicemail: Checking status. Call status: {telephone.call_status}, Active call: {telephone.active_call}", RNS.LOG_DEBUG)
|
||||
|
||||
if (
|
||||
telephone
|
||||
and telephone.active_call
|
||||
and telephone.active_call.get_remote_identity() == caller_identity
|
||||
and telephone.active_call.get_remote_identity().hash == caller_identity.hash
|
||||
and telephone.call_status == LXST.Signalling.STATUS_RINGING
|
||||
):
|
||||
RNS.log(
|
||||
f"Auto-answering call from {RNS.prettyhexrep(caller_identity.hash)} for voicemail",
|
||||
RNS.LOG_DEBUG,
|
||||
RNS.LOG_INFO,
|
||||
)
|
||||
self.start_voicemail_session(caller_identity)
|
||||
else:
|
||||
RNS.log("Voicemail: Auto-answer conditions not met after delay", RNS.LOG_DEBUG)
|
||||
if telephone.active_call:
|
||||
RNS.log(f"Voicemail: Active call remote: {RNS.prettyhexrep(telephone.active_call.get_remote_identity().hash)}", RNS.LOG_DEBUG)
|
||||
|
||||
threading.Thread(target=voicemail_job, daemon=True).start()
|
||||
|
||||
@@ -170,7 +204,7 @@ class VoicemailManager:
|
||||
greeting_path = os.path.join(self.greetings_dir, "greeting.opus")
|
||||
if not os.path.exists(greeting_path):
|
||||
# Fallback if no greeting generated yet
|
||||
self.generate_greeting(self.db.config.voicemail_greeting.get())
|
||||
self.generate_greeting(self.config.voicemail_greeting.get())
|
||||
|
||||
def session_job():
|
||||
try:
|
||||
@@ -206,7 +240,7 @@ class VoicemailManager:
|
||||
self.start_recording(caller_identity)
|
||||
|
||||
# 4. Wait for max recording time or hangup
|
||||
max_time = self.db.config.voicemail_max_recording_seconds.get()
|
||||
max_time = self.config.voicemail_max_recording_seconds.get()
|
||||
start_wait = time.time()
|
||||
while self.is_recording and (time.time() - start_wait < max_time):
|
||||
time.sleep(0.5)
|
||||
@@ -271,7 +305,7 @@ class VoicemailManager:
|
||||
|
||||
# Save to database if long enough
|
||||
if duration >= 1:
|
||||
remote_name = self.telephone_manager.get_name_for_identity_hash(
|
||||
remote_name = self.get_name_for_identity_hash(
|
||||
self.recording_remote_identity.hash.hex()
|
||||
)
|
||||
self.db.voicemails.add_voicemail(
|
||||
|
||||
Reference in New Issue
Block a user