norhan12 commited on
Commit
69c8a2d
·
verified ·
1 Parent(s): 93878a6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -124
app.py CHANGED
@@ -1,137 +1,83 @@
1
-
2
-
 
3
  import os
4
  import uuid
5
  import shutil
6
  import json
7
- from fastapi import FastAPI, HTTPException, Query
8
- from fastapi.responses import JSONResponse, FileResponse
9
- from pydantic import BaseModel
10
  import requests
11
- from process_interview import process_interview # Ensure process_interview function is available
12
-
13
  from fastapi.staticfiles import StaticFiles
 
14
 
15
- # Initialize FastAPI app
16
  app = FastAPI()
17
 
18
- # --- Directory Setup ---
19
- BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Get the directory of the current script
20
- TEMP_DIR = os.path.join(BASE_DIR, "temp_files") # Temporary storage for downloaded audio files
21
- STATIC_DIR = os.path.join(BASE_DIR, "static") # Root directory for all static assets
22
- OUTPUT_DIR = os.path.join(STATIC_DIR, "outputs") # Subdirectory within static for generated reports
23
 
24
- # Create directories if they don't exist
25
  os.makedirs(TEMP_DIR, exist_ok=True)
26
- os.makedirs(OUTPUT_DIR, exist_ok=True)
 
27
 
28
- # Mount the 'static' directory to be served at the '/static' URL path.
29
- # This means files in ./static/outputs/filename will be accessible at /static/outputs/filename
30
- app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
31
 
32
- # --- Configuration Constants ---
33
  VALID_EXTENSIONS = ('.wav', '.mp3', '.m4a', '.flac')
34
  MAX_FILE_SIZE_MB = 300
35
 
36
- # BASE_URL should be the root URL of your deployed application.
37
- # Example: "https://evalbot-audio-evalbot.hf.space" when deployed on Hugging Face.
38
- # Example: "http://localhost:8000" when running locally with Uvicorn.
39
- # It should NOT include "/static" or any subpaths.
40
  BASE_URL = os.getenv("BASE_URL", "https://evalbot-audio-evalbot.hf.space")
41
 
42
- # --- Pydantic Models ---
43
  class ProcessResponse(BaseModel):
44
- """
45
- Defines the structure of the response returned by the /process-audio endpoint.
46
- Includes a summary of the analysis and public URLs to the detailed JSON and PDF reports.
47
- """
48
  summary: str
49
  json_url: str
50
  pdf_url: str
51
 
52
- # --- Helper Function for URL Generation ---
53
- def generate_public_output_url(filename: str, file_type: str) -> str:
54
- """
55
- Constructs a public URL for a generated output file (JSON or PDF).
56
- The files are stored in STATIC_DIR/outputs/.
57
- """
58
- # The path relative to the mounted /static directory will be "outputs/json/filename" or "outputs/pdf/filename"
59
- # We join it directly to BASE_URL/static/
60
- return f"{BASE_URL}/static/outputs/{file_type}/{filename}"
61
-
62
- # --- API Endpoints ---
63
- @app.get("/")
64
- async def root():
65
- """
66
- Root endpoint for the API. Returns a simple message to confirm the API is running.
67
- """
68
- return {"message": "EvalBot API is running"}
69
 
70
  @app.post("/process-audio", response_model=ProcessResponse)
71
- async def process_audio(
72
- file_url: str = Query(..., description="URL of the audio file to be processed"),
73
- user_id: str = Query(..., description="Unique identifier for the user")
74
- ):
75
- """
76
- Main endpoint to process an audio file.
77
- Downloads the audio, performs interview analysis using `process_interview`,
78
- saves the generated reports, and returns their public URLs.
79
- """
80
  try:
81
- # Validate audio file extension
82
- file_ext = os.path.splitext(file_url)[1].lower()
83
  if file_ext not in VALID_EXTENSIONS:
84
- raise HTTPException(status_code=400, detail=f"Invalid file extension: {file_ext}. Supported: {', '.join(VALID_EXTENSIONS)}")
85
 
86
- # Generate a unique temporary filename for the downloaded audio
87
  local_filename = f"{user_id}_{uuid.uuid4().hex}{file_ext}"
88
  local_path = os.path.join(TEMP_DIR, local_filename)
89
 
90
- # Download the audio file from the provided URL
91
- try:
92
- resp = requests.get(file_url, stream=True, timeout=30)
93
- resp.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
94
- with open(local_path, "wb") as f:
95
- for chunk in resp.iter_content(chunk_size=8192):
96
- if chunk: # Write only non-empty chunks
97
- f.write(chunk)
98
- except requests.exceptions.RequestException as e:
99
- raise HTTPException(status_code=400, detail=f"Failed to download the file from URL: {e}")
100
-
101
- # Validate the size of the downloaded file
102
  file_size_mb = os.path.getsize(local_path) / (1024 * 1024)
103
  if file_size_mb > MAX_FILE_SIZE_MB:
