Spaces:
Paused
Paused
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>
- app.py +7 -3
- 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/
|
| 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 |
-
“{
|
| 931 |
</div>
|
| 932 |
<div style="margin-top: 8px; font-size: 10px; color: #94a3b8;">
|
| 933 |
-
Q: <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 |
+
“{safe_answer}”
|
| 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(
|
| 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 |
|