Docfile commited on
Commit
0efcaab
·
verified ·
1 Parent(s): 21e127e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -264
app.py CHANGED
@@ -1,283 +1,127 @@
1
- import os
2
- import asyncio
3
- import wave
4
- import tempfile
5
- import logging
6
- import json
7
- import time
8
- from flask import Flask, render_template, request, jsonify, send_file, stream_with_context, Response
9
- from google import genai
10
- import aiohttp
11
- from pydub import AudioSegment
12
-
13
- # Configure logging
14
- logging.basicConfig(level=logging.DEBUG)
15
- logger = logging.getLogger(__name__)
16
 
17
  app = Flask(__name__)
18
- app.secret_key = os.environ.get("SESSION_SECRET", "default-secret-key")
19
-
20
- # Configure Gemini API
21
- api_key = os.environ.get("GEMINI_API_KEY")
22
- if not api_key:
23
- logger.warning("GEMINI_API_KEY not found in environment variables. Using default value for development.")
24
- api_key = "YOUR_API_KEY" # This will be replaced with env var in production
25
-
26
- # Define available voices
27
- AVAILABLE_VOICES = [
28
- "Puck", "Charon", "Kore", "Fenrir",
29
- "Aoede", "Leda", "Orus", "Zephyr"
30
- ]
31
- language_code="fr-FR"
32
 
33
- # Global variable to track generation progress
34
- generation_progress = {
35
- "status": "idle",
36
- "current": 0,
37
- "total": 0,
38
- "message": ""
39
- }
 
 
 
40
 
41
- def update_progress(current, total, message):
42
- """Update the global progress tracker."""
43
- global generation_progress
44
- generation_progress = {
45
- "status": "in_progress" if current < total else "complete",
46
- "current": current,
47
- "total": total,
48
- "message": message
49
- }
50
- def create_async_enumerate(async_iterator):
51
- """Create an async enumerate function since it's not built-in."""
52
- i = 0
53
- async def async_iter():
54
- nonlocal i
55
- async for item in async_iterator:
56
- yield i, item
57
- i += 1
58
- return async_iter()
59
-
60
- async def generate_speech(text, selected_voice):
61
- """Generate speech from text using Gemini AI."""
62
- try:
63
- client = genai.Client(api_key=api_key)
64
- model = "gemini-2.0-flash-live-001"
65
-
66
- # Configure the voice settings
67
- speech_config = genai.types.SpeechConfig(
68
- language_code=language_code,
69
- voice_config=genai.types.VoiceConfig(
70
- prebuilt_voice_config=genai.types.PrebuiltVoiceConfig(
71
- voice_name=selected_voice
72
- )
73
- )
74
- )
75
-
76
- config = genai.types.LiveConnectConfig(
77
- response_modalities=["AUDIO"],
78
- speech_config=speech_config
79
- )
80
-
81
- # Create a temporary file to store the audio
82
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
83
- temp_filename = tmp_file.name
84
-
85
- async with client.aio.live.connect(model=model, config=config) as session:
86
- # Open the WAV file for writing
87
- wf = wave.open(temp_filename, "wb")
88
- wf.setnchannels(1)
89
- wf.setsampwidth(2)
90
- wf.setframerate(24000)
91
-
92
- # Send the text to Gemini
93
- await session.send_client_content(
94
- turns={"role": "user", "parts": [{"text": text}]},
95
- turn_complete=True
96
- )
97
-
98
- # Receive the audio data and write it to the file
99
- async for idx, response in create_async_enumerate(session.receive()):
100
- if response.data is not None:
101
- wf.writeframes(response.data)
102
-
103
- wf.close()
104
-
105
- return temp_filename
106
-
107
- except Exception as e:
108
- logger.error(f"Error generating speech: {str(e)}")
109
- raise e
110
 
111
  @app.route('/')
112
  def index():
113
- """Render the main page."""
114
- return render_template('index.html', voices=AVAILABLE_VOICES)
115
 
116
- @app.route('/generate', methods=['POST'])
117
- async def generate():
118
- """Generate speech from text."""
119
- try:
120
- data = request.json
121
- text = data.get('text', '')
122
- voice = data.get('voice', 'Kore') # Default voice
123
-
124
- if not text:
125
- return jsonify({"error": "Text is required"}), 400
126
-
127
- if voice not in AVAILABLE_VOICES:
128
- return jsonify({"error": "Invalid voice selection"}), 400
129
 
