Added PTT mode. Improved notification display.

This commit is contained in:
Mark Qvist 2024-08-30 21:17:22 +02:00
parent f4d148cbaf
commit 18cbb5dfe2
6 changed files with 402 additions and 160 deletions

View File

@ -1742,10 +1742,96 @@ class SidebandApp(MDApp):
RNS.log("Error while playing message audio:"+str(e))
RNS.trace_exception(e)
def message_record_audio_action(self):
ss = int(dp(18))
def message_ptt_down_action(self, sender=None):
self.audio_msg_mode = LXMF.AM_CODEC2_2400
self.message_attach_action(attach_type="audio", nodialog=True)
if self.rec_dialog == None:
self.message_init_rec_dialog()
self.rec_dialog.recording = True
el_button = self.messages_view.ids.message_ptt_button
el_icon = self.messages_view.ids.message_ptt_button.children[0].children[1]
el_button.theme_text_color="Custom"
el_button.text_color=mdc("Orange","400")
el_button.line_color=mdc("Orange","400")
el_icon.theme_text_color="Custom"
el_icon.text_color=mdc("Orange","400")
def cb(dt):
self.msg_audio.start()
Clock.schedule_once(cb, 0.15)
def message_ptt_up_action(self, sender=None):
self.rec_dialog.recording = False
el_button = self.messages_view.ids.message_ptt_button
el_icon = self.messages_view.ids.message_ptt_button.children[0].children[1]
el_button.theme_text_color="Custom"
el_button.text_color=mdc("BlueGray","500")
el_button.line_color=mdc("BlueGray","500")
el_icon.theme_text_color="Custom"
el_icon.text_color=mdc("BlueGray","500")
def cb_s(dt):
self.msg_audio.stop()
self.message_process_audio()
self.message_send_action()
Clock.schedule_once(cb_s, 0.25)
def message_process_audio(self):
if self.audio_msg_mode == LXMF.AM_OPUS_OGG:
from sideband.audioproc import voice_processing
proc_path = voice_processing(self.msg_audio._file_path)
if proc_path:
self.attach_path = proc_path
os.unlink(self.msg_audio._file_path)
RNS.log("Using voice-processed OPUS data in OGG container", RNS.LOG_DEBUG)
else:
self.attach_path = self.msg_audio._file_path
RNS.log("Using unmodified OPUS data in OGG container", RNS.LOG_DEBUG)
else:
ap_start = time.time()
from sideband.audioproc import voice_processing
proc_path = voice_processing(self.msg_audio._file_path)
if proc_path:
opus_file = pyogg.OpusFile(proc_path)
RNS.log("Using voice-processed audio for codec2 encoding", RNS.LOG_DEBUG)
else:
opus_file = pyogg.OpusFile(self.msg_audio._file_path)
RNS.log("Using unprocessed audio data for codec2 encoding", RNS.LOG_DEBUG)
RNS.log(f"OPUS LOAD {opus_file.frequency}Hz {opus_file.bytes_per_sample*8}bit {opus_file.channels}ch")
audio = AudioSegment(
bytes(opus_file.as_array()),
frame_rate=opus_file.frequency,
sample_width=opus_file.bytes_per_sample,
channels=opus_file.channels,
)
audio = audio.split_to_mono()[0]
audio = audio.apply_gain(-audio.max_dBFS)
if self.audio_msg_mode >= LXMF.AM_CODEC2_700C and self.audio_msg_mode <= LXMF.AM_CODEC2_3200:
audio = audio.set_frame_rate(8000)
audio = audio.set_sample_width(2)
samples = audio.get_array_of_samples()
from sideband.audioproc import encode_codec2, detect_codec2
if detect_codec2():
encoded = encode_codec2(samples, self.audio_msg_mode)
ap_duration = time.time() - ap_start
RNS.log("Audio processing complete in "+RNS.prettytime(ap_duration), RNS.LOG_DEBUG)
export_path = self.sideband.rec_cache+"/recording.enc"
with open(export_path, "wb") as export_file:
export_file.write(encoded)
self.attach_path = export_path
os.unlink(self.msg_audio._file_path)
else:
self.display_codec2_error()
return
def message_init_rec_dialog(self):
ss = int(dp(18))
if RNS.vendor.platformutils.is_android():
from plyer import audio
self.request_microphone_permission()
@ -1767,7 +1853,7 @@ class SidebandApp(MDApp):
self.rec_dialog.rec_item.text = "[size="+str(ss)+"]Stop Recording[/size]"
def cb(dt):
self.msg_audio.start()
Clock.schedule_once(cb, 0.15)
Clock.schedule_once(cb, 0.10)
else:
RNS.log("Stopping recording...") # TODO: Remove
@ -1827,46 +1913,7 @@ class SidebandApp(MDApp):
self.attach_path = self.msg_audio._file_path
RNS.log("Using unmodified OPUS data in OGG container", RNS.LOG_DEBUG)
else:
ap_start = time.time()
from sideband.audioproc import voice_processing
proc_path = voice_processing(self.msg_audio._file_path)
if proc_path:
opus_file = pyogg.OpusFile(proc_path)
RNS.log("Using voice-processed audio for codec2 encoding", RNS.LOG_DEBUG)
else:
opus_file = pyogg.OpusFile(self.msg_audio._file_path)
RNS.log("Using unprocessed audio data for codec2 encoding", RNS.LOG_DEBUG)
audio = AudioSegment(
bytes(opus_file.as_array()),
frame_rate=opus_file.frequency,
sample_width=opus_file.bytes_per_sample,
channels=opus_file.channels,
)
audio = audio.split_to_mono()[0]
audio = audio.apply_gain(-audio.max_dBFS)
if self.audio_msg_mode >= LXMF.AM_CODEC2_700C and self.audio_msg_mode <= LXMF.AM_CODEC2_3200:
audio = audio.set_frame_rate(8000)
audio = audio.set_sample_width(2)
samples = audio.get_array_of_samples()
from sideband.audioproc import encode_codec2, detect_codec2
if detect_codec2():
encoded = encode_codec2(samples, self.audio_msg_mode)
ap_duration = time.time() - ap_start
RNS.log("Audio processing complete in "+RNS.prettytime(ap_duration), RNS.LOG_DEBUG)
export_path = self.sideband.rec_cache+"/recording.enc"
with open(export_path, "wb") as export_file:
export_file.write(encoded)
self.attach_path = export_path
os.unlink(self.msg_audio._file_path)
else:
self.display_codec2_error()
return
self.message_process_audio()
self.update_message_widgets()
toast("Added recorded audio to message")
@ -1899,6 +1946,11 @@ class SidebandApp(MDApp):
self.rec_dialog.play_item = play_item
self.rec_dialog.save_item = save_item
def message_record_audio_action(self):
ss = int(dp(18))
if self.rec_dialog == None:
self.message_init_rec_dialog()
else:
self.rec_dialog.play_item.disabled = True
self.rec_dialog.save_item.disabled = True
@ -1910,7 +1962,7 @@ class SidebandApp(MDApp):
self.rec_dialog_is_open = True
self.rec_dialog.update_width()
def message_attach_action(self, attach_type=None):
def message_attach_action(self, attach_type=None, nodialog=False):
file_attach_types = ["lbimg", "defimg", "hqimg", "file"]
rec_attach_types = ["audio"]
@ -1918,9 +1970,11 @@ class SidebandApp(MDApp):
self.rec_dialog_is_open = False
if attach_type in file_attach_types:
self.attach_type = attach_type
if not nodialog:
self.message_select_file_action()
elif attach_type in rec_attach_types:
self.attach_type = attach_type
if not nodialog:
self.message_record_audio_action()
def message_attachment_action(self, sender):

