rairo commited on
Commit
8192a2e
·
verified ·
1 Parent(s): ade4311

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +93 -30
main.py CHANGED
@@ -25,6 +25,8 @@ from PyPDF2 import PdfReader
25
  import wikipedia
26
  from youtube_transcript_api import YouTubeTranscriptApi
27
  import arxiv # For ArXiv
 
 
28
 
29
  # --- Environment Variables ---
30
  # Load environment variables if using a .env file (optional, good practice)
@@ -883,7 +885,9 @@ def submit_quiz_attempt(quiz_id):
883
  return jsonify({'error': f'An unexpected error occurred: {e}'}), 500
884
 
885
 
886
- @app.route('/api/tutor/notes/<uuid:notes_id>/speak', methods=['GET'])
 
 
887
  def speak_notes(notes_id):
888
  user, error = verify_token(request.headers.get('Authorization'))
889
  if error:
@@ -893,63 +897,122 @@ def speak_notes(notes_id):
893
  return jsonify({'error': 'Backend service unavailable'}), 503
894
 
895
  try:
 
896
  profile_res = supabase.table('profiles').select('credits', 'suspended').eq('id', user.id).single().execute()
897
-
898
  if profile_res.data['suspended']:
899
  return jsonify({'error': 'Account suspended'}), 403
900
  if profile_res.data['credits'] < 5:
901
  return jsonify({'error': 'Insufficient credits (Need 5)'}), 402
902
 
903
  # Fetch notes content
904
- notes_res = supabase.table('notes').select('content').eq('id', notes_id).single().execute()
905
- if not notes_res.data or 'content' not in notes_res.data:
906
- return jsonify({'error': 'Notes not found or missing content'}), 404
 
 
 
 
 
 
 
 
907
 
908
  notes_content = notes_res.data['content']
909
  if not notes_content:
910
- return jsonify({'error': 'Notes content is empty, cannot generate audio.'}), 400
911
 
912
- # --- Generate TTS Audio ---
913
  start_time = time.time()
914
  logging.info(f"Generating TTS for user {user.id}, notes: {notes_id}")
915
- audio_bytes = generate_tts_audio(notes_content) # Add voice selection if needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916
  logging.info(f"TTS generation took {time.time() - start_time:.2f}s")
917
 
918
- # --- Save Audio to Supabase Storage ---
919
- bucket_name = 'notes-audio' # Ensure this bucket exists in Supabase Storage
920
  destination_path = f'users/{user.id}/{notes_id}.mp3'
921
- content_type = 'audio/mpeg'
922
-
923
- # Use a temporary file to upload
924
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_audio_file:
925
- tmp_audio_file.write(audio_bytes)
926
- tmp_file_path = tmp_audio_file.name
927
 
928
  try:
929
- audio_url = upload_to_supabase_storage(bucket_name, tmp_file_path, destination_path, content_type)
930
- logging.info(f"Uploaded TTS audio to: {audio_url}")
 
 
 
 
 
931
 
932
- # --- Update notes table with the URL ---
933
  supabase.table('notes').update({'tts_audio_url': audio_url}).eq('id', notes_id).execute()
934
-
935
- # Deduct 5 credits after successful generation
936
  new_credits = profile_res.data['credits'] - 5
937
  supabase.table('profiles').update({'credits': new_credits}).eq('id', user.id).execute()
938
 
939
- return jsonify({'success': True, 'audio_url': audio_url})
 
 
 
 
940
 
941
  finally:
942
- os.remove(tmp_file_path) # Clean up temporary file
943
 
944
- except ConnectionError as e:
945
- logging.error(f"Connection error during TTS generation: {e}")
946
- return jsonify({'error': f'A backend service is unavailable: {e}'}), 503
947
- except RuntimeError as e: # AI generation errors
948
- logging.error(f"RuntimeError during TTS generation for user {user.id}: {e}")
949
  return jsonify({'error': str(e)}), 500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
  except Exception as e:
951
- logging.error(f"Unexpected error generating TTS for user {user.id}, notes {notes_id}: {traceback.format_exc()}")
952
- return jsonify({'error': f'An unexpected error occurred: {e}'}), 500
953
 
954
  # ---------- View Notes and Quizzes Endpoints ----------
955
 
 
25
  import wikipedia
26
  from youtube_transcript_api import YouTubeTranscriptApi
27
  import arxiv # For ArXiv
28
+ from elevenlabs import play, stream, save
29
+ import math
30
 
31
  # --- Environment Variables ---
32
  # Load environment variables if using a .env file (optional, good practice)
 
885
  return jsonify({'error': f'An unexpected error occurred: {e}'}), 500
886
 
887
 
888
+
889
+ # Modified speak_notes endpoint with ElevenLabs Studio API and chunking
890
+ @app.route('/api/tutor/notes/<uuid:notes_id>/speak', methods=['POST']) # Changed to POST since it creates resources
891
  def speak_notes(notes_id):
892
  user, error = verify_token(request.headers.get('Authorization'))
893
  if error:
 
897
  return jsonify({'error': 'Backend service unavailable'}), 503