104
- os.remove(local_path) # Clean up the oversized file
105
- raise HTTPException(status_code=400, detail=f"File too large: {file_size_mb:.2f} MB. Max allowed: {MAX_FILE_SIZE_MB} MB")
106
 
107
- # Process the interview audio using the `process_interview` module
108
  result = process_interview(local_path)
109
  if not result:
110
- os.remove(local_path) # Clean up temporary audio file
111
- raise HTTPException(status_code=500, detail="Audio processing failed: `process_interview` returned no result.")
112
-
113
- # Get the source paths of the generated JSON and PDF reports from `process_interview`
114
- json_src_path = result['json_path']
115
- pdf_src_path = result['pdf_path']
116
 
117
- # Generate unique destination filenames for the reports within the public 'static/outputs' directory
118
  json_dest_name = f"{user_id}_{uuid.uuid4().hex}.json"
119
  pdf_dest_name = f"{user_id}_{uuid.uuid4().hex}.pdf"
120
 
121
- # Define the full destination paths for copying the reports
122
- json_dest_full_path = os.path.join(OUTPUT_DIR, "json", json_dest_name) # Ensure sub-folders
123
- pdf_dest_full_path = os.path.join(OUTPUT_DIR, "pdf", pdf_dest_name) # Ensure sub-folders
124
 
125
- # Create subdirectories if they don't exist
126
- os.makedirs(os.path.dirname(json_dest_full_path), exist_ok=True)
127
- os.makedirs(os.path.dirname(pdf_dest_full_path), exist_ok=True)
128
-
129
- # Copy the generated reports to their final public locations
130
- shutil.copyfile(json_src_path, json_dest_full_path)
131
- shutil.copyfile(pdf_src_path, pdf_dest_full_path)
132
 
133
- # Read analysis data from the JSON report to create the summary for the response
134
- with open(json_src_path, "r") as jf: # Read from the source path, as it's guaranteed to be complete
135
  analysis_data = json.load(jf)
136
 
137
  voice = analysis_data.get('voice_analysis', {}).get('interpretation', {})
@@ -146,47 +92,28 @@ async def process_audio(
146
  f"Anxiety: {voice.get('anxiety_level', 'N/A')}"
147
  )
148
 
149
- # Generate the public URLs using the helper function
150
- json_url = generate_public_output_url(json_dest_name, "json")
151
- pdf_url = generate_public_output_url(pdf_dest_name, "pdf")
152
 
153
- # Clean up the temporary downloaded audio file
154
  os.remove(local_path)
155
 
156
  return ProcessResponse(summary=summary, json_url=json_url, pdf_url=pdf_url)
157
 
158
- except HTTPException as e:
159
- # Re-raise explicit HTTPExceptions (e.g., 400 for bad requests)
160
- raise e
161
  except Exception as e:
162
- # Catch any other unexpected errors and return a 500 Internal Server Error
163
- # Log the full traceback for debugging
164
- import traceback
165
- traceback.print_exc()
166
- raise HTTPException(status_code=500, detail=f"An internal server error occurred: {str(e)}")
167
-
168
- # --- GET Endpoints for Direct File Access ---
169
- # These endpoints allow direct access to the generated JSON and PDF reports.
170
- # The URL paths are designed to match how files are mounted by StaticFiles.
171
-
172
- @app.get("/static/outputs/json/{filename}")
173
- async def get_json_report(filename: str):
174
- """
175
- Serves a JSON analysis file directly from the 'static/outputs/json' directory.
176
- Example URL: https://evalbot-audio-evalbot.hf.space/static/outputs/json/candidate-123_uuid.json
177
- """
178
- file_path = os.path.join(OUTPUT_DIR, "json", filename) # Construct full path
179
  if not os.path.exists(file_path):
180
  raise HTTPException(status_code=404, detail="JSON file not found")
181
  return FileResponse(file_path, media_type="application/json", filename=filename)
182
 
183
- @app.get("/static/outputs/pdf/{filename}")
184
- async def get_pdf_report(filename: str):
185
- """
186
- Serves a PDF report file directly from the 'static/outputs/pdf' directory.
187
- Example URL: https://evalbot-audio-evalbot.hf.space/static/outputs/pdf/candidate-123_uuid.pdf
188
- """
189
- file_path = os.path.join(OUTPUT_DIR, "pdf", filename) # Construct full path
190
  if not os.path.exists(file_path):
191
  raise HTTPException(status_code=404, detail="PDF file not found")
192
- return FileResponse(file_path, media_type="application/pdf", filename=filename)
 
1
+ norhan shalaby, [6/12/2025 7:24 AM]
2
+ from fastapi import FastAPI, HTTPException, Body
3
+ from pydantic import BaseModel, HttpUrl
4
  import os
5
  import uuid
6
  import shutil
7
  import json
 
 
 
8
  import requests
9
+ from process_interview import process_interview
 
10
  from fastapi.staticfiles import StaticFiles
11
+ from fastapi.responses import FileResponse
12
 
 
13
  app = FastAPI()
14
 