View File

@ -63,7 +63,7 @@ class NotifyDbus(Notification):
def _notify(self, **kwargs):
summary = kwargs.get('title', "title")
body = kwargs.get('message', "body")
app_name = kwargs.get('app_name', '')
app_name = "Sideband"
app_icon = kwargs.get('app_icon', '')
timeout = kwargs.get('timeout', 10)
actions = kwargs.get('actions', [])

View File

@ -26,7 +26,7 @@ class OSXNotification(Notification):
def _notify(self, **kwargs):
title = kwargs.get('title', '')
message = kwargs.get('message', '')
app_name = kwargs.get('app_name', '')
app_name = "Sideband"
# app_icon, timeout, ticker are not supported (yet)
notification = NSUserNotification.alloc().init()

View File

@ -112,6 +112,8 @@ class SidebandCore():
self.is_service = is_service
self.is_client = is_client
self.is_daemon = is_daemon
self.msg_audio = None
self.last_msg_audio = None
self.db = None
if not self.is_service and not self.is_client:
@ -938,6 +940,25 @@ class SidebandCore():
RNS.log("Error while checking trust for "+RNS.prettyhexrep(context_dest)+": "+str(e), RNS.LOG_ERROR)
return False
def ptt_enabled(self, context_dest, conv_data = None):
try:
if conv_data == None:
existing_conv = self._db_conversation(context_dest)
else:
existing_conv = conv_data
if existing_conv != None:
data_dict = existing_conv["data"]
if data_dict != None:
if "ptt_enabled" in data_dict:
return data_dict["ptt_enabled"]
return False
except Exception as e:
RNS.log("Error while checking PTT-enabled for "+RNS.prettyhexrep(context_dest)+": "+str(e), RNS.LOG_ERROR)
return False
def should_send_telemetry(self, context_dest, conv_data=None):
try:
if self.config["telemetry_enabled"]:
@ -1092,6 +1113,9 @@ class SidebandCore():
def conversation_set_object(self, context_dest, is_object):
self._db_conversation_set_object(context_dest, is_object)
def conversation_set_ptt_enabled(self, context_dest, ptt_enabled):
self._db_conversation_set_ptt_enabled(context_dest, ptt_enabled)
def send_telemetry_in_conversation(self, context_dest):
self._db_conversation_set_telemetry(context_dest, True)
@ -2069,6 +2093,32 @@ class SidebandCore():
RNS.log("Retrying operation...", RNS.LOG_ERROR)
self._db_conversation_set_object(context_dest, is_object, is_retry=True)
def _db_conversation_set_ptt_enabled(self, context_dest, ptt_enabled=False):
conv = self._db_conversation(context_dest)
data_dict = conv["data"]
if data_dict == None:
data_dict = {}
data_dict["ptt_enabled"] = ptt_enabled
packed_dict = msgpack.packb(data_dict)
db = self.__db_connect()
dbc = db.cursor()
query = "UPDATE conv set data = ? where dest_context = ?"
data = (packed_dict, context_dest)
dbc.execute(query, data)
result = dbc.fetchall()
try:
db.commit()
except Exception as e:
RNS.log("An error occurred while updating conversation PTT option: "+str(e), RNS.LOG_ERROR)
self.__db_reconnect()
if not is_retry:
RNS.log("Retrying operation...", RNS.LOG_ERROR)
self._db_conversation_set_ptt_enabled(context_dest, ptt_enabled, is_retry=True)
def _db_conversation_set_trusted(self, context_dest, trusted):
db = self.__db_connect()
dbc = db.cursor()
@ -3761,6 +3811,7 @@ class SidebandCore():
def lxm_ingest(self, message, originator = False):
should_notify = False
is_trusted = False
ptt_enabled = False
telemetry_only = False
own_command = False
unread_reason_tx = False
@ -3771,6 +3822,7 @@ class SidebandCore():
else:
context_dest = message.source_hash
is_trusted = self.is_trusted(context_dest)
ptt_enabled = self.ptt_enabled(context_dest)
if originator and LXMF.FIELD_COMMANDS in message.fields:
own_command = True
@ -3813,6 +3865,74 @@ class SidebandCore():
if self.gui_display() == "conversations_screen" and self.gui_foreground():
should_notify = False
### PTT #######################################################################
if not originator and LXMF.FIELD_AUDIO in message.fields and ptt_enabled:
if self.msg_audio == None:
if RNS.vendor.platformutils.is_android():
from plyer import audio
else:
from sbapp.plyer import audio
RNS.log("Audio init done")
self.msg_audio = audio
try:
temp_path = None
audio_field = message.fields[LXMF.FIELD_AUDIO]
if self.last_msg_audio != audio_field[1]:
RNS.log("Reloading audio source", RNS.LOG_DEBUG)
if len(audio_field[1]) > 10:
self.last_msg_audio = audio_field[1]
else:
self.last_msg_audio = None
return
if audio_field[0] == LXMF.AM_OPUS_OGG:
temp_path = self.rec_cache+"/msg.ogg"
with open(temp_path, "wb") as af:
af.write(self.last_msg_audio)
elif audio_field[0] >= LXMF.AM_CODEC2_700C and audio_field[0] <= LXMF.AM_CODEC2_3200:
temp_path = self.rec_cache+"/msg.ogg"
from sideband.audioproc import samples_to_ogg, decode_codec2, detect_codec2
target_rate = 8000
if RNS.vendor.platformutils.is_linux():
target_rate = 48000
if detect_codec2():
if samples_to_ogg(decode_codec2(audio_field[1], audio_field[0]), temp_path, input_rate=8000, output_rate=target_rate):
RNS.log("Wrote OGG file to: "+temp_path, RNS.LOG_DEBUG)
else:
RNS.log("OGG write failed", RNS.LOG_DEBUG)
else:
self.last_msg_audio = None
return
else:
# Unimplemented audio type
pass
self.msg_sound = self.msg_audio
self.msg_sound._file_path = temp_path
self.msg_sound.reload()
if self.msg_sound != None and self.msg_sound.playing():
RNS.log("Stopping playback", RNS.LOG_DEBUG)
self.msg_sound.stop()
else:
if self.msg_sound != None:
RNS.log("Starting playback", RNS.LOG_DEBUG)
self.msg_sound.play()
should_notify = False
else:
RNS.log("Playback was requested, but no audio data was loaded for playback", RNS.LOG_ERROR)
except Exception as e:
RNS.log("Error while playing message audio:"+str(e))
RNS.trace_exception(e)
###############################################################################
if self.is_client:
should_notify = False
@ -3824,7 +3944,14 @@ class SidebandCore():
text = message.content.decode("utf-8")
notification_content = text[:nlen]
if len(text) > nlen:
text += "..."
notification_content += " [...]"
if len(text) < 2 and LXMF.FIELD_AUDIO in message.fields:
notification_content = "Audio message"
if len(text) < 2 and LXMF.FIELD_IMAGE in message.fields:
notification_content = "Image"
if len(text) < 2 and LXMF.FIELD_FILE_ATTACHMENTS in message.fields:
notification_content = "File attachment"
self.notify(title=self.peer_display_name(context_dest), content=notification_content, group="LXM", context_id=RNS.hexrep(context_dest, delimit=False))