130
- # Generate the speech
131
- audio_file = await generate_speech(text, voice)
132
-
133
- return jsonify({
134
- "status": "success",
135
- "message": "Audio generated successfully",
136
- "audioUrl": f"/audio/{os.path.basename(audio_file)}"
137
- })
138
 
139
- except Exception as e:
140
- logger.error(f"Error in generate endpoint: {str(e)}")
141
- return jsonify({"error": str(e)}), 500
142
-
143
- @app.route('/audio/<filename>')
144
- def get_audio(filename):
145
- """Serve the generated audio file."""
146
- try:
147
- temp_dir = tempfile.gettempdir()
148
- file_path = os.path.join(temp_dir, filename)
149
-
150
- if not os.path.exists(file_path):
151
- return jsonify({"error": "Audio file not found"}), 404
152
-
153
- return send_file(file_path, mimetype="audio/wav", as_attachment=False)
154
 
155
- except Exception as e:
156
- logger.error(f"Error serving audio file: {str(e)}")
157
- return jsonify({"error": str(e)}), 500
158
 
159
- @app.route('/generate-podcast', methods=['POST'])
160
- async def generate_podcast_route():
161
- """Generate a podcast from a scenario."""
162
- try:
163
- scenario = request.json
164
-
165
- # Reset progress tracker
166
- global generation_progress
167
- generation_progress = {
168
- "status": "in_progress",
169
- "current": 0,
170
- "total": len(scenario.get('characters', [])),
171
- "message": "Démarrage de la génération..."
172
- }
173
-
174
- # Start the background task for podcast generation
175
- # We'll return immediately and let the client poll for progress
176
- asyncio.create_task(generate_podcast_background(scenario))
177
-
178
- return jsonify({
179
- "status": "started",
180
- "message": "Génération du podcast commencée. Suivez la progression sur l'interface."
181
- })
182
 
183
- except Exception as e:
184
- logger.error(f"Error in generate-podcast endpoint: {str(e)}")
185
- update_progress(0, 0, f"Erreur: {str(e)}")
186
- return jsonify({"error": str(e)}), 500
187
 
188
- async def generate_podcast_background(scenario):
189
- """Generate a podcast in the background."""
190
  try:
191
- # Generate audio for each character
192
- characters = scenario.get('characters', [])
193
- total_characters = len(characters)
194
- update_progress(0, total_characters, f"Préparation du podcast avec {total_characters} personnages...")
195
-
196
- audio_segments = []
197
- podcast_filename = None
198
-
199
- for idx, character in enumerate(characters):
200
- character_name = character.get('name', 'Unknown')
201
- voice = character.get('voice', 'Kore')
202
- text = character.get('text', '')
203
-
204
- update_progress(idx, total_characters, f"Génération de l'audio pour {character_name} ({idx+1}/{total_characters})...")
205
-
206
- if voice not in AVAILABLE_VOICES:
207
- logger.warning(f"Voice {voice} not available. Using default voice Kore for {character_name}.")
208
- voice = 'Kore'
209
-
210
- # Generate speech for this character
211
- try:
212
- audio_file = await generate_speech(text, voice)
213
- audio_segments.append(audio_file)
214
- except Exception as e:
215
- logger.error(f"Error generating speech for {character_name}: {str(e)}")
216
- update_progress(0, 0, f"Erreur lors de la génération pour {character_name}: {str(e)}")
217
- return
218
-
219
- update_progress(total_characters, total_characters, "Assemblage des segments audio...")
220
-
221
- # Combine all audio segments into one file
222
- combined = AudioSegment.empty()
223
-
224
- for audio_file in audio_segments:
225
- segment = AudioSegment.from_wav(audio_file)
226
- combined += segment
227
- # Add a short silence between segments (500ms)
228
- combined += AudioSegment.silent(duration=500)
229
-
230
- # Export the combined audio
231
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as output_file:
232
- podcast_filename = output_file.name
233
- combined.export(podcast_filename, format="wav")
234
-
235
- update_progress(total_characters + 1, total_characters + 1, f"Podcast généré avec succès! audio:{os.path.basename(podcast_filename)}")
236
-
237
- except Exception as e:
238
- logger.error(f"Error in podcast background task: {str(e)}")
239
- update_progress(0, 0, f"Erreur: {str(e)}")
240
 
