11from flask import Flask , render_template , request , jsonify , send_from_directory
2- from generate_podcast import generate , PODCAST_SCRIPT , setup_logging , validate_speakers , update_elevenlabs_quota
2+ from generate_podcast import generate , DEFAULT_INSTRUCTION , DEFAULT_SCRIPT , setup_logging , validate_speakers , update_elevenlabs_quota
33from utils import sanitize_text , get_asset_path , get_app_data_dir
44from config import AVAILABLE_VOICES , DEFAULT_APP_SETTINGS , DEMO_AVAILABLE
55from create_demo import create_html_demo_whisperx
1111import shutil
1212from elevenlabs .core import ApiError
1313import re
14+ import uuid
15+ import threading
1416
1517# --- App Initialization ---
1618app = Flask (__name__ )
1719logger = setup_logging ()
1820
21+ # --- In-Memory Task Manager ---
22+ tasks = {}
23+
1924# --- Version & License ---
2025try :
2126 from _version import __version__
@@ -55,7 +60,10 @@ def save_settings(settings):
5560# --- Routes ---
5661@app .route ('/' )
5762def index ():
58- return render_template ('index.html' , default_script = PODCAST_SCRIPT , demo_available = DEMO_AVAILABLE )
63+ return render_template ('index.html' ,
64+ default_instruction = DEFAULT_INSTRUCTION ,
65+ default_script = DEFAULT_SCRIPT ,
66+ demo_available = DEMO_AVAILABLE )
5967
6068@app .route ('/assets/<path:filename>' )
6169def get_asset (filename ):
@@ -130,6 +138,38 @@ def get_gemini_sample(voice_name):
130138 return "Sample directory not found" , 404
131139 return send_from_directory (sample_path , f"{ voice_name } .mp3" )
132140
141+ def run_generation_task (task_id , script_text , app_settings , output_filepath , api_key ):
142+ """The target function for the generation thread."""
143+ stop_event = tasks [task_id ]['stop_event' ]
144+ try :
145+ generated_file = generate (
146+ script_text = script_text ,
147+ app_settings = app_settings ,
148+ output_filepath = output_filepath ,
149+ api_key = api_key ,
150+ status_callback = logger .info ,
151+ stop_event = stop_event
152+ )
153+ if generated_file :
154+ tasks [task_id ]['status' ] = 'completed'
155+ tasks [task_id ]['result' ] = {'download_url' : f'/temp/{ os .path .basename (generated_file )} ' , 'filename' : os .path .basename (generated_file )}
156+ except Exception as e :
157+ # If the exception is due to the stop event, set a specific status
158+ if "stopped by user" in str (e ):
159+ tasks [task_id ]['status' ] = 'cancelled'
160+ tasks [task_id ]['error' ] = 'Generation cancelled by user.'
161+ # Clean up the partially created file
162+ if os .path .exists (output_filepath ):
163+ try :
164+ os .remove (output_filepath )
165+ logger .info (f"Removed partial file for stopped task: { output_filepath } " )
166+ except OSError as err :
167+ logger .error (f"Error removing partial file for stopped task: { err } " )
168+ else :
169+ logger .error (f"Error during generation for task { task_id } : { e } " , exc_info = True )
170+ tasks [task_id ]['status' ] = 'failed'
171+ tasks [task_id ]['error' ] = str (e )
172+
133173@app .route ('/generate' , methods = ['POST' ])
134174def handle_generate ():
135175 script_text = request .form .get ('script' , '' )
@@ -146,46 +186,53 @@ def handle_generate():
146186 except ValueError as e :
147187 return jsonify ({'error' : str (e )}), 400
148188
149- first_words = re .sub (r'<[^>]+>' , '' , sanitized_script ).strip ().split ()[:2 ]
150- base_name = "_" .join (first_words ).lower ()
151- safe_base_name = re .sub (r'[^a-z0-9_]+' , '' , base_name )
152- if not safe_base_name :
153- safe_base_name = "podcast"
154- random_suffix = os .urandom (4 ).hex ()
155- output_filename = f"{ safe_base_name } _{ random_suffix } .mp3"
156-
157- output_filepath = os .path .join (app .config ['TEMP_DIR' ], output_filename )
158-
159189 provider = app_settings .get ("tts_provider" , "elevenlabs" )
160190 api_key_env_var = "ELEVENLABS_API_KEY" if provider == "elevenlabs" else "GEMINI_API_KEY"
161191 api_key = os .environ .get (api_key_env_var )
162-
163192 if not api_key :
164193 return jsonify ({'error' : f'API key ({ api_key_env_var } ) not found in environment variables.' }), 500
165194
166195 from utils import sanitize_app_settings_for_backend
167196 app_settings_clean = sanitize_app_settings_for_backend (app_settings )
168197
169- try :
170- generated_file = generate (
171- script_text = sanitized_script ,
172- app_settings = app_settings_clean ,
173- output_filepath = output_filepath ,
174- api_key = api_key ,
175- status_callback = logger .info
176- )
177- if generated_file :
178- return jsonify ({'download_url' : f'/temp/{ output_filename } ' , 'filename' : output_filename })
179- else :
180- return jsonify ({'error' : 'Generation failed for an unknown reason. Check server logs.' }), 500
181- except ApiError as e :
182- error_detail = e .body .get ('detail' , {})
183- message = error_detail .get ('message' , 'An unknown ElevenLabs API error occurred.' )
184- logger .error (f"ElevenLabs API Error: { message } " )
185- return jsonify ({'error' : f"ElevenLabs Error: { message } " }), 500
186- except Exception as e :
187- logger .error (f"Error during generation: { e } " , exc_info = True )
188- return jsonify ({'error' : f'An unexpected error occurred: { str (e )} ' }), 500
198+ task_id = str (uuid .uuid4 ())
199+ output_filename = f"{ task_id } .mp3"
200+ output_filepath = os .path .join (app .config ['TEMP_DIR' ], output_filename )
201+
202+ stop_event = threading .Event ()
203+ thread = threading .Thread (target = run_generation_task , args = (task_id , sanitized_script , app_settings_clean , output_filepath , api_key ))
204+
205+ tasks [task_id ] = {'thread' : thread , 'stop_event' : stop_event , 'status' : 'running' }
206+ thread .start ()
207+
208+ return jsonify ({'task_id' : task_id })
209+
210+ @app .route ('/api/generation_status/<task_id>' , methods = ['GET' ])
211+ def get_generation_status (task_id ):
212+ task = tasks .get (task_id )
213+ if not task :
214+ return jsonify ({'error' : 'Task not found' }), 404
215+
216+ response = {'status' : task ['status' ]}
217+ if task ['status' ] == 'completed' :
218+ response ['result' ] = task ['result' ]
219+ elif task ['status' ] in ['failed' , 'cancelled' ]:
220+ response ['error' ] = task .get ('error' , 'An unknown error occurred.' )
221+
222+ return jsonify (response )
223+
224+ @app .route ('/api/stop_generation/<task_id>' , methods = ['POST' ])
225+ def stop_generation (task_id ):
226+ task = tasks .get (task_id )
227+ if not task :
228+ return jsonify ({'error' : 'Task not found' }), 404
229+
230+ if task ['status' ] == 'running' :
231+ task ['stop_event' ].set ()
232+ task ['status' ] = 'stopping'
233+ return jsonify ({'status' : 'Stop signal sent.' })
234+
235+ return jsonify ({'status' : 'Task was not running.' })
189236
190237@app .route ('/api/generate_demo' , methods = ['POST' ])
191238def handle_generate_demo ():
0 commit comments