ranar110 commited on
Commit
37c6d1c
·
0 Parent(s):

Initial commit of Voice Detection System

Browse files
Files changed (12) hide show
  1. .gitignore +5 -0
  2. audio_processor.py +56 -0
  3. auth.py +8 -0
  4. detector.py +20 -0
  5. main.py +58 -0
  6. murf_generator.py +50 -0
  7. requirements.txt +4 -0
  8. static/tester.html +214 -0
  9. test_api.py +49 -0
  10. test_details.py +24 -0
  11. test_murf.py +25 -0
  12. verify_murf_key.py +29 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ uploads/
4
+ *.mp3
5
+ .fastapi_cache/
audio_processor.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import shutil
2
+ import os
3
+ import requests
4
+ import uuid
5
+ from urllib.parse import urlparse
6
+ from fastapi import UploadFile, HTTPException
7
+
8
+ UPLOAD_DIR = "uploads"
9
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
10
+
11
+ async def download_audio_from_url(url: str) -> str:
12
+ try:
13
+ response = requests.get(url, stream=True)
14
+ response.raise_for_status()
15
+
16
+ # Parse URL to get path and extension, ignoring query params
17
+ parsed_url = urlparse(url)
18
+ path = parsed_url.path
19
+ ext = os.path.splitext(path)[1]
20
+
21
+ if not ext:
22
+ ext = ".mp3" # Default to mp3 if no extension found
23
+
24
+ # Generate a safe filename
25
+ filename = f"{uuid.uuid4()}{ext}"
26
+
27
+ file_location = f"{UPLOAD_DIR}/{filename}"
28
+ with open(file_location, "wb") as f:
29
+ for chunk in response.iter_content(chunk_size=8192):
30
+ f.write(chunk)
31
+ return file_location
32
+ except Exception as e:
33
+ raise HTTPException(status_code=400, detail=f"Failed to download audio: {str(e)}")
34
+
35
+ async def process_audio(file: UploadFile = None, audio_url: str = None):
36
+ if file:
37
+ file_location = f"{UPLOAD_DIR}/{file.filename}"
38
+ with open(file_location, "wb+") as file_object:
39
+ shutil.copyfileobj(file.file, file_object)
40
+ filename = file.filename
41
+ elif audio_url:
42
+ file_location = await download_audio_from_url(audio_url)
43
+ filename = os.path.basename(file_location)
44
+ else:
45
+ raise HTTPException(status_code=400, detail="No audio file or URL provided")
46
+
47
+ # In a real scenario, use librosa or pydub to get duration/sample_rate
48
+ # For now, just return file size as a proxy for 'processed' metadata
49
+ file_size = os.path.getsize(file_location)
50
+
51
+ return {
52
+ "filename": filename,
53
+ "size_bytes": file_size,
54
+ "format": filename.split(".")[-1],
55
+ "duration_seconds": 0.0 # Placeholder
56
+ }
auth.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Header, HTTPException
2
+
3
+ API_KEY = "my_secret_key_123" # Simple hardcoded key for demo
4
+
5
+ async def verify_api_key(x_api_key: str = Header(...)):
6
+ if x_api_key != API_KEY:
7
+ raise HTTPException(status_code=401, detail="Invalid API Key")
8
+ return x_api_key
detector.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+
3
+ def analyze_audio(metadata):
4
+ # Mock detection logic
5
+ # In a real app, this would use a model
6
+
7
+ # Deterministic mock based on file size to be consistent for testing if needed,
8
+ # or just random for demo. Let's make it random but weighted.
9
+
10
+ is_human = random.choice([True, False])
11
+ confidence = round(random.uniform(0.70, 0.99), 2)
12
+
13
+ return {
14
+ "is_human": is_human,
15
+ "confidence": confidence,
16
+ "detected_language": "multilingual",
17
+ "segments": [
18
+ {"start": 0.0, "end": 2.5, "label": "speech"}
19
+ ]
20
+ }
main.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, Depends, HTTPException, Header, Form
2
+ from fastapi.staticfiles import StaticFiles
3
+ from fastapi.responses import FileResponse
4
+ from auth import verify_api_key
5
+ from audio_processor import process_audio
6
+ from detector import analyze_audio
7
+ from murf_generator import generate_audio_with_murf
8
+ from pydantic import BaseModel
9
+ from typing import Optional
10
+
11
+ app = FastAPI()
12
+
13
+ app.mount("/static", StaticFiles(directory="static"), name="static")
14
+
15
+ class AnalysisResponse(BaseModel):
16
+ status: str
17
+ analysis: dict
18
+ metadata: dict
19
+
20
+ @app.post("/detect", response_model=AnalysisResponse)
21
+ async def detect_voice(
22
+ file: UploadFile = File(None),
23
+ message: str = Form(None),
24
+ audio_url: str = Form(None),
25
+ x_api_key: str = Depends(verify_api_key)
26
+ ):
27
+ # validate file type if file is uploaded
28
+ if file and not file.filename.endswith(('.mp3', '.wav', '.m4a')):
29
+ raise HTTPException(status_code=400, detail="Invalid file format. Only audio files allowed.")
30
+
31
+ if not file and not audio_url:
32
+ raise HTTPException(status_code=400, detail="Either file or audio_url is required.")
33
+
34
+ # Process Audio (get metadata)
35
+ metadata = await process_audio(file, audio_url)
36
+
37
+ # Run Detection
38
+ result = analyze_audio(metadata)
39
+
40
+ return {
41
+ "status": "success",
42
+ "analysis": result,
43
+ "metadata": metadata
44
+ }
45
+
46
+ class GenerateRequest(BaseModel):
47
+ text: str
48
+ murf_api_key: str
49
+ voice_id: Optional[str] = "en-US-marcus"
50
+
51
+ @app.post("/generate")
52
+ def generate_speech(request: GenerateRequest):
53
+ audio_url = generate_audio_with_murf(request.text, request.murf_api_key, request.voice_id)
54
+ return {"status": "success", "audio_url": audio_url}
55
+
56
+ @app.get("/")
57
+ def read_root():
58
+ return FileResponse('static/tester.html')
murf_generator.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from fastapi import HTTPException
3
+
4
+ # Murf API Endpoint
5
+ MURF_API_URL = "https://api.murf.ai/v1/speech/generate"
6
+
7
+ def generate_audio_with_murf(text: str, api_key: str, voice_id: str = "en-US-marcus") -> str:
8
+ """
9
+ Generates audio using Murf.ai API and returns the audio URL.
10
+ """
11
+ payload = {
12
+ "voiceId": voice_id,
13
+ "text": text,
14
+ "style": "General",
15
+ "rate": 0,
16
+ "pitch": 0,
17
+ "sampleRate": 24000,
18
+ "format": "MP3",
19
+ "channelType": "MONO",
20
+ "encodeAsBase64": False
21
+ }
22
+
23
+ headers = {
24
+ "Content-Type": "application/json",
25
+ "Accept": "application/json",
26
+ "api-key": api_key
27
+ }
28
+
29
+ try:
30
+ response = requests.post(MURF_API_URL, json=payload, headers=headers)
31
+ response.raise_for_status()
32
+
33
+ data = response.json()
34
+
35
+ # Check if URL is present in response
36
+ if "audioFile" in data:
37
+ return data["audioFile"]
38
+ elif "encodedAudio" in data:
39
+ raise HTTPException(status_code=500, detail="Received encoded audio but expected URL")
40
+ else:
41
+ raise HTTPException(status_code=500, detail=f"Unexpected response from Murf API: {data}")
42
+
43
+ except requests.exceptions.RequestException as e:
44
+ detail = str(e)
45
+ if e.response is not None:
46
+ try:
47
+ detail = e.response.json()
48
+ except:
49
+ detail = e.response.text
50
+ raise HTTPException(status_code=400, detail=f"Murf API Error: {detail}")
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ requests
static/tester.html ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Voice Detection Verification Tool</title>
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ background-color: #f4f4f9;
12
+ color: #333;
13
+ padding: 20px;
14
+ }
15
+
16
+ .container {
17
+ max-width: 600px;
18
+ margin: 0 auto;
19
+ background: #fff;
20
+ padding: 30px;
21
+ border-radius: 8px;
22
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
23
+ }
24
+
25
+ h1 {
26
+ text-align: center;
27
+ color: #444;
28
+ }
29
+
30
+ .form-group {
31
+ margin-bottom: 20px;
32
+ }
33
+
34
+ label {
35
+ display: block;
36
+ margin-bottom: 5px;
37
+ font-weight: bold;
38
+ }
39
+
40
+ input[type="text"],
41
+ input[type="url"],
42
+ textarea {
43
+ width: 100%;
44
+ padding: 10px;
45
+ border: 1px solid #ddd;
46
+ border-radius: 4px;
47
+ box-sizing: border-box;
48
+ }
49
+
50
+ button {
51
+ width: 100%;
52
+ padding: 12px;
53
+ background-color: #007bff;
54
+ color: white;
55
+ border: none;
56
+ border-radius: 4px;
57
+ cursor: pointer;
58
+ font-size: 16px;
59
+ }
60
+
61
+ button:hover {
62
+ background-color: #0056b3;
63
+ }
64
+
65
+ #response {
66
+ margin-top: 20px;
67
+ background: #f8f9fa;
68
+ padding: 15px;
69
+ border-radius: 4px;
70
+ border: 1px solid #ddd;
71
+ word-wrap: break-word;
72
+ white-space: pre-wrap;
73
+ display: none;
74
+ }
75
+
76
+ .error {
77
+ color: #dc3545;
78
+ }
79
+
80
+ .success {
81
+ color: #28a745;
82
+ }
83
+ </style>
84
+ </head>
85
+
86
+ <body>
87
+ <div class="container">
88
+ <h1>Voice Detection Tool</h1>
89
+
90
+ <div style="background: #e9ecef; padding: 15px; border-radius: 4px; margin-bottom: 20px;">
91
+ <h3>Generate AI Audio (Murf.ai)</h3>
92
+ <div class="form-group">
93
+ <label for="murfText">Text to Speak</label>
94
+ <input type="text" id="murfText" placeholder="Hello, this is a test.">
95
+ </div>
96
+ <div class="form-group">
97
+ <label for="murfKey">Murf API Key</label>
98
+ <input type="text" id="murfKey" placeholder="Enter Murf API Key">
99
+ </div>
100
+ <button onclick="generateAudio()" style="background-color: #28a745;">Generate Audio</button>
101
+ <div id="genStatus" style="margin-top:10px;"></div>
102
+ </div>
103
+
104
+ <div class="form-group">
105
+ <label for="apiUrl">API Endpoint URL</label>
106
+ <input type="text" id="apiUrl" value="http://127.0.0.1:8000/detect"
107
+ placeholder="http://127.0.0.1:8000/detect">
108
+ </div>
109
+
110
+ <div class="form-group">
111
+ <label for="apiKey">API Key (Authorization)</label>
112
+ <input type="text" id="apiKey" value="my_secret_key_123" placeholder="Enter API Key">
113
+ </div>
114
+
115
+ <div class="form-group">
116
+ <label for="message">Test Message (Optional)</label>
117
+ <textarea id="message" rows="2" placeholder="Describe this test..."></textarea>
118
+ </div>
119
+
120
+ <div class="form-group">
121
+ <label for="audioUrl">Audio File URL (MP3)</label>
122
+ <input type="url" id="audioUrl" placeholder="https://example.com/sample.mp3">
123
+ </div>
124
+
125
+ <button onclick="testEndpoint()">Test Endpoint</button>
126
+
127
+ <div id="response"></div>
128
+ </div>
129
+
130
+ <script>
131
+ async function generateAudio() {
132
+ const text = document.getElementById('murfText').value;
133
+ const murfKey = document.getElementById('murfKey').value;
134
+ const statusDiv = document.getElementById('genStatus');
135
+ const audioUrlInput = document.getElementById('audioUrl');
136
+
137
+ if (!text || !murfKey) {
138
+ alert("Please enter Text and Murf API Key");
139
+ return;
140
+ }
141
+
142
+ statusDiv.innerHTML = "Generating...";
143
+
144
+ try {
145
+ const response = await fetch('/generate', {
146
+ method: 'POST',
147
+ headers: { 'Content-Type': 'application/json' },
148
+ body: JSON.stringify({
149
+ text: text,
150
+ murf_api_key: murfKey
151
+ })
152
+ });
153
+
154
+ const data = await response.json();
155
+
156
+ if (response.ok) {
157
+ statusDiv.innerHTML = "Generated! URL copied to input below.";
158
+ statusDiv.className = 'success';
159
+ audioUrlInput.value = data.audio_url;
160
+ } else {
161
+ statusDiv.innerHTML = "Error: " + (data.detail || JSON.stringify(data));
162
+ statusDiv.className = 'error';
163
+ }
164
+ } catch (e) {
165
+ statusDiv.innerHTML = "Error: " + e.message;
166
+ statusDiv.className = 'error';
167
+ }
168
+ }
169
+
170
+ async function testEndpoint() {
171
+ const apiUrl = document.getElementById('apiUrl').value;
172
+ const apiKey = document.getElementById('apiKey').value;
173
+ const message = document.getElementById('message').value;
174
+ const audioUrl = document.getElementById('audioUrl').value;
175
+ const responseDiv = document.getElementById('response');
176
+
177
+ responseDiv.style.display = 'block';
178
+ responseDiv.innerHTML = 'Sending request...';
179
+ responseDiv.className = '';
180
+
181
+ const formData = new FormData();
182
+ formData.append('message', message);
183
+ formData.append('audio_url', audioUrl);
184
+
185
+ // Note: We are strictly testing audio_url flow as per tool description.
186
+ // If we supported file upload input, we would append it here.
187
+
188
+ try {
189
+ const response = await fetch(apiUrl, {
190
+ method: 'POST',
191
+ headers: {
192
+ 'x-api-key': apiKey
193
+ },
194
+ body: formData
195
+ });
196
+
197
+ const data = await response.json();
198
+
199
+ if (response.ok) {
200
+ responseDiv.innerHTML = '<strong>Success!</strong>\n' + JSON.stringify(data, null, 2);
201
+ responseDiv.className = 'success';
202
+ } else {
203
+ responseDiv.innerHTML = '<strong>Error ' + response.status + ':</strong>\n' + JSON.stringify(data, null, 2);
204
+ responseDiv.className = 'error';
205
+ }
206
+ } catch (error) {
207
+ responseDiv.innerHTML = '<strong>Network Error:</strong> ' + error.message;
208
+ responseDiv.className = 'error';
209
+ }
210
+ }
211
+ </script>
212
+ </body>
213
+
214
+ </html>
test_api.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+
4
+ # Configuration
5
+ BASE_URL = "http://127.0.0.1:8000"
6
+ API_KEY = "my_secret_key_123"
7
+ TEST_FILE = "test_audio.mp3"
8
+
9
+ def create_dummy_mp3():
10
+ with open(TEST_FILE, "wb") as f:
11
+ f.write(b"\x00" * 1024) # 1KB of dummy data
12
+
13
+ def test_auth_failure():
14
+ print("Testing Authentication Failure...")
15
+ files = {'file': open(TEST_FILE, 'rb')}
16
+ response = requests.post(f"{BASE_URL}/detect", files=files, headers={"x-api-key": "wrong_key"})
17
+ if response.status_code == 401:
18
+ print("PASS: Auth failure handled correctly.")
19
+ else:
20
+ print(f"FAIL: Expected 401, got {response.status_code}")
21
+
22
+ def test_success_flow():
23
+ print("Testing Success Flow...")
24
+ files = {'file': open(TEST_FILE, 'rb')}
25
+ response = requests.post(f"{BASE_URL}/detect", files=files, headers={"x-api-key": API_KEY})
26
+
27
+ if response.status_code == 200:
28
+ data = response.json()
29
+ print("PASS: Request successful.")
30
+ print("Response:", data)
31
+
32
+ # Verify structure
33
+ if "analysis" in data and "is_human" in data["analysis"]:
34
+ print("PASS: JSON structure validated.")
35
+ else:
36
+ print("FAIL: JSON structure incorrect.")
37
+ else:
38
+ print(f"FAIL: Expected 200, got {response.status_code}. Text: {response.text}")
39
+
40
+ if __name__ == "__main__":
41
+ create_dummy_mp3()
42
+ try:
43
+ test_auth_failure()
44
+ test_success_flow()
45
+ except Exception as e:
46
+ print(f"An error occurred: {e}")
47
+ finally:
48
+ if os.path.exists(TEST_FILE):
49
+ os.remove(TEST_FILE)
test_details.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ def test_api_with_url():
4
+ url = "http://127.0.0.1:8000/detect"
5
+ headers = {"x-api-key": "my_secret_key_123"}
6
+ data = {
7
+ "message": "Testing audio URL",
8
+ "audio_url": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" # A public sample
9
+ }
10
+
11
+ print("Testing with audio_url...")
12
+ try:
13
+ response = requests.post(url, headers=headers, data=data)
14
+ if response.status_code == 200:
15
+ print("PASS: Request successful")
16
+ print(response.json())
17
+ else:
18
+ print(f"FAIL: Status {response.status_code}")
19
+ print(response.text)
20
+ except Exception as e:
21
+ print(f"FAIL: {e}")
22
+
23
+ if __name__ == "__main__":
24
+ test_api_with_url()
test_murf.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ def test_generate_endpoint():
4
+ url = "http://127.0.0.1:8000/generate"
5
+ payload = {
6
+ "text": "Hello",
7
+ "murf_api_key": "dummy_key",
8
+ "voice_id": "en-US-marcus"
9
+ }
10
+
11
+ print("Testing /generate endpoint...")
12
+ response = requests.post(url, json=payload)
13
+
14
+ # We expect 400 because the key is dummy, but this proves the endpoint exists and calls Murf
15
+ if response.status_code == 400:
16
+ print("PASS: Endpoint exists and returned 400 as expected (Invalid Key).")
17
+ print("Response:", response.json())
18
+ elif response.status_code == 200:
19
+ print("FAIL: Unexpected Success with dummy key?")
20
+ else:
21
+ print(f"FAIL: Status {response.status_code}")
22
+ print(response.text)
23
+
24
+ if __name__ == "__main__":
25
+ test_generate_endpoint()
verify_murf_key.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ def test_generate_real():
4
+ url = "http://127.0.0.1:8000/generate"
5
+ # Key provided by user
6
+ api_key = "ap2_b71da4b1-5155-47a2-b522-431fe5cb728d"
7
+
8
+ payload = {
9
+ "text": "Hello, verification successful.",
10
+ "murf_api_key": api_key,
11
+ "voice_id": "en-US-marcus"
12
+ }
13
+
14
+ print("Testing /generate with PROVIDED key...")
15
+ try:
16
+ response = requests.post(url, json=payload)
17
+
18
+ if response.status_code == 200:
19
+ data = response.json()
20
+ print("PASS: Audio Generated Successfully!")
21
+ print(f"Audio URL: {data.get('audio_url')}")
22
+ else:
23
+ print(f"FAIL: Status {response.status_code}")
24
+ print(response.text)
25
+ except Exception as e:
26
+ print(f"FAIL: {e}")
27
+
28
+ if __name__ == "__main__":
29
+ test_generate_real()