241
- @app.route('/podcast-status')
242
- def podcast_status():
243
- """Get the current status of the podcast generation."""
244
- global generation_progress
245
-
246
- # If status is complete and contains an audioUrl in the message, extract it
247
- if generation_progress["status"] == "complete" and "audio:" in generation_progress["message"]:
248
- message_parts = generation_progress["message"].split("audio:")
249
- if len(message_parts) > 1:
250
- audio_filename = message_parts[1].strip()
251
- return jsonify({
252
- "status": "complete",
253
- "message": message_parts[0].strip(),
254
- "audioUrl": f"/audio/{audio_filename}"
255
- })
256
-
257
- # Otherwise just return the current progress
258
- return jsonify(generation_progress)
259
 
260
- @app.route('/generation-progress')
261
- def get_generation_progress():
262
- """Get the current progress of podcast generation."""
263
- return jsonify(generation_progress)
264
 
265
- @app.route('/download/<filename>')
266
- def download_audio(filename):
267
- """Download the generated audio file."""
268
- try:
269
- temp_dir = tempfile.gettempdir()
270
- file_path = os.path.join(temp_dir, filename)
271
-
272
- if not os.path.exists(file_path):
273
- return jsonify({"error": "Audio file not found"}), 404
274
-
275
- # Check if this is a podcast or simple speech
276
- download_name = "gemini_podcast.wav"
277
-
278
- return send_file(file_path, mimetype="audio/wav", as_attachment=True,
279
- download_name=download_name)
280
 
