first commit
This commit is contained in:
commit
9b6e170b5d
|
@ -0,0 +1,392 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
from pvrecorder import PvRecorder
|
||||
from pydub import AudioSegment
|
||||
from scipy.io import wavfile
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
import argparse
|
||||
import sys, os
|
||||
import struct
|
||||
import wave
|
||||
|
||||
VERSION = 0.1
|
||||
|
||||
def detect_frequency(file_path, duration=60):
|
||||
"""
|
||||
Detect the dominant frequency in an audio file.
|
||||
|
||||
Parameters:
|
||||
file_path (str): Path to the audio file.
|
||||
duration (int, optional): Duration in seconds to analyze. Defaults to 60.
|
||||
|
||||
Returns:
|
||||
float: The dominant frequency in Hz.
|
||||
|
||||
Src: https://github.com/Michael-Sebero/Audio-Frequency-Tools/blob/main/tools/frequency-detector.py
|
||||
"""
|
||||
try:
|
||||
# Read audio file with soundfile (supports many formats)
|
||||
audio_data, sample_rate = sf.read(file_path, always_2d=True)
|
||||
|
||||
# Extract the first channel if the audio is stereo
|
||||
audio_data = audio_data[:, 0]
|
||||
|
||||
# Truncate or pad the audio to the desired duration
|
||||
max_samples = int(sample_rate * duration)
|
||||
audio_data = audio_data[:max_samples] if len(audio_data) > max_samples else np.pad(
|
||||
audio_data, (0, max_samples - len(audio_data)), 'constant'
|
||||
)
|
||||
|
||||
|
||||
if len(audio_data) == 0:
|
||||
raise ValueError("Audio file contains no data or is too short for analysis.")
|
||||
|
||||
# Perform FFT on the audio data
|
||||
fft_result = np.fft.rfft(audio_data)
|
||||
freqs = np.fft.rfftfreq(len(audio_data), 1.0 / sample_rate)
|
||||
|
||||
# Find the dominant frequency
|
||||
dominant_freq = freqs[np.argmax(np.abs(fft_result))]
|
||||
#print(f"The dominant frequency is {dominant_freq:.2f} Hz.")
|
||||
return dominant_freq
|
||||
|
||||
except FileNotFoundError:
|
||||
print("Error: The specified audio file was not found.")
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}")
|
||||
except RuntimeError as e:
|
||||
print(f"Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
|
||||
|
||||
def getDevices():
|
||||
"""
|
||||
Detect and return enum of devices
|
||||
|
||||
Return:
|
||||
enumerate of devices.
|
||||
"""
|
||||
return enumerate(PvRecorder.get_available_devices())
|
||||
|
||||
|
||||
def getArgs(parser):
|
||||
"""
|
||||
Parsing Args and return them
|
||||
|
||||
Argmunents:
|
||||
parser: Namespace.
|
||||
|
||||
return:
|
||||
parser parser.
|
||||
"""
|
||||
|
||||
parser.add_argument(
|
||||
"-s","--show_devices",
|
||||
help="List of audio devices currently available for use.",
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument(
|
||||
"-i","--device_index",
|
||||
help="Index of input audio device. (Default: -1)",
|
||||
type=int,
|
||||
default=-1)
|
||||
|
||||
parser.add_argument(
|
||||
"-r","--rate",
|
||||
help="Rate config. (Default: 16000)",
|
||||
type=int,
|
||||
default=16000)
|
||||
|
||||
parser.add_argument(
|
||||
"-l","--length_frame",
|
||||
help="Frame size. (Default: 512)",
|
||||
type=int,
|
||||
default=512)
|
||||
|
||||
parser.add_argument(
|
||||
"-p","--output_name",
|
||||
help="Name to file to store raw audio. (Default: recorder.wav)",
|
||||
type=str,
|
||||
default="recorder.wav")
|
||||
|
||||
parser.add_argument(
|
||||
"-v","--verbosity",
|
||||
help="Increase logging. (Default: False)",
|
||||
default=False,
|
||||
action="store_true")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def saving(audio, output_name, rate, len_frame, wave_dir_path):
|
||||
"""
|
||||
Saving audio data in file outut_path.
|
||||
|
||||
Arguments:
|
||||
audio: file containing data audio
|
||||
output_name: str
|
||||
rate: int. Audio rate.
|
||||
len_frame: int. Length of frame
|
||||
|
||||
"""
|
||||
|
||||
print(f"\nSaving wav file: {wave_dir_path}/{output_name}.")
|
||||
|
||||
try:
|
||||
with wave.open(wave_dir_path + "/" + output_name, 'w') as f:
|
||||
f.setparams((1, 2, rate, len_frame, "NONE", "NONE"))
|
||||
f.writeframes(struct.pack("h" * len(audio), *audio))
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}")
|
||||
except RuntimeError as e:
|
||||
print(f"Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
|
||||
|
||||
def recording(dev_index,output_name,rate,len_frame, wave_dir_path):
|
||||
"""
|
||||
Create recorder, start listener.
|
||||
If ctrl + c are pressed:
|
||||
Stop listening
|
||||
Close PvRecorder instance.
|
||||
Call saving function.
|
||||
|
||||
Arguments:
|
||||
dev_index: int. Number of device. (can show it with -s option)
|
||||
output_name: str.
|
||||
rate: int. Audio rate.
|
||||
len_frame: int. Length of frame
|
||||
|
||||
Return:
|
||||
data and length (in sec) of them.
|
||||
"""
|
||||
|
||||
# Create object for recording
|
||||
recorder = PvRecorder(device_index=dev_index, frame_length=len_frame)
|
||||
|
||||
# Var containing audio
|
||||
audio = []
|
||||
|
||||
try:
|
||||
# Start recorder
|
||||
recorder.start()
|
||||
print("Listener is running.")
|
||||
|
||||
while True:
|
||||
# Read and append data in audio buffer
|
||||
frame = recorder.read()
|
||||
audio.extend(frame)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# If ctrl +c stop recoding, delete object and save audio
|
||||
recorder.stop()
|
||||
saving(audio,output_name, rate, len_frame,wave_dir_path)
|
||||
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}")
|
||||
except RuntimeError as e:
|
||||
print(f"Error: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
finally:
|
||||
recorder.delete()
|
||||
|
||||
rate, data = wavfile.read(wave_dir_path + "/" + output_name)
|
||||
length = data.shape[0] / rate
|
||||
return data,length
|
||||
|
||||
|
||||
def showInfos(data, rate,length, output_name,dev_index):
|
||||
print(" ######## INFOS ########")
|
||||
print(f" Value recording = {data}")
|
||||
print(f" Data shape: {data.shape[0]}")
|
||||
print(f" Rate: {rate}")
|
||||
print(f" length: {length}s")
|
||||
print(f" Path file: {output_name}")
|
||||
print(f" Device Index: {dev_index}")
|
||||
for index, device in getDevices():
|
||||
if index == dev_index:
|
||||
print(f" Device name: {device}")
|
||||
|
||||
|
||||
def cutWaveInBits(wave_dir_path, tmp_dir_path, output_name, length):
|
||||
"""
|
||||
Cut original wave in part. Actually support only 8bits
|
||||
The time duration for isolate data is 0.125s
|
||||
|
||||
Function trunc file in many file with time duration.
|
||||
|
||||
Arguments:
|
||||
output_name: str
|
||||
length: Duration of original wave.
|
||||
|
||||
Return:
|
||||
List of all files.
|
||||
"""
|
||||
bits_audio = AudioSegment.from_wav(os.path.join(wave_dir_path, output_name))
|
||||
begin = 0
|
||||
bits_time = 125 # milli = 0.125s. Its the time duration for bits information
|
||||
file_number = (length * 1000 ) / bits_time
|
||||
count = 0
|
||||
tmp_tab = []
|
||||
while count < file_number:
|
||||
new_audio = bits_audio[begin:begin + bits_time]
|
||||
filepath = str(f"{tmp_dir_path}/{count}-bits-{begin}-{begin + bits_time}.wav")
|
||||
new_audio.export(filepath, format="wav")
|
||||
tmp_tab.append(filepath)
|
||||
begin += bits_time
|
||||
count += 1
|
||||
|
||||
return tmp_tab
|
||||
|
||||
|
||||
def readBits(bits_tab_wave):
|
||||
"""
|
||||
Read all files cutted and isolate freq.
|
||||
|
||||
Values:
|
||||
if freq is > at 1500 => 1
|
||||
if freq is < 1500 and > 500 => 0
|
||||
else pass.
|
||||
|
||||
Arguments:
|
||||
List containing all files cutted
|
||||
|
||||
Return:
|
||||
Message in binary
|
||||
|
||||
"""
|
||||
|
||||
bits_message = []
|
||||
print("Extracting data from wav file:")
|
||||
for waves in bits_tab_wave:
|
||||
freq = detect_frequency(waves)
|
||||
if freq < 500:
|
||||
sys.stdout.write('.')
|
||||
elif freq < 1500 and freq > 500:
|
||||
sys.stdout.write("0")
|
||||
bits_message.append(0)
|
||||
elif freq >= 1500:
|
||||
sys.stdout.write("1")
|
||||
bits_message.append(1)
|
||||
|
||||
sys.stdout.flush()
|
||||
|
||||
return bits_message
|
||||
|
||||
|
||||
def showArgs(parser):
|
||||
print("Args value:")
|
||||
for arg,opt in parser.parse_args().__dict__.items():
|
||||
print(' %s: %s' % (arg, opt))
|
||||
|
||||
|
||||
def decodeMessage(message):
|
||||
"""
|
||||
Function decoding message.
|
||||
|
||||
Argument:
|
||||
Message: str. String containing message in binary
|
||||
|
||||
Return:
|
||||
Message decoded.
|
||||
"""
|
||||
|
||||
print("\nDecoding message:")
|
||||
|
||||
i = 0
|
||||
j = 0
|
||||
final_message = []
|
||||
|
||||
while i <len(message):
|
||||
if message[i] in (0, 1):
|
||||
|
||||
tmp_msg = message[i:i+8]
|
||||
|
||||
tmp_msg.reverse()
|
||||
binary_tab=''.join(str(x) for x in tmp_msg)
|
||||
|
||||
c=bytes("0b" + binary_tab, "utf-8")
|
||||
binary_str= int(c,2)
|
||||
try:
|
||||
final_message.append(binary_str.to_bytes((binary_str.bit_length() + 7) // 8, 'big').decode('utf-8'))
|
||||
except:
|
||||
final_message.append("?")
|
||||
|
||||
# Jump to next bits
|
||||
i += 8
|
||||
else:
|
||||
i += 1
|
||||
print(".", end='')
|
||||
|
||||
return final_message
|
||||
|
||||
|
||||
def checkEnv(root_path, tmp_dir_path, wave_dir_path):
|
||||
|
||||
for dir in (root_path, tmp_dir_path, wave_dir_path):
|
||||
if not os.path.exists(dir):
|
||||
try:
|
||||
os.mkdir(dir)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
|
||||
def welcome():
|
||||
print(f"""\
|
||||
____ __
|
||||
/ __ \___ _________ _________/ /__ _____
|
||||
/ /_/ / _ \/ ___/ __ \/ ___/ __ / _ \/ ___/
|
||||
/ _, _/ __/ /__/ /_/ / / / /_/ / __/ /
|
||||
/_/ |_|\___/\___/\____/_/ \__,_/\___/_/
|
||||
|
||||
Author: Snorky
|
||||
Version : {VERSION}
|
||||
""")
|
||||
|
||||
def main():
|
||||
|
||||
welcome()
|
||||
parser = argparse.ArgumentParser()
|
||||
args = getArgs(parser)
|
||||
showArgs(parser)
|
||||
|
||||
verbosity = False
|
||||
root_path = "/home/" + os.getlogin() + "/.cache/recorder"
|
||||
tmp_dir_path = root_path + "/tmp"
|
||||
wave_dir_path = root_path + "/wave"
|
||||
checkEnv(root_path, tmp_dir_path, wave_dir_path)
|
||||
|
||||
if args.show_devices:
|
||||
devices = getDevices()
|
||||
for index, device in devices:
|
||||
print(f"[{index}] {device}")
|
||||
exit(2)
|
||||
|
||||
if args.verbosity:
|
||||
verbosity=True
|
||||
|
||||
output_name = args.output_name
|
||||
dev_index = args.device_index
|
||||
length_frame = args.length_frame
|
||||
rate = args.rate
|
||||
|
||||
data, length = recording(dev_index, output_name, rate, length_frame, wave_dir_path)
|
||||
|
||||
if verbosity:
|
||||
showInfos(data, rate,length,output_name,dev_index)
|
||||
|
||||
bits_tab_wave = cutWaveInBits(wave_dir_path, tmp_dir_path, output_name, length)
|
||||
|
||||
message = readBits(bits_tab_wave)
|
||||
final_message = decodeMessage(message)
|
||||
|
||||
print(f"\nMessage transmitted is: {''.join(final_message)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in New Issue
Block a user