minhahwang Copilot commited on
Commit
d70ebd4
·
1 Parent(s): e99dd36

fix: XSS sanitization, per-request audio files, soundfile-based ASR

Browse files

- HTML-escape LLM output and user input before rendering in gr.HTML
- Use UUID-based filenames for Q&A audio to prevent concurrent overwrites
- Load audio via soundfile instead of ffmpeg for cross-platform ASR support

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Files changed (2) hide show
  1. app.py +7 -3
  2. inference.py +9 -1
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  import math
3
  import struct
4
  import wave
@@ -907,6 +908,7 @@ with gr.Blocks(css=css_code, title="VoiceBook Gradio Hub") as demo:
907
  from tts import split_into_chunks as _split, generate_audio_stream as _gen_stream
908
  import soundfile as sf
909
  import numpy as np
 
910
 
911
  chunks = _split(answer_text)
912
  audio_segments = []
@@ -917,20 +919,22 @@ with gr.Blocks(css=css_code, title="VoiceBook Gradio Hub") as demo:
917
  sample_rate = sr
918
  if audio_segments:
919
  full_audio = np.concatenate(audio_segments)
920
- answer_audio_path = "sample_sounds/qa_answer.wav"
921
  sf.write(answer_audio_path, full_audio, sample_rate)
922
  except Exception:
923
  # Fall back to no audio if TTS fails
924
  pass
925
 
 
 
926
  answer_html = f"""
927
  <div style="margin-top: 12px; padding: 16px; background: rgba(240,253,244,0.1); border: 1px solid rgba(187,247,208,0.3); border-radius: 14px;">
928
  <div style="font-size: 10px; font-weight: 700; text-transform: uppercase; color: #4ade80; margin-bottom: 6px;">Answer in Narrator's Voice</div>
929
  <div style="font-family: 'Playfair Display', Georgia, serif; font-style: italic; color: #FAF7F2; font-size: 13px; line-height: 1.6;">
930
- &ldquo;{answer_text}&rdquo;
931
  </div>
932
  <div style="margin-top: 8px; font-size: 10px; color: #94a3b8;">
933
- Q: <em>{q_text}</em>
934
  </div>
935
  </div>
936
  """
 
1
  import os
2
+ import html
3
  import math
4
  import struct
5
  import wave
 
908
  from tts import split_into_chunks as _split, generate_audio_stream as _gen_stream
909
  import soundfile as sf
910
  import numpy as np
911
+ import uuid
912
 
913
  chunks = _split(answer_text)
914
  audio_segments = []
 
919
  sample_rate = sr
920
  if audio_segments:
921
  full_audio = np.concatenate(audio_segments)
922
+ answer_audio_path = f"sample_sounds/qa_answer_{uuid.uuid4().hex[:8]}.wav"
923
  sf.write(answer_audio_path, full_audio, sample_rate)
924
  except Exception:
925
  # Fall back to no audio if TTS fails
926
  pass
927
 
928
+ safe_answer = html.escape(answer_text)
929
+ safe_question = html.escape(q_text)
930
  answer_html = f"""
931
  <div style="margin-top: 12px; padding: 16px; background: rgba(240,253,244,0.1); border: 1px solid rgba(187,247,208,0.3); border-radius: 14px;">
932
  <div style="font-size: 10px; font-weight: 700; text-transform: uppercase; color: #4ade80; margin-bottom: 6px;">Answer in Narrator's Voice</div>
933
  <div style="font-family: 'Playfair Display', Georgia, serif; font-style: italic; color: #FAF7F2; font-size: 13px; line-height: 1.6;">
934
+ &ldquo;{safe_answer}&rdquo;
935
  </div>
936
  <div style="margin-top: 8px; font-size: 10px; color: #94a3b8;">
937
+ Q: <em>{safe_question}</em>
938
  </div>
939
  </div>
940
  """
inference.py CHANGED
@@ -35,8 +35,16 @@ def transcribe_audio(audio_path: str) -> str:
35
  """Transcribe an audio file to text using Whisper-small."""
36
  if not audio_path:
37
  return ""
 
 
 
 
 
 
 
 
38
  pipe = get_asr_pipeline()
39
- result = pipe(audio_path, generate_kwargs={"language": "en"})
40
  return result.get("text", "").strip()
41
 
42
 
 
35
  """Transcribe an audio file to text using Whisper-small."""
36
  if not audio_path:
37
  return ""
38
+ import soundfile as sf
39
+ import numpy as np
40
+
41
+ audio_data, sample_rate = sf.read(audio_path, dtype="float32")
42
+ # Convert stereo to mono if needed
43
+ if len(audio_data.shape) > 1:
44
+ audio_data = audio_data.mean(axis=1)
45
+
46
  pipe = get_asr_pipeline()
47
+ result = pipe({"raw": audio_data, "sampling_rate": sample_rate}, generate_kwargs={"language": "en"})
48
  return result.get("text", "").strip()
49
 
50