mMonika commited on
Commit
5491071
·
verified ·
1 Parent(s): adb7efc

create app.py file

Browse files
Files changed (1) hide show
  1. app.py +450 -0
app.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import gspread
3
+ import shutil
4
+ import os
5
+ from datetime import datetime
6
+ from google.cloud import storage
7
+ import os
8
+ import shutil
9
+ import threading
10
+ from google.cloud import speech
11
+
12
+
13
+ # GCS Bucket Configuration
14
+ GCS_BUCKET_NAME = "userrecordings"
15
+ # Google Sheets API setup
16
+ SHEET_URL = "https://docs.google.com/spreadsheets/d/1ZlO_YQyFV6HsZH6hWIEtCw4VHxa7NXKlRULG_2Dkyao/edit"
17
+ PROJECT_CODE = "P2401" # Hardcoded project code
18
+
19
+ def fetch_sheet_data(sheet_url):
20
+ gc = gspread.service_account(filename="/content/story_legacy_service_account.json")
21
+ sh = gc.open_by_url(sheet_url)
22
+ worksheet = sh.sheet1
23
+ data = worksheet.get_all_records()
24
+ return data
25
+
26
+ # Load Google Sheet data
27
+ data = fetch_sheet_data(SHEET_URL)
28
+
29
+ # Global variables to track the state
30
+ current_project = None
31
+ current_prompt_index = 0
32
+ prompts = []
33
+ recording_status = []
34
+ responses = []
35
+
36
+ # Load the project using the hardcoded project code
37
+ current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
38
+ if current_project:
39
+ current_project = current_project[0]
40
+ prompts = [current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")]
41
+ recording_status = [False] * len(prompts) # Initialize recording status for each prompt
42
+ responses = [None] * len(prompts)
43
+ else:
44
+ prompts = []
45
+ custom_html="""
46
+ <style>
47
+ /* Body Styles */
48
+ .gr-container {
49
+ font-family: Arial, sans-serif !important;
50
+ background-color: #F5F5DC !important;
51
+ color: #333 !important;
52
+ }
53
+
54
+ /* Row Styles */
55
+ .gr-row {
56
+ display: flex;
57
+ justify-content: center;
58
+ align-items: center;
59
+ margin: 15px 0;
60
+ padding: 10px;
61
+ width: 100%;
62
+ }
63
+
64
+ /* Audio Component Styles */
65
+ .gr-audio {
66
+ background-color: #F5F5DC;
67
+ padding: 10px 10px;
68
+ border-radius: 10px;
69
+ font-size:20px;
70
+ color:blue;
71
+ }
72
+
73
+ .gr-audio button[title="Clear"] {
74
+ display: none !important;
75
+ }
76
+
77
+ /* Prompt style */
78
+ .gr-prompt {
79
+ background-color: #F5F5DC;
80
+ color: #00008B; /* Deep Blue */
81
+ padding: 10px 10px;
82
+ border-radius: 10px;
83
+
84
+ }
85
+ .gr-submit_button{
86
+ background-color: #28a745;
87
+ color: white;
88
+ border: none;
89
+ padding: 10px 20px;
90
+ border-radius: 20px;
91
+ font-size: 20px;
92
+ cursor: pointer;
93
+ margin: 5px;
94
+ }
95
+
96
+ /* Erase button */
97
+ .gr-erase_button {
98
+ background-color: #EE4B2B;
99
+ color: white;
100
+ border: none;
101
+ padding: 10px 20px;
102
+ border-radius: 20px;
103
+ font-size: 20px;
104
+ cursor: pointer;
105
+ margin: 5px;
106
+ }
107
+
108
+ .gr-erase_button:hover {
109
+ background-color: #A52A2A;
110
+ }
111
+
112
+ /* NExt button */
113
+ .gr-next_button {
114
+ background-color: #28a745;
115
+ color: white;
116
+ border: none;
117
+ padding: 10px 20px;
118
+ border-radius: 20px;
119
+ font-size: 20px;
120
+ cursor: pointer;
121
+ margin: 5px;
122
+ }
123
+
124
+ .gr-next_button:hover {
125
+ background-color: #218838;
126
+ }
127
+
128
+ .warning-alert {
129
+ font-size: 16px;
130
+ color: black;
131
+ margin-top: 2px;
132
+ text-align: center;
133
+ font-weight: bold;
134
+ border: 1px solid #d9534f;
135
+ padding: 10px;
136
+ background-color: #FFED01; /* Light red background for alert */
137
+
138
+ }
139
+ .fail-alert {
140
+ font-size: 16px;
141
+ color: black;
142
+ margin-top: 2px;
143
+ text-align: center;
144
+ font-weight: bold;
145
+ border: 1px solid #d9534f;
146
+ padding: 10px;
147
+ background-color: #FF0000; /* Light red background for alert */
148
+ }
149
+ .sucess-alert {
150
+ font-size: 16px;
151
+ color: black;
152
+ margin-top: 2px;
153
+ text-align: center;
154
+ font-weight: bold;
155
+ border: 1px solid #d9534f;
156
+ padding: 10px;
157
+ background-color:#90EE90; /* Light red background for alert */
158
+ }
159
+ .story-legacy-heading {
160
+ background-color: White;
161
+ font-size: 30px;
162
+ font-weight: bold;
163
+ color:#00008B;
164
+ text-align: left;
165
+ margin-top:-10px;
166
+ margin-bottom: 10px;
167
+ }
168
+ .project-code {
169
+ background-color: White;
170
+ font-size: 30px;
171
+ font-weight: bold;
172
+ color:#00008B;
173
+ text-align: right;
174
+ margin-top:-10px;
175
+ margin-bottom: 10px;
176
+ }
177
+ .prompt-class {
178
+ font-weight: bold;
179
+ color:#00008B;
180
+ font-size: 20px;
181
+
182
+ }
183
+ .gr-markdown {
184
+ position: fixed;
185
+ width: 95%;
186
+ z-index: 1000;
187
+ border-radius: 5px;
188
+ margin-top:-10px;
189
+ margin-bottom: 20px;
190
+ }
191
+ .prompt-text {
192
+ font-size: 15px;
193
+ }
194
+ </style>
195
+ """
196
+ # Function to transcribe audio files and save them locally
197
+ def transcribe_audio(local_file_path):
198
+ """Transcribes audio data from a Google Cloud Storage bucket."""
199
+
200
+ with open(local_file_path, "rb") as audio_file:
201
+ content = audio_file.read()
202
+
203
+ client = speech.SpeechClient()
204
+ audio = speech.RecognitionAudio(content=content)
205
+ config = speech.RecognitionConfig(
206
+ encoding=speech.RecognitionConfig.AudioEncoding.LINEAR16,
207
+ sample_rate_hertz=44100, # Adjust if needed
208
+ language_code="en-US",
209
+ )
210
+ response = client.recognize(config=config, audio=audio)
211
+ return response.results[0].alternatives[0].transcript if response.results else "No transcription available."
212
+
213
+ # function to upload responses and generate transcripts
214
+ def upload_to_gcs_and_cleanup():
215
+ try:
216
+ # Initialize GCS client
217
+ client = storage.Client(project="story-legacy-442314")
218
+ bucket = client.bucket(GCS_BUCKET_NAME)
219
+
220
+ # Walk through the responses directory
221
+ for root, _, files in os.walk(f"responses/{PROJECT_CODE}"):
222
+ for file in files:
223
+ local_path = os.path.join(root, file)
224
+
225
+ # Define the relative path for GCS
226
+ relative_path = os.path.relpath(local_path, f"responses/{PROJECT_CODE}")
227
+ gcs_path = f"{PROJECT_CODE}/responses/{relative_path}"
228
+
229
+ # Upload the file to GCS
230
+ blob = bucket.blob(gcs_path)
231
+ blob.upload_from_filename(local_path)
232
+ print(f"Uploaded {local_path} to {gcs_path}")
233
+
234
+ # Generate the transcript
235
+ transcript_text = transcribe_audio(local_path)
236
+
237
+ # Save the transcript in GCS under the transcripts folder with the new structure
238
+ transcript_relative_path = relative_path.replace("responses/", "") # Remove "responses/" from path
239
+ transcript_gcs_path = f"{PROJECT_CODE}/transcripts/{transcript_relative_path.replace(os.path.splitext(file)[1], '.txt')}"
240
+ transcript_blob = bucket.blob(transcript_gcs_path)
241
+ transcript_blob.upload_from_string(transcript_text)
242
+ print(f"Transcript uploaded to {transcript_gcs_path}")
243
+
244
+ # Delete the local response file
245
+ os.remove(local_path)
246
+
247
+ # Remove the responses directory after successful upload
248
+ response_dir = f"responses/{PROJECT_CODE}"
249
+ if os.path.exists(response_dir):
250
+ shutil.rmtree(response_dir)
251
+ print(f"Local directory {response_dir} has been deleted.")
252
+
253
+ return True, "All responses and transcripts have been uploaded to GCS, and local files deleted."
254
+ except Exception as e:
255
+ print(f"Error during GCS upload: {e}")
256
+ return False, f"Error during GCS upload: {str(e)}"
257
+
258
+ # Background task function
259
+ def async_upload_to_gcs_and_cleanup():
260
+ success, message = upload_to_gcs_and_cleanup()
261
+ print(message)
262
+
263
+
264
+
265
+ def submit_audio(audio_path):
266
+ global current_prompt_index
267
+ print(f"Received audio_path: {audio_path}") # Debugging line to check the value
268
+
269
+ # Check if the response has already been submitted
270
+ if recording_status[current_prompt_index]:
271
+ return (
272
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div> <div class="prompt-text">{prompts[current_prompt_index]}</div>""",
273
+ gr.update(value=f"""<div class="sucess-alert">⚠️ Response for Prompt {current_prompt_index + 1} has already been submitted.</div>""", visible=True),
274
+ )
275
+
276
+ # Check if no audio is recorded
277
+ if audio_path is None:
278
+ return (
279
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
280
+ gr.update(value="", visible=True),
281
+ )
282
+
283
+ try:
284
+ # Ensure the directory exists and move the file
285
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
286
+ folder_path = f"responses/{PROJECT_CODE}/Prompt{current_prompt_index + 1}"
287
+ os.makedirs(folder_path, exist_ok=True)
288
+ file_path = f"{folder_path}/response_{timestamp}.wav"
289
+ shutil.move(audio_path, file_path)
290
+
291
+ # Update recording status and responses
292
+ recording_status[current_prompt_index] = True
293
+ responses[current_prompt_index] = file_path
294
+
295
+ return (
296
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
297
+ gr.update(value=f"""<div class = "sucess-alert">Response for Prompt {current_prompt_index + 1} has been submitted successfully!</div>""", visible=True),
298
+ )
299
+ except Exception as e:
300
+ print(f"Error occurred during submission: {e}") # Debugging line for exception
301
+ return (
302
+ f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
303
+ gr.update(value=f"""<div class= "fail-alert">⚠️ An error occurred during submission: {str(e)}</div>""", visible=True),
304
+ )
305
+
306
+ def save_and_next():
307
+ global current_prompt_index
308
+ if not recording_status[current_prompt_index]:
309
+ return (
310
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text">{prompts[current_prompt_index]}</div>""",
311
+ gr.update(value=f"""<div class="warning-alert">⚠️ Please record and submit your response before moving to the next prompt.</div>""", visible=True),
312
+ gr.update(visible=True),
313
+ gr.update(visible=True),
314
+ gr.update(visible=True),
315
+
316
+ )
317
+
318
+ if current_prompt_index < len(prompts) - 1:
319
+ current_prompt_index += 1
320
+ is_last_prompt = current_prompt_index == len(prompts) - 1
321
+ next_button_text = "Finish" if is_last_prompt else f"Next Prompt ({current_prompt_index + 2} of {len(prompts)})"
322
+ return (
323
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
324
+ gr.update(value="", visible=False), # Clear the alert message
325
+ gr.update(value=None, visible=True),
326
+ gr.update(value=next_button_text, visible=True),
327
+ gr.update(visible=True),
328
+
329
+ )
330
+ else:
331
+ # "Finish" button clicked
332
+ threading.Thread(target=async_upload_to_gcs_and_cleanup, daemon=True).start()
333
+
334
+ # print(upload_to_gcs_and_cleanup())
335
+ # alert_class = "success-alert" if success else "fail-alert"
336
+ return (
337
+ "Thank you for your participation in this research, all of your stories have been submitted. We place great value on your stories and will treat them with the respect they deserve.",
338
+ gr.update(value="", visible=True),
339
+ gr.update(visible=False),
340
+ gr.update(visible=False),
341
+ gr.update(visible=False),
342
+
343
+ )
344
+
345
+ def erase_and_record():
346
+ global current_prompt_index
347
+ # Check if a response exists for the current prompt
348
+ if responses[current_prompt_index]:
349
+ # Delete the file
350
+ try:
351
+ os.remove(responses[current_prompt_index])
352
+ except FileNotFoundError:
353
+ pass # Ignore if the file doesn't exist
354
+ responses[current_prompt_index] = None # Reset the response
355
+ recording_status[current_prompt_index] = False # Reset recording status
356
+
357
+ # Return updated prompt and alert message
358
+ return (
359
+ f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
360
+ gr.update(value=None, visible=True), # Reset the audio input
361
+ gr.update(value=f"""<div class= "sucess-alert">Your previous response has been erased. Please re-record your response.</div>""", visible=True), # Updated alert message
362
+ )
363
+ else:
364
+ return (
365
+ f"""<div class="prompt-class">Prompt {current_prompt_index + 1}/{len(prompts)}:</div><div class="prompt-text"> {prompts[current_prompt_index]}</div>""",
366
+ gr.update(visible=True), # Reset the audio input
367
+ gr.update(value=f"""<div class= "warning-alert">⚠️ Please record and submit your response first.</div>""", visible=True), # Updated alert message
368
+ )
369
+ # Gradio Interface
370
+ def gradio_app():
371
+ # reset_state()
372
+ with gr.Blocks(css=custom_html) as demo:
373
+ with gr.Row():
374
+ with gr.Blocks(css=custom_html):
375
+ gr.Markdown("""
376
+ <div class="story-legacy-heading">StoryLegacy</div>
377
+ """)
378
+ gr.Markdown(f"""<div class="project-code">Project: {PROJECT_CODE}</div>""")
379
+ with gr.Row():
380
+ alert_box = gr.Markdown("", elem_id="alert-box", visible=False)
381
+ with gr.Row():
382
+ prompt_display = gr.Markdown(
383
+ f"""<div class ="prompt-class">Prompt {current_prompt_index + 1} of {len(prompts)}:</div> <div class="prompt-text">{prompts[0] if prompts else 'No prompts available.'}</div>",
384
+ """,elem_classes=["gr-prompt"]
385
+ )
386
+ with gr.Row():
387
+ # Create a hidden label to dynamically update microphone status
388
+ record_audio = gr.Audio(
389
+ sources="microphone",
390
+ type="filepath",
391
+ label="Record your response",
392
+ elem_classes=["gr-audio"]
393
+ )
394
+ with gr.Row():
395
+ erase_button = gr.Button("Erase & Re - Record Current Prompt", elem_classes=["gr-erase_button"])
396
+ save_next_button = gr.Button(f"Next Prompt (2 of {len(prompts)})", elem_classes=["gr-next_button"])
397
+
398
+ record_audio.change(submit_audio, inputs=record_audio, outputs=[prompt_display,alert_box])
399
+
400
+ save_next_button.click(
401
+ save_and_next,
402
+ inputs=[],
403
+ outputs=[
404
+ prompt_display,
405
+ alert_box,
406
+ record_audio,
407
+ save_next_button,
408
+ erase_button,
409
+
410
+ ],
411
+ )
412
+ erase_button.click(
413
+ erase_and_record,
414
+ inputs=[],
415
+ outputs=[
416
+ prompt_display,
417
+ record_audio,
418
+ alert_box,
419
+
420
+ ],
421
+ )
422
+
423
+ @demo.load
424
+ def initialize_state():
425
+ """Initialize or reset the app's state when reloaded."""
426
+ global current_project, current_prompt_index, prompts, recording_status, responses
427
+
428
+ # Load the Google Sheet data
429
+ data = fetch_sheet_data(SHEET_URL)
430
+
431
+ # Load the project using the hardcoded project code
432
+ current_project = [row for row in data if row["Project Code"] == PROJECT_CODE]
433
+ if current_project:
434
+ current_project = current_project[0]
435
+ prompts = [current_project[f"Prompt{i+1}"] for i in range(4) if current_project.get(f"Prompt{i+1}")]
436
+ recording_status = [False] * len(prompts)
437
+ responses = [None] * len(prompts)
438
+ else:
439
+ prompts = []
440
+
441
+ # Reset the current prompt index
442
+ current_prompt_index = 0
443
+ print("State has been initialized.")
444
+
445
+ return demo
446
+
447
+ # Run the app
448
+ # reset_state()
449
+ app = gradio_app()
450
+ app.launch(share=True)