281
- except Exception as e:
282
- logger.error(f"Error downloading audio file: {str(e)}")
283
- return jsonify({"error": str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, session
2
+ from stockfish import Stockfish # Assurez-vous que le chemin est correct si besoin
3
+ import chess # python-chess
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  app = Flask(__name__)
6
+ app.secret_key = 'super_secret_key_for_session' # Important pour les sessions
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ # Pour simplifier, une instance globale. Pour une prod, gérer par session/jeu.
9
+ # Assurez-vous que le binaire stockfish est dans votre PATH ou spécifiez le chemin.
10
+ try:
11
+ stockfish_path = "stockfish" # ou "/usr/games/stockfish" ou chemin vers votre binaire
12
+ stockfish = Stockfish(path=stockfish_path, parameters={"Threads": 2, "Hash": 128})
13
+ except Exception as e:
14
+ print(f"Erreur à l'initialisation de Stockfish: {e}")
15
+ print("Veuillez vérifier que Stockfish est installé et accessible via le PATH, ou spécifiez le chemin correct.")
16
+ # Vous pourriez vouloir quitter l'application ou avoir un mode dégradé.
17
+ stockfish = None
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  @app.route('/')
21
  def index():
22
+ return render_template('index.html')
 
23
 
24
+ @app.route('/new_game', methods=['POST'])
25
+ def new_game():
26
+ if not stockfish:
27
+ return jsonify({"error": "Stockfish non initialisé"}), 500
 
 
 
 
 
 
 
 
 
28
 
29
+ data = request.json
30
+ mode = data.get('mode', 'human') # 'ai' or 'human'
 
 
 
 
 
 
31
 
32
+ initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
33
+ session['fen'] = initial_fen
34
+ session['mode'] = mode
35
+ session['turn'] = 'w' # White's turn
36
+ session['history'] = []
37
+
38
+ stockfish.set_fen_position(initial_fen)
 
 
 
 
 
 
 
 
39
 
40
+ # python-chess board (optionnel mais recommandé)
41
+ session['board_pgn'] = chess.Board().fen() # Stocker le FEN pour reconstruire
 
42
 
43
+ return jsonify({
44
+ "fen": initial_fen,
45
+ "turn": session['turn'],
46
+ "message": "Nouvelle partie commencée."
47
+ })
48
+
49
+ @app.route('/move', methods=['POST'])
50
+ def handle_move():
51
+ if not stockfish:
52
+ return jsonify({"error": "Stockfish non initialisé"}), 500
53
+
54
+ if 'fen' not in session:
55
+ return jsonify({"error": "Aucune partie en cours. Commencez une nouvelle partie."}), 400
56
+
57
+ data = request.json
58
+ move_uci = data.get('move') # e.g., "e2e4"
59
+
60
+ # Reconstruire l'état du board python-chess (si utilisé)
61
+ board = chess.Board(session['fen'])
 
 
 
 
62
 
63
+ # Validation du tour
64
+ current_player_color = 'w' if board.turn == chess.WHITE else 'b'
65
+ if session['turn'] != current_player_color:
66
+ return jsonify({"error": "Pas votre tour.", "fen": session['fen'], "turn": session['turn']}), 400
67
 
 
 
68
  try:
69
+ move_obj = board.parse_uci(move_uci)
70
+ except ValueError:
71
+ return jsonify({"error": "Format de coup invalide.", "fen": session['fen'], "turn": session['turn']}), 400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
+ if not stockfish.is_move_correct(move_uci) or move_obj not in board.legal_moves:
75
+ return jsonify({"error": "Coup illégal.", "fen": session['fen'], "turn": session['turn']}), 400
 
 
76
 
77
+ # Appliquer le coup humain
78
+ stockfish.make_moves_from_current_position([move_uci])
79
+ board.push(move_obj)
80
+ session['fen'] = stockfish.get_fen_position() # ou board.fen()
81
+ session['history'].append(move_uci)
82
+ session['turn'] = 'b' if current_player_color == 'w' else 'w'
 
 
 
 
 
 
 
 
 
83
 
84
+ ai_move_uci = None
85
+ game_status = "En cours"
86
+
87
+ if board.is_checkmate():
88
+ game_status = f"Mat! {'Les Blancs' if board.turn == chess.BLACK else 'Les Noirs'} gagnent."
89
+ elif board.is_stalemate() or board.is_insufficient_material() or board.is_seventyfive_moves() or board.is_fivefold_repetition():
90
+ game_status = "Pat!"
91
+
92
+ # Mode IA
93
+ if session['mode'] == 'ai' and game_status == "En cours" and ( (board.turn == chess.BLACK and current_player_color == 'w') or \
94
+ (board.turn == chess.WHITE and current_player_color == 'b') ) : # Tour de l'IA
95
+ # S'assurer que stockfish a la bonne position si on utilise board.fen()
96
+ stockfish.set_fen_position(board.fen())
97
+ ai_move_uci = stockfish.get_best_move_time(1000) # 1 seconde de réflexion
98
+ if ai_move_uci:
99
+ ai_move_obj = board.parse_uci(ai_move_uci)
100
+ stockfish.make_moves_from_current_position([ai_move_uci]) # Stockfish est déjà à jour
101
+ board.push(ai_move_obj)
102
+ session['fen'] = stockfish.get_fen_position() # ou board.fen()
103
+ session['history'].append(ai_move_uci)
104
+ session['turn'] = 'w' if session['turn'] == 'b' else 'b'
105
+
106
+ if board.is_checkmate():
107
+ game_status = f"Mat! {'Les Blancs' if board.turn == chess.BLACK else 'Les Noirs'} gagnent."
108
+ elif board.is_stalemate() or board.is_insufficient_material() or board.is_seventyfive_moves() or board.is_fivefold_repetition():
109
+ game_status = "Pat!"
110
+ else: # Si l'IA ne retourne pas de coup (ce qui peut arriver si elle est matée/patée)
111
+ if board.is_checkmate():
112
+ game_status = f"Mat! {'Les Blancs' if board.turn == chess.BLACK else 'Les Noirs'} gagnent."
113
+ elif board.is_stalemate():
114
+ game_status = "Pat!"
115
+
116
+
117
+ return jsonify({
118
+ "fen": session['fen'],
119
+ "turn": session['turn'],
120
+ "last_move_human": move_uci,
121
+ "last_move_ai": ai_move_uci,
122
+ "game_status": game_status,
123
+ "history": session['history']
124
+ })
125
+
126
+ if __name__ == '__main__':
127
+ app.run(debug=True)