15
+ TEMP_DIR = "./temp_files"
16
+ OUTPUT_DIR = "./static/outputs"
17
+ JSON_DIR = os.path.join(OUTPUT_DIR, "json")
18
+ PDF_DIR = os.path.join(OUTPUT_DIR, "pdf")
 
19
 
 
20
  os.makedirs(TEMP_DIR, exist_ok=True)
21
+ os.makedirs(JSON_DIR, exist_ok=True)
22
+ os.makedirs(PDF_DIR, exist_ok=True)
23
 
24
+ app.mount("/static", StaticFiles(directory="static"), name="static")
 
 
25
 
 
26
  VALID_EXTENSIONS = ('.wav', '.mp3', '.m4a', '.flac')
27
  MAX_FILE_SIZE_MB = 300
28
 
 
 
 
 
29
  BASE_URL = os.getenv("BASE_URL", "https://evalbot-audio-evalbot.hf.space")
30
 
 
31
  class ProcessResponse(BaseModel):
 
 
 
 
32
  summary: str
33
  json_url: str
34
  pdf_url: str
35
 
36
+ class ProcessAudioRequest(BaseModel):
37
+ file_url: HttpUrl
38
+ user_id: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  @app.post("/process-audio", response_model=ProcessResponse)
41
+ async def process_audio(request: ProcessAudioRequest = Body(...)):
42
+ file_url = request.file_url
43
+ user_id = request.user_id
 
 
 
 
 
 
44
  try:
45
+ file_ext = os.path.splitext(str(file_url))[1].lower()
 
46
  if file_ext not in VALID_EXTENSIONS:
47
+ raise HTTPException(status_code=400, detail=f"Invalid file extension: {file_ext}")
48
 
 
49
  local_filename = f"{user_id}_{uuid.uuid4().hex}{file_ext}"
50
  local_path = os.path.join(TEMP_DIR, local_filename)
51
 
52
+ resp = requests.get(str(file_url), stream=True, timeout=30)
53
+ if resp.status_code != 200:
54
+ raise HTTPException(status_code=400, detail="Could not download the file from URL")
55
+
56
+ with open(local_path, "wb") as f:
57
+ for chunk in resp.iter_content(chunk_size=8192):
58
+ if chunk:
59
+ f.write(chunk)
60
+
 
 
 
61
  file_size_mb = os.path.getsize(local_path) / (1024 * 1024)
62
  if file_size_mb > MAX_FILE_SIZE_MB:
63
+ os.remove(local_path)
64
+ raise HTTPException(status_code=400, detail=f"File too large: {file_size_mb:.2f} MB")
65
 
 
66
  result = process_interview(local_path)
67
  if not result:
68
+ os.remove(local_path)
69
+ raise HTTPException(status_code=500, detail="Processing failed")
 
 
 
 
70
 
 
71
  json_dest_name = f"{user_id}_{uuid.uuid4().hex}.json"
72
  pdf_dest_name = f"{user_id}_{uuid.uuid4().hex}.pdf"
73
 
74
+ json_dest = os.path.join(JSON_DIR, json_dest_name)
75
+ pdf_dest = os.path.join(PDF_DIR, pdf_dest_name)
 
76
 
77
+ shutil.copyfile(result['json_path'], json_dest)
78
+ shutil.copyfile(result['pdf_path'], pdf_dest)
 
 
 
 
 
79
 
80
+ with open(result['json_path'], "r") as jf:
 
81
  analysis_data = json.load(jf)
82
 
83
  voice = analysis_data.get('voice_analysis', {}).get('interpretation', {})
 
92
  f"Anxiety: {voice.get('anxiety_level', 'N/A')}"
93
  )
94
 
95
+ json_url = f"{BASE_URL}/outputs/json/{json_dest_name}"
96
+ pdf_url = f"{BASE_URL}/outputs/pdf/{pdf_dest_name}"
 
97
 
 
98
  os.remove(local_path)
99
 
100
  return ProcessResponse(summary=summary, json_url=json_url, pdf_url=pdf_url)
101
 
 
 
 
102
  except Exception as e:
103
+ raise HTTPException(status_code=500, detail=str(e))
104
+
105
+
106
+ @app.get("/outputs/json/{filename}")
107
+ async def get_json_file(filename: str):
108
+ file_path = os.path.join(JSON_DIR, filename)
 
 
 
 
 
 
 
 
 
 
 
109
  if not os.path.exists(file_path):
110
  raise HTTPException(status_code=404, detail="JSON file not found")
111
  return FileResponse(file_path, media_type="application/json", filename=filename)
112
 
113
+ norhan shalaby, [6/12/2025 7:24 AM]
114
+ @app.get("/outputs/pdf/{filename}")
115
+ async def get_pdf_file(filename: str):
116
+ file_path = os.path.join(PDF_DIR, filename)
 
 
 
117
  if not os.path.exists(file_path):
118
  raise HTTPException(status_code=404, detail="PDF file not found")
119
+ return FileResponse(file_path, media_type="application/pdf", filename=filename)