View File

@ -36,6 +36,7 @@ class ConvSettings(BoxLayout):
telemetry = BooleanProperty()
allow_requests = BooleanProperty()
is_object = BooleanProperty()
ptt_enabled = BooleanProperty()
class Conversations():
def __init__(self, app):
@ -192,6 +193,7 @@ class Conversations():
if not context_dest in self.added_item_dests:
existing_conv = self.app.sideband._db_conversation(context_dest)
is_object = self.app.sideband.is_object(context_dest, conv_data=existing_conv)
ptt_enabled = self.app.sideband.ptt_enabled(context_dest, conv_data=existing_conv)
iconl = self.get_icon(conv)
item = OneLineAvatarIconListItem(text=peer_disp_name, on_release=self.app.conversation_action)
item.add_widget(iconl)
@ -210,13 +212,14 @@ class Conversations():
disp_name = self.app.sideband.raw_display_name(dest)
is_trusted = self.app.sideband.is_trusted(dest, conv_data=cd)
is_object = self.app.sideband.is_object(dest, conv_data=cd)
ptt_enabled = self.app.sideband.ptt_enabled(dest, conv_data=cd)
send_telemetry = self.app.sideband.should_send_telemetry(dest, conv_data=cd)
allow_requests = self.app.sideband.requests_allowed_from(dest, conv_data=cd)
yes_button = MDRectangleFlatButton(text="Save",font_size=dp(18), theme_text_color="Custom", line_color=self.app.color_accept, text_color=self.app.color_accept)
no_button = MDRectangleFlatButton(text="Cancel",font_size=dp(18))
dialog_content = ConvSettings(disp_name=disp_name, context_dest=RNS.hexrep(dest, delimit=False), trusted=is_trusted,
telemetry=send_telemetry, allow_requests=allow_requests, is_object=is_object)
telemetry=send_telemetry, allow_requests=allow_requests, is_object=is_object, ptt_enabled=ptt_enabled)
dialog_content.ids.name_field.font_name = self.app.input_font
dialog = MDDialog(
@ -235,6 +238,7 @@ class Conversations():
telemetry = dialog.d_content.ids["telemetry_switch"].active
allow_requests = dialog.d_content.ids["allow_requests_switch"].active
conv_is_object = dialog.d_content.ids["is_object_switch"].active
ptt_is_enabled = dialog.d_content.ids["ptt_enabled_switch"].active
if trusted:
self.app.sideband.trusted_conversation(dest)
else:
@ -255,6 +259,13 @@ class Conversations():
else:
self.app.sideband.conversation_set_object(dest, False)
if ptt_is_enabled:
RNS.log("Setting PTT enabled")
self.app.sideband.conversation_set_ptt_enabled(dest, True)
else:
RNS.log("Setting PTT disabled")
self.app.sideband.conversation_set_ptt_enabled(dest, False)
self.app.sideband.named_conversation(name, dest)
except Exception as e:
@ -604,6 +615,21 @@ Builder.load_string("""
pos_hint: {"center_y": 0.43}
active: root.allow_requests
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None
padding: [0,0,dp(8),0]
height: dp(32)
MDLabel:
id: ptt_enabled_label
text: "PTT Enabled"
font_style: "H6"
MDSwitch:
id: ptt_enabled_switch
pos_hint: {"center_y": 0.43}
active: root.ptt_enabled
MDBoxLayout:
orientation: "horizontal"
size_hint_y: None

View File

@ -57,6 +57,7 @@ class Messages():
self.context_dest = context_dest
self.source_dest = context_dest
self.is_trusted = self.app.sideband.is_trusted(self.context_dest)
self.ptt_enabled = self.app.sideband.ptt_enabled(self.context_dest)
self.screen = self.app.root.ids.screen_manager.get_screen("messages_screen")
self.ids = self.screen.ids
@ -216,6 +217,15 @@ class Messages():
w.dmenu.items.append(w.dmenu.retry_item)
def hide_widget(self, wid, dohide=True):
if hasattr(wid, 'saved_attrs'):
if not dohide:
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = wid.saved_attrs
del wid.saved_attrs
elif dohide:
wid.saved_attrs = wid.height, wid.size_hint_y, wid.opacity, wid.disabled
wid.height, wid.size_hint_y, wid.opacity, wid.disabled = 0, None, 0, True
def update_widget(self):
if self.app.sideband.config["dark_ui"]:
intensity_msgs = intensity_msgs_dark
@ -228,6 +238,11 @@ class Messages():
self.ids.message_text.font_name = self.app.input_font
if self.ptt_enabled:
self.hide_widget(self.ids.message_ptt, False)
else:
self.hide_widget(self.ids.message_ptt, True)
if self.loading_earlier_messages:
self.new_messages.reverse()
@ -1021,6 +1036,26 @@ MDScreen:
text: "Query Network For Keys"
on_release: root.app.key_query_action(self)
BoxLayout:
id: message_ptt
padding: [dp(16), dp(8), dp(16), dp(8)]
spacing: dp(24)
size_hint_y: None
height: self.minimum_height
MDRectangleFlatIconButton:
id: message_ptt_button
icon: "microphone"
text: "PTT"
size_hint_x: 1.0
padding: [dp(10), dp(13), dp(10), dp(14)]
icon_size: dp(24)
font_size: dp(16)
on_press: root.app.message_ptt_down_action(self)
on_release: root.app.message_ptt_up_action(self)
_no_ripple_effect: True
background_normal: ""
background_down: ""
BoxLayout:
id: message_input_part