UdasriHasindu commited on
Commit
586d5d0
·
1 Parent(s): 9900500

implement voice extraction features

Browse files
utils/__init__.py ADDED
File without changes
utils/file_handler.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tempfile
2
+ import os
3
+ import subprocess
4
+
5
+ async def save_temp_file(upload_file):
6
+ # Get the original file extension from content type or filename
7
+ content_type = upload_file.content_type or ""
8
+ filename = upload_file.filename
9
+
10
+ # Determine file extension
11
+ if 'webm' in content_type or filename.endswith('.webm'):
12
+ original_suffix = ".webm"
13
+ elif 'ogg' in content_type or filename.endswith('.ogg'):
14
+ original_suffix = ".ogg"
15
+ elif 'mp3' in content_type or filename.endswith('.mp3'):
16
+ original_suffix = ".mp3"
17
+ elif 'wav' in content_type or filename.endswith('.wav'):
18
+ original_suffix = ".wav"
19
+ else:
20
+ original_suffix = ".wav" # default
21
+
22
+ # Save original file first
23
+ with tempfile.NamedTemporaryFile(delete=False, suffix=original_suffix) as tmp_original:
24
+ content = await upload_file.read()
25
+ tmp_original.write(content)
26
+ original_path = tmp_original.name
27
+
28
+ # If already WAV, return as is
29
+ if original_suffix == ".wav":
30
+ return original_path
31
+
32
+ # Convert to WAV using ffmpeg
33
+ try:
34
+ wav_path = original_path.replace(original_suffix, ".wav")
35
+
36
+ # Use ffmpeg for conversion
37
+ cmd = ['ffmpeg', '-i', original_path, '-acodec', 'pcm_s16le', '-ar', '16000', wav_path, '-y']
38
+ result = subprocess.run(cmd, capture_output=True, timeout=30)
39
+
40
+ if result.returncode != 0:
41
+ raise Exception(f"FFmpeg conversion failed: {result.stderr.decode()}")
42
+
43
+ # Delete original file
44
+ os.remove(original_path)
45
+
46
+ return wav_path
47
+ except Exception as e:
48
+ # If conversion fails, try to use original
49
+ print(f"Audio conversion error: {e}")
50
+ # Cleanup
51
+ if os.path.exists(original_path):
52
+ os.remove(original_path)
53
+ raise Exception(f"Failed to convert audio file: {e}")
utils/voice_data_extraction.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import parselmouth
2
+ from parselmouth.praat import call
3
+ import numpy as np
4
+ from scipy.stats import entropy
5
+
6
+ def extract_voice_features(audio_file):
7
+
8
+ sound = parselmouth.Sound(audio_file)
9
+ pitch = call(sound, "To Pitch", 0.0, 75, 600)
10
+
11
+ # Calculate jitter measures using individual Parselmouth functions
12
+ pointprocess = call(sound, "To PointProcess (periodic, cc)", 75, 600)
13
+
14
+ # Jitter measurements
15
+ jitter_percent = call(pointprocess, "Get jitter (local)", 0, 0, 0.0001, 0.02, 1.3)
16
+ jitter_abs = call(pointprocess, "Get jitter (local, absolute)", 0, 0, 0.0001, 0.02, 1.3)
17
+ jitter_rap = call(pointprocess, "Get jitter (rap)", 0, 0, 0.0001, 0.02, 1.3)
18
+ jitter_ppq5 = call(pointprocess, "Get jitter (ppq5)", 0, 0, 0.0001, 0.02, 1.3)
19
+ jitter_ddp = call(pointprocess, "Get jitter (ddp)", 0, 0, 0.0001, 0.02, 1.3)
20
+
21
+ # Shimmer measurements
22
+ shimmer = call([sound, pointprocess], "Get shimmer (local)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
23
+ shimmer_db = call([sound, pointprocess], "Get shimmer (local_dB)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
24
+ shimmer_apq3 = call([sound, pointprocess], "Get shimmer (apq3)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
25
+ shimmer_apq5 = call([sound, pointprocess], "Get shimmer (apq5)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
26
+ shimmer_apq11 = call([sound, pointprocess], "Get shimmer (apq11)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
27
+ shimmer_dda = call([sound, pointprocess], "Get shimmer (dda)", 0, 0, 0.0001, 0.02, 1.3, 1.6)
28
+
29
+ # HNR (Harmonics-to-Noise Ratio)
30
+ harmonicity = call(sound, "To Harmonicity (cc)", 0.01, 75, 0.1, 1.0)
31
+ hnr = call(harmonicity, "Get mean", 0, 0)
32
+
33
+ # NHR (Noise-to-Harmonics Ratio) = 1 / linear_HNR
34
+ # HNR from Praat is in dB, so convert: linear_HNR = 10^(HNR_dB / 10)
35
+ # Guard against zero/negative linear values (unvoiced / very noisy signal)
36
+ if hnr > 0:
37
+ nhr_value = 1.0 / (10 ** (hnr / 10))
38
+ else:
39
+ nhr_value = float('inf')
40
+
41
+ # Get pitch periods for nonlinear features
42
+ # Alternative approach: use pitch values directly for period calculation
43
+ pitch_values = pitch.selected_array['frequency']
44
+ voiced_frames = pitch_values[pitch_values > 0] # Only voiced frames
45
+
46
+ if len(voiced_frames) > 0:
47
+ # Convert frequency to periods (1/frequency)
48
+ periods = 1.0 / voiced_frames
49
+ else:
50
+ periods = np.array([])
51
+
52
+ # Nonlinear features
53
+ if len(periods) < 50:
54
+ rpde = dfa = ppe = np.nan
55
+ else:
56
+ # PPE: Pitch Period Entropy
57
+ hist, _ = np.histogram(periods, bins=min(20, len(periods)//5), density=True)
58
+ hist = hist[hist > 0]
59
+ ppe = entropy(hist + 1e-10)
60
+
61
+ # Simplified RPDE: Recurrence Period Density Entropy
62
+ diffs = np.abs(np.diff(periods))
63
+ rec_threshold = np.std(diffs) * 0.1
64
+ rec_matrix = (np.abs(periods[:, None] - periods[None, :]) < rec_threshold).astype(float)
65
+ np.fill_diagonal(rec_matrix, 0)
66
+ hist_rpde, _ = np.histogram(rec_matrix.flatten(), bins=2, density=True)
67
+ rpde = entropy(hist_rpde + 1e-10)
68
+
69
+ # DFA: Detrended Fluctuation Analysis
70
+ y = np.cumsum(periods - np.mean(periods))
71
+ scales = np.logspace(np.log10(4), np.log10(len(y)/4), 8, dtype=int)
72
+ log_F = []
73
+ log_s = []
74
+ for s in scales:
75
+ if s > len(y) // 4:
76
+ continue
77
+ num_seg = len(y) // s
78
+ rms = []
79
+ for i in range(num_seg):
80
+ seg = y[i*s:(i+1)*s]
81
+ if len(seg) < 3:
82
+ continue
83
+ x = np.arange(len(seg))
84
+ p = np.polyfit(x, seg, 1)
85
+ detrend = seg - np.polyval(p, x)
86
+ rms.append(np.sqrt(np.mean(detrend**2)))
87
+ if rms:
88
+ F = np.sqrt(np.mean(rms))
89
+ log_F.append(np.log(F))
90
+ log_s.append(np.log(s))
91
+ if len(log_F) > 1:
92
+ slope, _ = np.polyfit(log_s, log_F, 1)
93
+ dfa = slope
94
+ else:
95
+ dfa = np.nan
96
+
97
+ return {
98
+ 'Jitter(%)': jitter_percent * 100,
99
+ 'Jitter(Abs)': jitter_abs,
100
+ 'Jitter:RAP': jitter_rap,
101
+ 'Jitter:PPQ5': jitter_ppq5,
102
+ 'Jitter:DDP': jitter_ddp,
103
+ 'Shimmer': shimmer,
104
+ 'Shimmer(dB)': shimmer_db,
105
+ 'Shimmer:APQ3': shimmer_apq3,
106
+ 'Shimmer:APQ5': shimmer_apq5,
107
+ 'Shimmer:APQ11': shimmer_apq11,
108
+ 'Shimmer:DDA': shimmer_dda,
109
+ 'NHR': nhr_value,
110
+ 'HNR': hnr,
111
+ 'RPDE': float(rpde),
112
+ 'DFA': float(dfa),
113
+ 'PPE': float(ppe)
114
+ }
115
+
116
+
117
+ # Load audio file
118
+ # audio_file = "test_voice.wav"
119
+ # features = measure_jitter_shimmer(audio_file)
120
+ # print(features)