Update app.py
Browse files
app.py
CHANGED
@@ -12,9 +12,6 @@ import logging
|
|
12 |
from dash.exceptions import PreventUpdate
|
13 |
import pandas as pd
|
14 |
import time
|
15 |
-
import os
|
16 |
-
from huggingface_hub import HfApi
|
17 |
-
from dash import callback_context
|
18 |
|
19 |
# Set up logging
|
20 |
logging.basicConfig(level=logging.INFO)
|
@@ -23,12 +20,8 @@ logger = logging.getLogger(__name__)
|
|
23 |
# Initialize Dash app
|
24 |
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
25 |
|
26 |
-
# Initialize Gemini AI
|
27 |
-
|
28 |
-
if not gemini_api_key:
|
29 |
-
raise ValueError("GEMINI_API_KEY not found in environment variables. Please set it as a secret in your Hugging Face Space.")
|
30 |
-
|
31 |
-
genai.configure(api_key=gemini_api_key)
|
32 |
|
33 |
def generate_podcast_script(api_key, content, duration, num_hosts):
|
34 |
genai.configure(api_key=api_key)
|
@@ -47,8 +40,6 @@ def generate_podcast_script(api_key, content, duration, num_hosts):
|
|
47 |
Do not use any special characters or markdown. Only include the monologue with proper punctuation.
|
48 |
Ensure the content flows naturally and stays relevant to the topic.
|
49 |
Limit the script length to match the requested duration of {duration}.
|
50 |
-
Do not put an intro our outro music as I only need the dialog
|
51 |
-
The dialog must have proper punctuation like apostrophes
|
52 |
"""
|
53 |
else:
|
54 |
prompt = f"""
|
@@ -63,8 +54,6 @@ def generate_podcast_script(api_key, content, duration, num_hosts):
|
|
63 |
Do not use any special characters or markdown. Only include the alternating dialogue lines with proper punctuation.
|
64 |
Ensure the conversation flows naturally and stays relevant to the topic.
|
65 |
Limit the script length to match the requested duration of {duration}.
|
66 |
-
Do not put an intro our outro music as I only need the dialog
|
67 |
-
The dialog must have proper punctuation like apostrophes
|
68 |
"""
|
69 |
|
70 |
response = model.generate_content(prompt)
|
@@ -166,6 +155,7 @@ app.layout = dbc.Container([
|
|
166 |
|
167 |
dbc.Card([
|
168 |
dbc.CardBody([
|
|
|
169 |
dbc.Textarea(id="content-input", placeholder="Paste your content or upload a document", rows=5, className="my-3"),
|
170 |
dcc.Upload(
|
171 |
id='document-upload',
|
@@ -254,66 +244,108 @@ def update_voice2_options(lang):
|
|
254 |
|
255 |
@app.callback(
|
256 |
[Output("script-output", "value"),
|
257 |
-
Output("script-progress", "value"),
|
258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
Output("download-audio", "data"),
|
260 |
Output("podcast-progress", "value")],
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
State("duration", "value"),
|
265 |
-
State("num-hosts", "value"),
|
266 |
State("voice1-select", "value"),
|
267 |
State("voice2-select", "value"),
|
268 |
-
State("
|
269 |
prevent_initial_call=True
|
270 |
)
|
271 |
-
def
|
272 |
-
|
273 |
-
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
274 |
-
|
275 |
-
if not ctx.triggered:
|
276 |
raise PreventUpdate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
|
|
287 |
|
288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
try:
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
wav_audio.export(buffer, format="mp3")
|
304 |
-
buffer.seek(0)
|
305 |
-
mp3_bytes = buffer.getvalue()
|
306 |
-
|
307 |
-
# Create base64 audio for playback
|
308 |
-
audio_base64 = base64.b64encode(mp3_bytes).decode('utf-8')
|
309 |
-
audio_src = f"data:audio/mp3;base64,{audio_base64}"
|
310 |
-
|
311 |
-
return dash.no_update, dash.no_update, html.Audio(src=audio_src, controls=True), dcc.send_bytes(mp3_bytes, "podcast.mp3"), 100
|
312 |
except Exception as e:
|
313 |
-
logger.error(f"Error
|
314 |
-
return
|
315 |
-
|
316 |
-
return dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
|
317 |
|
318 |
# Run the app
|
319 |
if __name__ == '__main__':
|
|
|
12 |
from dash.exceptions import PreventUpdate
|
13 |
import pandas as pd
|
14 |
import time
|
|
|
|
|
|
|
15 |
|
16 |
# Set up logging
|
17 |
logging.basicConfig(level=logging.INFO)
|
|
|
20 |
# Initialize Dash app
|
21 |
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
22 |
|
23 |
+
# Initialize Gemini AI
|
24 |
+
genai.configure(api_key='YOUR_GEMINI_API_KEY')
|
|
|
|
|
|
|
|
|
25 |
|
26 |
def generate_podcast_script(api_key, content, duration, num_hosts):
|
27 |
genai.configure(api_key=api_key)
|
|
|
40 |
Do not use any special characters or markdown. Only include the monologue with proper punctuation.
|
41 |
Ensure the content flows naturally and stays relevant to the topic.
|
42 |
Limit the script length to match the requested duration of {duration}.
|
|
|
|
|
43 |
"""
|
44 |
else:
|
45 |
prompt = f"""
|
|
|
54 |
Do not use any special characters or markdown. Only include the alternating dialogue lines with proper punctuation.
|
55 |
Ensure the conversation flows naturally and stays relevant to the topic.
|
56 |
Limit the script length to match the requested duration of {duration}.
|
|
|
|
|
57 |
"""
|
58 |
|
59 |
response = model.generate_content(prompt)
|
|
|
155 |
|
156 |
dbc.Card([
|
157 |
dbc.CardBody([
|
158 |
+
dbc.Input(id="api-key-input", type="password", placeholder="Enter your Gemini API Key"),
|
159 |
dbc.Textarea(id="content-input", placeholder="Paste your content or upload a document", rows=5, className="my-3"),
|
160 |
dcc.Upload(
|
161 |
id='document-upload',
|
|
|
244 |
|
245 |
@app.callback(
|
246 |
[Output("script-output", "value"),
|
247 |
+
Output("script-progress", "value")],
|
248 |
+
Input("generate-btn", "n_clicks"),
|
249 |
+
[State("api-key-input", "value"),
|
250 |
+
State("content-input", "value"),
|
251 |
+
State("duration", "value"),
|
252 |
+
State("num-hosts", "value")],
|
253 |
+
prevent_initial_call=True
|
254 |
+
)
|
255 |
+
def generate_script(n_clicks, api_key, content, duration, num_hosts):
|
256 |
+
if n_clicks is None:
|
257 |
+
raise PreventUpdate
|
258 |
+
try:
|
259 |
+
for i in range(10):
|
260 |
+
time.sleep(0.5) # Simulate progress
|
261 |
+
# Instead of yielding, we'll just pass and update at the end
|
262 |
+
pass
|
263 |
+
script = generate_podcast_script(api_key, content, duration, num_hosts)
|
264 |
+
return script, 100
|
265 |
+
except Exception as e:
|
266 |
+
logger.error(f"Error generating script: {str(e)}")
|
267 |
+
return f"Error: {str(e)}", 0
|
268 |
+
|
269 |
+
@app.callback(
|
270 |
+
[Output("audio-output", "children"),
|
271 |
Output("download-audio", "data"),
|
272 |
Output("podcast-progress", "value")],
|
273 |
+
Input("generate-podcast-btn", "n_clicks"),
|
274 |
+
[State("api-key-input", "value"),
|
275 |
+
State("script-output", "value"),
|
|
|
|
|
276 |
State("voice1-select", "value"),
|
277 |
State("voice2-select", "value"),
|
278 |
+
State("num-hosts", "value")],
|
279 |
prevent_initial_call=True
|
280 |
)
|
281 |
+
def render_and_download_podcast(n_clicks, api_key, script, voice1, voice2, num_hosts):
|
282 |
+
if n_clicks is None:
|
|
|
|
|
|
|
283 |
raise PreventUpdate
|
284 |
+
try:
|
285 |
+
# Run the async function in a synchronous context
|
286 |
+
sample_rate, audio_data = asyncio.run(render_podcast(api_key, script, voice1, voice2, num_hosts))
|
287 |
+
|
288 |
+
# Convert numpy array to WAV
|
289 |
+
wav_audio = AudioSegment(
|
290 |
+
audio_data.tobytes(),
|
291 |
+
frame_rate=sample_rate,
|
292 |
+
sample_width=audio_data.dtype.itemsize,
|
293 |
+
channels=1
|
294 |
+
)
|
295 |
+
|
296 |
+
# Convert WAV to MP3
|
297 |
+
buffer = io.BytesIO()
|
298 |
+
wav_audio.export(buffer, format="mp3")
|
299 |
+
buffer.seek(0)
|
300 |
+
mp3_bytes = buffer.getvalue()
|
301 |
+
|
302 |
+
# Create base64 audio for playback
|
303 |
+
audio_base64 = base64.b64encode(mp3_bytes).decode('utf-8')
|
304 |
+
audio_src = f"data:audio/mp3;base64,{audio_base64}"
|
305 |
+
|
306 |
+
return html.Audio(src=audio_src, controls=True), dcc.send_bytes(mp3_bytes, "podcast.mp3"), 100
|
307 |
+
except Exception as e:
|
308 |
+
logger.error(f"Error rendering podcast: {str(e)}")
|
309 |
+
return html.Div(f"Error: {str(e)}"), None, 0
|
310 |
|
311 |
+
@app.callback(
|
312 |
+
[Output("lang2-select", "style"),
|
313 |
+
Output("voice2-select", "style")],
|
314 |
+
Input("num-hosts", "value")
|
315 |
+
)
|
316 |
+
def update_second_voice_visibility(num_hosts):
|
317 |
+
if num_hosts == 2:
|
318 |
+
return {"display": "block"}, {"display": "block"}
|
319 |
+
else:
|
320 |
+
return {"display": "none"}, {"display": "none"}
|
321 |
|
322 |
+
@app.callback(
|
323 |
+
Output("content-input", "value"),
|
324 |
+
Input("document-upload", "contents"),
|
325 |
+
State("document-upload", "filename"),
|
326 |
+
prevent_initial_call=True
|
327 |
+
)
|
328 |
+
def update_content(contents, filename):
|
329 |
+
if contents is not None:
|
330 |
+
content_type, content_string = contents.split(',')
|
331 |
+
decoded = base64.b64decode(content_string)
|
332 |
try:
|
333 |
+
if 'csv' in filename:
|
334 |
+
# Assume that the user uploaded a CSV file
|
335 |
+
df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
|
336 |
+
return df.to_string()
|
337 |
+
elif 'xls' in filename:
|
338 |
+
# Assume that the user uploaded an excel file
|
339 |
+
df = pd.read_excel(io.BytesIO(decoded))
|
340 |
+
return df.to_string()
|
341 |
+
elif 'txt' in filename or 'md' in filename:
|
342 |
+
# Assume that the user uploaded a text or markdown file
|
343 |
+
return decoded.decode('utf-8')
|
344 |
+
else:
|
345 |
+
return 'Unsupported file type. Please upload a CSV, Excel, text, or markdown file.'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
except Exception as e:
|
347 |
+
logger.error(f"Error processing uploaded file: {str(e)}")
|
348 |
+
return f'There was an error processing this file: {str(e)}'
|
|
|
|
|
349 |
|
350 |
# Run the app
|
351 |
if __name__ == '__main__':
|