898
 
899
  try:
900
+ # Check user status and credits
901
  profile_res = supabase.table('profiles').select('credits', 'suspended').eq('id', user.id).single().execute()
 
902
  if profile_res.data['suspended']:
903
  return jsonify({'error': 'Account suspended'}), 403
904
  if profile_res.data['credits'] < 5:
905
  return jsonify({'error': 'Insufficient credits (Need 5)'}), 402
906
 
907
  # Fetch notes content
908
+ notes_res = supabase.table('notes').select('content, tts_audio_url').eq('id', notes_id).single().execute()
909
+ if not notes_res.data:
910
+ return jsonify({'error': 'Notes not found'}), 404
911
+
912
+ # Return existing audio if available
913
+ if notes_res.data.get('tts_audio_url'):
914
+ return jsonify({
915
+ 'success': True,
916
+ 'audio_url': notes_res.data['tts_audio_url'],
917
+ 'message': 'Using existing audio file'
918
+ })
919
 
920
  notes_content = notes_res.data['content']
921
  if not notes_content:
922
+ return jsonify({'error': 'Notes content is empty'}), 400
923
 
924
+ # --- Generate TTS Audio with chunking ---
925
  start_time = time.time()
926
  logging.info(f"Generating TTS for user {user.id}, notes: {notes_id}")
927
+
928
+ # Chunk text if too long (ElevenLabs limit is ~2500 chars for best quality)
929
+ CHUNK_SIZE = 2000 # Conservative chunk size
930
+ chunks = [notes_content[i:i+CHUNK_SIZE] for i in range(0, len(notes_content), CHUNK_SIZE)]
931
+
932
+ audio_bytes = b""
933
+ for chunk in chunks:
934
+ try:
935
+ # Using ElevenLabs Studio API with streaming
936
+ chunk_audio = elevenlabs_client.generate(
937
+ text=chunk,
938
+ voice="Rachel", # Default voice
939
+ model="eleven_multilingual_v2",
940
+ stream=True
941
+ )
942
+ audio_bytes += b"".join(chunk_audio)
943
+ except Exception as e:
944
+ logging.error(f"Error generating chunk: {str(e)}")
945
+ raise RuntimeError(f"Failed to generate audio chunk: {str(e)}")
946
+
947
+ if not audio_bytes:
948
+ raise RuntimeError("Generated empty audio file")
949
+
950
  logging.info(f"TTS generation took {time.time() - start_time:.2f}s")
951
 
952
+ # --- Save to Supabase Storage ---
953
+ bucket_name = 'notes-audio'
954
  destination_path = f'users/{user.id}/{notes_id}.mp3'
955
+
956
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
957
+ tmp_file.write(audio_bytes)
958
+ tmp_path = tmp_file.name
 
 
959
 
960
  try:
961
+ # Upload with proper content type
962
+ audio_url = upload_to_supabase_storage(
963
+ bucket_name,
964
+ tmp_path,
965
+ destination_path,
966
+ "audio/mpeg"
967
+ )
968
 
969
+ # Update database
970
  supabase.table('notes').update({'tts_audio_url': audio_url}).eq('id', notes_id).execute()
971
+
972
+ # Deduct credits
973
  new_credits = profile_res.data['credits'] - 5
974
  supabase.table('profiles').update({'credits': new_credits}).eq('id', user.id).execute()
975
 
976
+ return jsonify({
977
+ 'success': True,
978
+ 'audio_url': audio_url,
979
+ 'remaining_credits': new_credits
980
+ })
981
 
982
  finally:
983
+ os.remove(tmp_path)
984
 
985
+ except Exception as e:
986
+ logging.error(f"Error in speak_notes: {traceback.format_exc()}")
 
 
 
987
  return jsonify({'error': str(e)}), 500
988
+
989
+ # New endpoint to view existing audio URL
990
+ @app.route('/api/tutor/notes/<uuid:notes_id>/audio', methods=['GET'])
991
+ def get_note_audio(notes_id):
992
+ user, error = verify_token(request.headers.get('Authorization'))
993
+ if error:
994
+ return jsonify({'error': error['error']}), error['status']
995
+
996
+ try:
997
+ notes_res = supabase.table('notes').select('tts_audio_url, user_id').eq('id', notes_id).single().execute()
998
+
999
+ if not notes_res.data:
1000
+ return jsonify({'error': 'Notes not found'}), 404
1001
+
1002
+ if notes_res.data['user_id'] != user.id:
1003
+ return jsonify({'error': 'Unauthorized access'}), 403
1004
+
1005
+ if not notes_res.data['tts_audio_url']:
1006
+ return jsonify({'error': 'No audio available for these notes'}), 404
1007
+
1008
+ return jsonify({
1009
+ 'success': True,
1010
+ 'audio_url': notes_res.data['tts_audio_url']
1011
+ })
1012
+
1013
  except Exception as e:
1014
+ logging.error(f"Error getting audio URL: {str(e)}")
1015
+ return jsonify({'error': str(e)}), 500
1016
 
1017
  # ---------- View Notes and Quizzes Endpoints ----------
1018