MrSimple01 commited on
Commit
4cc0ea8
·
verified ·
1 Parent(s): 5f2eb21

Upload 8 files

Browse files
src/__init__.py ADDED
File without changes
src/audioProcessing.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import requests
4
+ import json
5
+ import uuid
6
+ from typing import Tuple, Optional, Dict, Any
7
+
8
+ def transcribe_audio(audio_file, api_key, model_id="scribe_v1"):
9
+ if not api_key:
10
+ return {"error": "Please provide an API key"}
11
+ url = "https://api.elevenlabs.io/v1/speech-to-text"
12
+ headers = {
13
+ "xi-api-key": api_key
14
+ }
15
+ files = {
16
+ "file": open(audio_file, "rb"),
17
+ "model_id": (None, model_id)
18
+ }
19
+ try:
20
+ response = requests.post(url, headers=headers, files=files)
21
+ response.raise_for_status()
22
+ result = response.json()
23
+ return result
24
+ except requests.exceptions.RequestException as e:
25
+ return {"error": f"API request failed: {str(e)}"}
26
+ except json.JSONDecodeError:
27
+ return {"error": "Failed to parse API response"}
28
+ finally:
29
+ files["file"].close()
30
+
31
+ def save_transcription(transcription):
32
+ if "error" in transcription:
33
+ return None, transcription["error"]
34
+ transcript_filename = f"transcription_{uuid.uuid4().hex[:8]}.txt"
35
+ try:
36
+ with open(transcript_filename, "w", encoding="utf-8") as f:
37
+ f.write(transcription.get('text', 'No text found'))
38
+ return transcript_filename, "Transcription saved as text file"
39
+ except Exception as e:
40
+ return None, f"Error saving transcription: {str(e)}"
41
+
42
+ def process_audio_file(audio_file, elevenlabs_api_key, model_id="scribe_v1") -> Tuple[str, str, str]:
43
+ if not elevenlabs_api_key:
44
+ return None, "ElevenLabs API key is required for transcription", None
45
+
46
+ transcription_result = transcribe_audio(audio_file, elevenlabs_api_key, model_id)
47
+
48
+ if "error" in transcription_result:
49
+ return None, transcription_result["error"], None
50
+
51
+ transcript_text = transcription_result.get('text', '')
52
+ transcript_path = tempfile.mktemp(suffix='.txt')
53
+
54
+ with open(transcript_path, 'w', encoding='utf-8') as transcript_file:
55
+ transcript_file.write(transcript_text)
56
+
57
+ return transcript_path, f"Transcription completed successfully. Length: {len(transcript_text)} characters.", transcript_text
src/documentProcessing.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import docx
2
+ import PyPDF2
3
+
4
+ def extract_text_from_pdf(pdf_path):
5
+ text = ""
6
+ try:
7
+ with open(pdf_path, 'rb') as file:
8
+ reader = PyPDF2.PdfReader(file)
9
+ for page_num in range(len(reader.pages)):
10
+ text += reader.pages[page_num].extract_text() + "\n"
11
+ return text
12
+ except Exception as e:
13
+ raise Exception(f"Error extracting text from PDF: {str(e)}")
14
+
15
+ def extract_text_from_docx(docx_path):
16
+ try:
17
+ doc = docx.Document(docx_path)
18
+ text = "\n".join([paragraph.text for paragraph in doc.paragraphs])
19
+ return text
20
+ except Exception as e:
21
+ raise Exception(f"Error extracting text from DOCX: {str(e)}")
22
+
23
+ def extract_text_from_txt(txt_path):
24
+ try:
25
+ with open(txt_path, 'r', encoding='utf-8') as file:
26
+ text = file.read()
27
+ return text
28
+ except Exception as e:
29
+ raise Exception(f"Error extracting text from TXT: {str(e)}")
30
+
31
+ def process_document(document_path, gemini_api_key, language, content_type):
32
+ try:
33
+ temp_file = tempfile.mktemp(suffix=os.path.splitext(document_path.name)[-1])
34
+ with open(temp_file, 'wb') as f:
35
+ f.write(document_path.read())
36
+
37
+ file_extension = os.path.splitext(document_path.name)[-1].lower()
38
+ if file_extension == '.pdf':
39
+ text = extract_text_from_pdf(temp_file)
40
+ elif file_extension == '.docx':
41
+ text = extract_text_from_docx(temp_file)
42
+ elif file_extension == '.txt':
43
+ text = extract_text_from_txt(temp_file)
44
+ else:
45
+ raise Exception(f"Unsupported file type: {file_extension}")
46
+
47
+ text_file_path = tempfile.mktemp(suffix='.txt')
48
+ with open(text_file_path, 'w', encoding='utf-8') as f:
49
+ f.write(text)
50
+
51
+ formatted_output, json_path, txt_path = analyze_document(
52
+ text,
53
+ gemini_api_key,
54
+ language,
55
+ content_type
56
+ )
57
+
58
+ return f"Document processed successfully", text_file_path, formatted_output, txt_path, json_path
59
+ except Exception as e:
60
+ error_message = f"Error processing document: {str(e)}"
61
+ return error_message, None, error_message, None, None
src/mainFunctions.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import subprocess
4
+ from typing import Optional, Tuple, List
5
+ import pytube
6
+ from src.video_processing import extract_audio_from_video
7
+ from src.quiz_processing import analyze_document
8
+ import docx
9
+ import PyPDF2
10
+ import re
11
+
12
+
13
+ def parse_quiz_content(quiz_text):
14
+ questions = []
15
+ lines = quiz_text.split('\n')
16
+ current_question = None
17
+
18
+ for line in lines:
19
+ line = line.strip()
20
+ if not line:
21
+ continue
22
+
23
+ q_match = re.match(r'^(?:\d+\.|\[?Q\d+\]?\.?)\s+(.*)', line, re.IGNORECASE)
24
+ if q_match:
25
+ if current_question:
26
+ questions.append(current_question)
27
+ current_question = {"question": q_match.group(1), "answer": ""}
28
+ elif current_question and line.lower().startswith(("answer:", "a:", "ans:")):
29
+ answer_text = re.sub(r'^(?:answer:|a:|ans:)\s*', '', line, flags=re.IGNORECASE)
30
+ current_question["answer"] = answer_text.strip()
31
+
32
+ if current_question:
33
+ questions.append(current_question)
34
+
35
+ return {"questions": questions}
36
+
37
+
38
+ def transcribe_audio(audio_path, elevenlabs_api_key, model_id):
39
+ import requests
40
+ import json
41
+
42
+ try:
43
+ with open(audio_path, 'rb') as audio_file:
44
+ response = requests.post(
45
+ 'https://api.elevenlabs.io/v1/transcribe',
46
+ headers={'xi-api-key': elevenlabs_api_key},
47
+ files={'audio': audio_file},
48
+ data={'model_id': model_id}
49
+ )
50
+
51
+ if response.status_code == 200:
52
+ transcription = response.json().get('transcription', '')
53
+
54
+ transcript_path = tempfile.mktemp(suffix='.txt')
55
+ with open(transcript_path, 'w', encoding='utf-8') as f:
56
+ f.write(transcription)
57
+
58
+ return transcription, transcript_path, "Transcription completed successfully"
59
+ else:
60
+ return None, None, f"Transcription failed: {response.text}"
61
+ except Exception as e:
62
+ return None, None, f"Transcription error: {str(e)}"
63
+
64
+ def process_video_file(video_path, audio_format, elevenlabs_api_key, model_id, gemini_api_key, language, content_type):
65
+ try:
66
+ audio_path = extract_audio_from_video(video_path, audio_format)
67
+
68
+ transcription, transcript_path, transcription_status = transcribe_audio(
69
+ audio_path,
70
+ elevenlabs_api_key,
71
+ model_id
72
+ )
73
+
74
+ if not transcription:
75
+ return audio_path, "Audio extracted, but transcription failed", None, transcription_status, None, None, None
76
+
77
+ formatted_output, json_path, txt_path = analyze_document(
78
+ transcription,
79
+ gemini_api_key,
80
+ language,
81
+ content_type
82
+ )
83
+
84
+ return audio_path, "Processing completed successfully", transcript_path, transcription_status, formatted_output, txt_path, json_path
85
+ except Exception as e:
86
+ error_message = f"Error processing video: {str(e)}"
87
+ return None, error_message, None, error_message, error_message, None, None
88
+
89
+ def process_youtube_video(youtube_url, audio_format, elevenlabs_api_key, model_id, gemini_api_key, language, content_type):
90
+ try:
91
+ yt = pytube.YouTube(youtube_url)
92
+ stream = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first()
93
+
94
+ if not stream:
95
+ raise Exception("No suitable video stream found")
96
+
97
+ # Download to temporary file
98
+ video_path = tempfile.mktemp(suffix='.mp4')
99
+ stream.download(filename=video_path)
100
+ audio_path = extract_audio_from_video(video_path, audio_format)
101
+
102
+ transcription, transcript_path, transcription_status = transcribe_audio(
103
+ audio_path,
104
+ elevenlabs_api_key,
105
+ model_id
106
+ )
107
+
108
+ if not transcription:
109
+ return audio_path, "Audio extracted, but transcription failed", None, transcription_status, None, None, None
110
+
111
+ formatted_output, json_path, txt_path = analyze_document(
112
+ transcription,
113
+ gemini_api_key,
114
+ language,
115
+ content_type
116
+ )
117
+
118
+ return audio_path, "Processing completed successfully", transcript_path, transcription_status, formatted_output, txt_path, json_path
119
+ except Exception as e:
120
+ error_message = f"Error processing YouTube video: {str(e)}"
121
+ return None, error_message, None, error_message, error_message, None, None
122
+
123
+ def process_audio_document(audio_path, elevenlabs_api_key, model_id, gemini_api_key, language, content_type):
124
+ """Process an audio file - transcribe and generate summary or quiz."""
125
+ try:
126
+ transcription, transcript_path, transcription_status = transcribe_audio(
127
+ audio_path,
128
+ elevenlabs_api_key,
129
+ model_id
130
+ )
131
+
132
+ if not transcription:
133
+ return "Transcription failed", None, None, None, None
134
+
135
+ formatted_output, json_path, txt_path = analyze_document(
136
+ transcription,
137
+ gemini_api_key,
138
+ language,
139
+ content_type
140
+ )
141
+
142
+ return "Processing completed successfully", transcript_path, formatted_output, txt_path, json_path
143
+ except Exception as e:
144
+ error_message = f"Error processing audio: {str(e)}"
145
+ return error_message, None, error_message, None, None
146
+
src/prompts.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SYSTEM_PROMPT = """You are an expert educational content analyzer. Your task is to analyze text content,
2
+ identify distinct segments, and create high-quality educational quiz questions for each segment."""
3
+
4
+ SUMMARY_PROMPT_TEMPLATE = """
5
+ You are an expert content analyst specialized in creating professional, actionable summaries of educational content.
6
+
7
+ Please analyze the following text to create a comprehensive yet concise summary that will be valuable to readers. Your summary should:
8
+
9
+ 1. Begin with a brief overview of the main topic and its significance (2-3 sentences)
10
+ 2. Identify and clearly highlight the most important concepts, names, places, and technical terms using markdown formatting (bold for key terms, italics for definitions)
11
+ 3. Present key information in organized bullet points grouped by theme or concept
12
+ 4. Include specific supporting details, examples, statistics, or quotes that enhance understanding
13
+ 5. End with the practical implications or conclusions that can be drawn from the content
14
+
15
+ The text to analyze is:
16
+ {text}
17
+
18
+ Respond with a properly formatted JSON object according to this schema:
19
+ {{
20
+ "summary": {{
21
+ "title": "A clear, descriptive title for the content",
22
+ "overview": "A concise overview paragraph introducing the main topic",
23
+ "key_points": [
24
+ {{
25
+ "theme": "First major theme or concept",
26
+ "points": [
27
+ "First key bullet point with important terms highlighted",
28
+ "Second key bullet point with contextual information",
29
+ "Third key bullet point with specific details or examples"
30
+ ]
31
+ }},
32
+ {{
33
+ "theme": "Second major theme or concept",
34
+ "points": [
35
+ "First key bullet point for this theme",
36
+ "Second key bullet point for this theme",
37
+ "Additional bullet points as needed"
38
+ ]
39
+ }}
40
+ ],
41
+ "key_entities": [
42
+ {{
43
+ "name": "Name of person, place, or organization",
44
+ "description": "Brief description of their relevance"
45
+ }},
46
+ {{
47
+ "name": "Another key entity",
48
+ "description": "Brief description of their relevance"
49
+ }}
50
+ ],
51
+ "conclusion": "A concise statement summarizing the main implications or takeaways"
52
+ }}
53
+ }}
54
+
55
+ Focus on creating a summary that is immediately useful, visually scannable, and highlights the most important information. Use markdown formatting strategically to make the summary more readable and engaging.
56
+ """
57
+
58
+
59
+ QUIZ_PROMPT_TEMPLATE = """
60
+ You are an expert quiz creator specialized in creating educational assessments.
61
+ Please analyze the following text and create 10 multiple-choice quiz questions that test understanding of the key concepts and information presented in the text. For each question:
62
+ 1. Write a clear, concise question
63
+ 2. Create 4 answer options (A, B, C, D)
64
+ The text to analyze is:
65
+ {text}
66
+ Respond with a properly formatted JSON object according to this schema:
67
+
68
+ {{
69
+ "quiz_questions": [
70
+ {{
71
+ "question": "The full text of the question?",
72
+ "options": [
73
+ {{
74
+ "text": "First option text",
75
+ "correct": false
76
+ }},
77
+ {{
78
+ "text": "Second option text",
79
+ "correct": true
80
+ }},
81
+ {{
82
+ "text": "Third option text",
83
+ "correct": false
84
+ }},
85
+ {{
86
+ "text": "Fourth option text",
87
+ "correct": false
88
+ }}
89
+ ]
90
+ }},
91
+ ... additional questions ...
92
+ ]
93
+ }}
94
+
95
+ Create questions that test different levels of understanding, from recall of facts to application of concepts. Ensure the questions cover the most important information from the text.
96
+ """
src/quiz_processing.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import time
5
+ import tempfile
6
+ from typing import Dict, Any, List, Optional
7
+ from transformers import AutoTokenizer
8
+ from sentence_transformers import SentenceTransformer
9
+ from huggingface_hub import login
10
+
11
+ GEMINI_MODEL = "gemini-2.0-flash"
12
+ DEFAULT_TEMPERATURE = 0.7
13
+
14
+ TOKENIZER_MODEL = "answerdotai/ModernBERT-base"
15
+ SENTENCE_TRANSFORMER_MODEL = "all-MiniLM-L6-v2"
16
+
17
+ hf_token = os.environ.get('HF_TOKEN', None)
18
+ login(token=hf_token)
19
+
20
+ tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_MODEL)
21
+ sentence_model = SentenceTransformer(SENTENCE_TRANSFORMER_MODEL)
22
+
23
+ def clean_text(text):
24
+ text = re.sub(r'\[speaker_\d+\]', '', text)
25
+ text = re.sub(r'\s+', ' ', text).strip()
26
+ return text
27
+
28
+ def split_text_by_tokens(text, max_tokens=12000):
29
+ text = clean_text(text)
30
+ tokens = tokenizer.encode(text)
31
+
32
+ if len(tokens) <= max_tokens:
33
+ return [text]
34
+
35
+ split_point = len(tokens) // 2
36
+
37
+ sentences = re.split(r'(?<=[.!?])\s+', text)
38
+
39
+ first_half = []
40
+ second_half = []
41
+
42
+ current_tokens = 0
43
+ for sentence in sentences:
44
+ sentence_tokens = len(tokenizer.encode(sentence))
45
+
46
+ if current_tokens + sentence_tokens <= split_point:
47
+ first_half.append(sentence)
48
+ current_tokens += sentence_tokens
49
+ else:
50
+ second_half.append(sentence)
51
+
52
+ return [" ".join(first_half), " ".join(second_half)]
53
+
54
+ def generate_with_gemini(text, api_key, language, content_type="summary"):
55
+ from langchain_google_genai import ChatGoogleGenerativeAI
56
+ os.environ["GOOGLE_API_KEY"] = api_key
57
+ llm = ChatGoogleGenerativeAI(
58
+ model=GEMINI_MODEL,
59
+ temperature=DEFAULT_TEMPERATURE,
60
+ max_retries=3
61
+ )
62
+
63
+ if content_type == "summary":
64
+ base_prompt = SUMMARY_PROMPT_TEMPLATE.format(text=text)
65
+ else:
66
+ base_prompt = QUIZ_PROMPT_TEMPLATE.format(text=text)
67
+
68
+ language_instruction = f"\nIMPORTANT: Generate ALL content in {language} language."
69
+ prompt = base_prompt + language_instruction
70
+
71
+ try:
72
+ messages = [
73
+ {"role": "system", "content": "You are a helpful AI assistant that creates high-quality text summaries and quizzes."},
74
+ {"role": "user", "content": prompt}
75
+ ]
76
+
77
+ response = llm.invoke(messages)
78
+
79
+ try:
80
+ content = response.content
81
+ json_match = re.search(r'```json\s*([\s\S]*?)\s*```', content)
82
+
83
+ if json_match:
84
+ json_str = json_match.group(1)
85
+ else:
86
+ json_match = re.search(r'(\{[\s\S]*\})', content)
87
+ if json_match:
88
+ json_str = json_match.group(1)
89
+ else:
90
+ json_str = content
91
+
92
+ # Parse the JSON
93
+ function_call = json.loads(json_str)
94
+ return function_call
95
+ except json.JSONDecodeError:
96
+ raise Exception("Could not parse JSON from LLM response")
97
+ except Exception as e:
98
+ raise Exception(f"Error calling API: {str(e)}")
99
+
100
+ def format_summary_for_display(results, language="English"):
101
+ output = []
102
+
103
+ if language == "Uzbek":
104
+ segment_header = "QISM"
105
+ key_concepts_header = "ASOSIY TUSHUNCHALAR"
106
+ summary_header = "QISQACHA MAZMUN"
107
+ elif language == "Russian":
108
+ segment_header = "СЕГМЕНТ"
109
+ key_concepts_header = "КЛЮЧЕВЫЕ ПОНЯТИЯ"
110
+ summary_header = "КРАТКОЕ СОДЕРЖАНИЕ"
111
+ else:
112
+ segment_header = "SEGMENT"
113
+ key_concepts_header = "KEY CONCEPTS"
114
+ summary_header = "SUMMARY"
115
+
116
+ segments = results.get("segments", [])
117
+ for i, segment in enumerate(segments):
118
+ topic = segment["topic_name"]
119
+ segment_num = i + 1
120
+ output.append(f"\n\n{'='*40}")
121
+ output.append(f"{segment_header} {segment_num}: {topic}")
122
+ output.append(f"{'='*40}\n")
123
+ output.append(f"{key_concepts_header}:")
124
+ for concept in segment["key_concepts"]:
125
+ output.append(f"• {concept}")
126
+ output.append(f"\n{summary_header}:")
127
+ output.append(segment["summary"])
128
+
129
+ return "\n".join(output)
130
+
131
+ def format_quiz_for_display(results, language="English"):
132
+ output = []
133
+
134
+ if language == "Uzbek":
135
+ quiz_questions_header = "TEST SAVOLLARI"
136
+ elif language == "Russian":
137
+ quiz_questions_header = "ТЕСТОВЫЕ ВОПРОСЫ"
138
+ else:
139
+ quiz_questions_header = "QUIZ QUESTIONS"
140
+
141
+ output.append(f"{'='*40}")
142
+ output.append(f"{quiz_questions_header}")
143
+ output.append(f"{'='*40}\n")
144
+
145
+ quiz_questions = results.get("quiz_questions", [])
146
+ for i, q in enumerate(quiz_questions):
147
+ output.append(f"\n{i+1}. {q['question']}")
148
+ for j, option in enumerate(q['options']):
149
+ letter = chr(97 + j).upper()
150
+ correct_marker = " ✓" if option["correct"] else ""
151
+ output.append(f" {letter}. {option['text']}{correct_marker}")
152
+
153
+ return "\n".join(output)
154
+
155
+ def analyze_document(text, gemini_api_key, language, content_type="summary"):
156
+ try:
157
+ start_time = time.time()
158
+ text_parts = split_text_by_tokens(text)
159
+
160
+ input_tokens = 0
161
+ output_tokens = 0
162
+
163
+ if content_type == "summary":
164
+ all_results = {"segments": []}
165
+ segment_counter = 1
166
+
167
+ for part in text_parts:
168
+ actual_prompt = SUMMARY_PROMPT_TEMPLATE.format(text=part)
169
+ prompt_tokens = len(tokenizer.encode(actual_prompt))
170
+ input_tokens += prompt_tokens
171
+
172
+ analysis = generate_with_gemini(part, gemini_api_key, language, "summary")
173
+
174
+ if "segments" in analysis:
175
+ for segment in analysis["segments"]:
176
+ segment["segment_number"] = segment_counter
177
+ all_results["segments"].append(segment)
178
+ segment_counter += 1
179
+
180
+ formatted_output = format_summary_for_display(all_results, language)
181
+
182
+ else: # Quiz generation
183
+ all_results = {"quiz_questions": []}
184
+
185
+ for part in text_parts:
186
+ actual_prompt = QUIZ_PROMPT_TEMPLATE.format(text=part)
187
+ prompt_tokens = len(tokenizer.encode(actual_prompt))
188
+ input_tokens += prompt_tokens
189
+
190
+ analysis = generate_with_gemini(part, gemini_api_key, language, "quiz")
191
+
192
+ if "quiz_questions" in analysis:
193
+ remaining_slots = 10 - len(all_results["quiz_questions"])
194
+ if remaining_slots > 0:
195
+ questions_to_add = analysis["quiz_questions"][:remaining_slots]
196
+ all_results["quiz_questions"].extend(questions_to_add)
197
+
198
+ formatted_output = format_quiz_for_display(all_results, language)
199
+
200
+ end_time = time.time()
201
+ total_time = end_time - start_time
202
+
203
+ output_tokens = len(tokenizer.encode(formatted_output))
204
+ token_info = f"Input tokens: {input_tokens}\nOutput tokens: {output_tokens}\nTotal tokens: {input_tokens + output_tokens}\n"
205
+ formatted_text = f"Total Processing time: {total_time:.2f}s\n{token_info}\n" + formatted_output
206
+
207
+ json_path = tempfile.mktemp(suffix='.json')
208
+ with open(json_path, 'w', encoding='utf-8') as json_file:
209
+ json.dump(all_results, json_file, indent=2)
210
+
211
+ txt_path = tempfile.mktemp(suffix='.txt')
212
+ with open(txt_path, 'w', encoding='utf-8') as txt_file:
213
+ txt_file.write(formatted_text)
214
+
215
+ return formatted_text, json_path, txt_path
216
+ except Exception as e:
217
+ error_message = f"Error processing document: {str(e)}"
218
+ return error_message, None, None
src/quiz_processing_1.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import json
4
+ import time
5
+ import gradio as gr
6
+ import tempfile
7
+ from typing import Dict, Any, List, Optional
8
+ from transformers import AutoTokenizer
9
+ from sentence_transformers import SentenceTransformer
10
+ from pydantic import BaseModel, Field
11
+ from anthropic import Anthropic
12
+ from huggingface_hub import login
13
+
14
+ CLAUDE_MODEL = "claude-3-5-sonnet-20241022"
15
+ OPENAI_MODEL = "gpt-4o"
16
+ GEMINI_MODEL = "gemini-2.0-flash"
17
+
18
+ DEFAULT_TEMPERATURE = 0.7
19
+
20
+ TOKENIZER_MODEL = "answerdotai/ModernBERT-base"
21
+ SENTENCE_TRANSFORMER_MODEL = "all-MiniLM-L6-v2"
22
+
23
+ class CourseInfo(BaseModel):
24
+ course_name: str = Field(description="Name of the course")
25
+ section_name: str = Field(description="Name of the course section")
26
+ lesson_name: str = Field(description="Name of the lesson")
27
+
28
+ class QuizOption(BaseModel):
29
+ text: str = Field(description="The text of the answer option")
30
+ correct: bool = Field(description="Whether this option is correct")
31
+
32
+ class QuizQuestion(BaseModel):
33
+ question: str = Field(description="The text of the quiz question")
34
+ options: List[QuizOption] = Field(description="List of answer options")
35
+
36
+ class Segment(BaseModel):
37
+ segment_number: int = Field(description="The segment number")
38
+ topic_name: str = Field(description="Unique and specific topic name that clearly differentiates it from other segments")
39
+ key_concepts: List[str] = Field(description="3-5 key concepts discussed in the segment")
40
+ summary: str = Field(description="Brief summary of the segment (3-5 sentences)")
41
+ quiz_questions: List[QuizQuestion] = Field(description="5 quiz questions based on the segment content")
42
+
43
+ class TextSegmentAnalysis(BaseModel):
44
+ course_info: CourseInfo = Field(description="Information about the course")
45
+ segments: List[Segment] = Field(description="List of text segments with analysis")
46
+
47
+
48
+ hf_token = os.environ.get('HF_TOKEN', None)
49
+ login(token=hf_token)
50
+
51
+ tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_MODEL)
52
+ sentence_model = SentenceTransformer(SENTENCE_TRANSFORMER_MODEL)
53
+
54
+ # System prompt
55
+ system_prompt = """You are an expert educational content analyzer. Your task is to analyze text content,
56
+ identify distinct segments, and create high-quality educational quiz questions for each segment."""
57
+
58
+ def clean_text(text):
59
+ text = re.sub(r'\[speaker_\d+\]', '', text)
60
+ text = re.sub(r'\s+', ' ', text).strip()
61
+ return text
62
+
63
+ def split_text_by_tokens(text, max_tokens=8000):
64
+ text = clean_text(text)
65
+ tokens = tokenizer.encode(text)
66
+
67
+ if len(tokens) <= max_tokens:
68
+ return [text]
69
+
70
+ split_point = len(tokens) // 2
71
+
72
+ sentences = re.split(r'(?<=[.!?])\s+', text)
73
+
74
+ first_half = []
75
+ second_half = []
76
+
77
+ current_tokens = 0
78
+ for sentence in sentences:
79
+ sentence_tokens = len(tokenizer.encode(sentence))
80
+
81
+ if current_tokens + sentence_tokens <= split_point:
82
+ first_half.append(sentence)
83
+ current_tokens += sentence_tokens
84
+ else:
85
+ second_half.append(sentence)
86
+
87
+ return [" ".join(first_half), " ".join(second_half)]
88
+
89
+
90
+
91
+ def generate_with_claude(text, api_key, course_name="", section_name="", lesson_name=""):
92
+ from prompts import SYSTEM_PROMPT, ANALYSIS_PROMPT_TEMPLATE_CLAUDE
93
+
94
+ client = Anthropic(api_key=api_key)
95
+
96
+ segment_analysis_schema = TextSegmentAnalysis.model_json_schema()
97
+
98
+ tools = [
99
+ {
100
+ "name": "build_segment_analysis",
101
+ "description": "Build the text segment analysis with quiz questions",
102
+ "input_schema": segment_analysis_schema
103
+ }
104
+ ]
105
+
106
+ system_prompt = """You are a helpful assistant specialized in text analysis and educational content creation.
107
+ You analyze texts to identify distinct segments, create summaries, and generate quiz questions."""
108
+
109
+ prompt = prompt = ANALYSIS_PROMPT_TEMPLATE_CLAUDE.format(
110
+ course_name=course_name,
111
+ section_name=section_name,
112
+ lesson_name=lesson_name,
113
+ text=text
114
+ )
115
+
116
+ try:
117
+ response = client.messages.create(
118
+ model=CLAUDE_MODEL,
119
+ max_tokens=8192,
120
+ temperature=DEFAULT_TEMPERATURE,
121
+ system=system_prompt,
122
+ messages=[
123
+ {
124
+ "role": "user",
125
+ "content": prompt
126
+ }
127
+ ],
128
+ tools=tools,
129
+ tool_choice={"type": "tool", "name": "build_segment_analysis"}
130
+ )
131
+
132
+ # Extract the tool call content
133
+ if response.content and len(response.content) > 0 and hasattr(response.content[0], 'input'):
134
+ function_call = response.content[0].input
135
+ return function_call
136
+ else:
137
+ raise Exception("No valid tool call found in the response")
138
+ except Exception as e:
139
+ raise Exception(f"Error calling Anthropic API: {str(e)}")
140
+
141
+
142
+
143
+
144
+ def get_llm_by_api_key(api_key):
145
+ if api_key.startswith("sk-ant-"): # Claude API key format
146
+ from langchain_anthropic import ChatAnthropic
147
+ return ChatAnthropic(
148
+ anthropic_api_key=api_key,
149
+ model_name=CLAUDE_MODEL,
150
+ temperature=DEFAULT_TEMPERATURE,
151
+ max_retries=3
152
+ )
153
+ elif api_key.startswith("sk-"): # OpenAI API key format
154
+ from langchain_openai import ChatOpenAI
155
+ return ChatOpenAI(
156
+ openai_api_key=api_key,
157
+ model_name=OPENAI_MODEL,
158
+ temperature=DEFAULT_TEMPERATURE,
159
+ max_retries=3
160
+ )
161
+ else: # Default to Gemini
162
+ from langchain_google_genai import ChatGoogleGenerativeAI
163
+ os.environ["GOOGLE_API_KEY"] = api_key
164
+ return ChatGoogleGenerativeAI(
165
+ model=GEMINI_MODEL,
166
+ temperature=DEFAULT_TEMPERATURE,
167
+ max_retries=3
168
+ )
169
+
170
+ def segment_and_analyze_text(text: str, api_key: str, course_name="", section_name="", lesson_name="") -> Dict[str, Any]:
171
+ from prompts import SYSTEM_PROMPT, ANALYSIS_PROMPT_TEMPLATE_GEMINI
172
+ if api_key.startswith("sk-ant-"):
173
+ return generate_with_claude(text, api_key, course_name, section_name, lesson_name)
174
+
175
+ # For other models, use LangChain
176
+ llm = get_llm_by_api_key(api_key)
177
+
178
+ prompt = ANALYSIS_PROMPT_TEMPLATE_GEMINI.format(
179
+ course_name=course_name,
180
+ section_name=section_name,
181
+ lesson_name=lesson_name,
182
+ text=text
183
+ )
184
+
185
+ try:
186
+ messages = [
187
+ {"role": "system", "content": system_prompt},
188
+ {"role": "user", "content": prompt}
189
+ ]
190
+
191
+ response = llm.invoke(messages)
192
+
193
+ try:
194
+ content = response.content
195
+ json_match = re.search(r'```json\s*([\s\S]*?)\s*```', content)
196
+
197
+ if json_match:
198
+ json_str = json_match.group(1)
199
+ else:
200
+ json_match = re.search(r'(\{[\s\S]*\})', content)
201
+ if json_match:
202
+ json_str = json_match.group(1)
203
+ else:
204
+ json_str = content
205
+
206
+ # Parse the JSON
207
+ function_call = json.loads(json_str)
208
+ return function_call
209
+ except json.JSONDecodeError:
210
+ raise Exception("Could not parse JSON from LLM response")
211
+ except Exception as e:
212
+ raise Exception(f"Error calling API: {str(e)}")
213
+
214
+ def format_quiz_for_display(results):
215
+ output = []
216
+
217
+ if "course_info" in results:
218
+ course_info = results["course_info"]
219
+ output.append(f"{'='*40}")
220
+ output.append(f"COURSE: {course_info.get('course_name', 'N/A')}")
221
+ output.append(f"SECTION: {course_info.get('section_name', 'N/A')}")
222
+ output.append(f"LESSON: {course_info.get('lesson_name', 'N/A')}")
223
+ output.append(f"{'='*40}\n")
224
+
225
+ segments = results.get("segments", [])
226
+ for i, segment in enumerate(segments):
227
+ topic = segment["topic_name"]
228
+ segment_num = i + 1
229
+ output.append(f"\n\n{'='*40}")
230
+ output.append(f"SEGMENT {segment_num}: {topic}")
231
+ output.append(f"{'='*40}\n")
232
+ output.append("KEY CONCEPTS:")
233
+ for concept in segment["key_concepts"]:
234
+ output.append(f"• {concept}")
235
+ output.append("\nSUMMARY:")
236
+ output.append(segment["summary"])
237
+ output.append("\nQUIZ QUESTIONS:")
238
+ for i, q in enumerate(segment["quiz_questions"]):
239
+ output.append(f"\n{i+1}. {q['question']}")
240
+ for j, option in enumerate(q['options']):
241
+ letter = chr(97 + j).upper()
242
+ correct_marker = " ✓" if option["correct"] else ""
243
+ output.append(f" {letter}. {option['text']}{correct_marker}")
244
+ return "\n".join(output)
245
+
246
+ def analyze_document(text, api_key, course_name, section_name, lesson_name):
247
+ try:
248
+ start_time = time.time()
249
+ text_parts = split_text_by_tokens(text)
250
+
251
+ all_results = {
252
+ "course_info": {
253
+ "course_name": course_name,
254
+ "section_name": section_name,
255
+ "lesson_name": lesson_name
256
+ },
257
+ "segments": []
258
+ }
259
+ segment_counter = 1
260
+
261
+ # Process each part of the text
262
+ for part in text_parts:
263
+ analysis = segment_and_analyze_text(
264
+ part,
265
+ api_key,
266
+ course_name=course_name,
267
+ section_name=section_name,
268
+ lesson_name=lesson_name
269
+ )
270
+
271
+ if "segments" in analysis:
272
+ for segment in analysis["segments"]:
273
+ segment["segment_number"] = segment_counter
274
+ all_results["segments"].append(segment)
275
+ segment_counter += 1
276
+
277
+ end_time = time.time()
278
+ total_time = end_time - start_time
279
+ print(f"Total quiz processing time: {total_time}s")
280
+
281
+ # Format the results for display
282
+ formatted_text = format_quiz_for_display(all_results)
283
+ # formatted_text = f"Total processing time: {total_time:.2f} seconds\n\n" + formatted_text
284
+
285
+ # Create temporary files for JSON and text output
286
+ json_path = tempfile.mktemp(suffix='.json')
287
+ with open(json_path, 'w', encoding='utf-8') as json_file:
288
+ json.dump(all_results, json_file, indent=2)
289
+
290
+ txt_path = tempfile.mktemp(suffix='.txt')
291
+ with open(txt_path, 'w', encoding='utf-8') as txt_file:
292
+ txt_file.write(formatted_text)
293
+
294
+ return formatted_text, json_path, txt_path
295
+ except Exception as e:
296
+ error_message = f"Error processing document: {str(e)}"
297
+ return error_message, None, None
src/video_processing.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import uuid
4
+ import subprocess
5
+ import time
6
+
7
+ def extract_audio_from_video(video_path, output_format="mp3"):
8
+ if not video_path:
9
+ return None
10
+
11
+ output_path = f"audio_{uuid.uuid4().hex[:6]}.{output_format}"
12
+
13
+ try:
14
+ cmd = [
15
+ "ffmpeg",
16
+ "-i", video_path,
17
+ "-vn",
18
+ "-c:a", "libmp3lame" if output_format == "mp3" else output_format,
19
+ "-q:a", "9",
20
+ "-ac", "1",
21
+ "-ar", "12000",
22
+ "-y", output_path
23
+ ]
24
+
25
+ subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26
+
27
+ if os.path.exists(output_path):
28
+ return output_path
29
+ else:
30
+ raise Exception("Audio extraction failed")
31
+ except Exception as e:
32
+ raise Exception(f"Error extracting audio: {str(e)}")
33
+
34
+ def transcribe_audio(audio_path, api_key, model_id="scribe_v1"):
35
+ if not api_key:
36
+ raise Exception("API key required")
37
+
38
+ url = "https://api.elevenlabs.io/v1/speech-to-text"
39
+ headers = {"xi-api-key": api_key}
40
+
41
+ try:
42
+ with open(audio_path, "rb") as file:
43
+ response = requests.post(
44
+ url,
45
+ headers=headers,
46
+ files={"file": file, "model_id": (None, model_id)},
47
+ timeout=120
48
+ )
49
+
50
+ if response.status_code == 200:
51
+ result = response.json()
52
+ transcript_text = result.get("text", "")
53
+
54
+ # Save transcript to file
55
+ transcript_file = f"transcript_{uuid.uuid4().hex[:6]}.txt"
56
+ with open(transcript_file, "w", encoding="utf-8") as f:
57
+ f.write(transcript_text)
58
+
59
+ return transcript_text, transcript_file, "Transcription completed successfully"
60
+ else:
61
+ raise Exception(f"API error: {response.status_code}")
62
+ except Exception as e:
63
+ raise Exception(f"Transcription failed: {str(e)}")
64
+
65
+ def process_video_file(video_path, audio_format, elevenlabs_api_key, model_id, gemini_api_key, language, content_type):
66
+ try:
67
+ print("Starting video processing...")
68
+ start = time.time()
69
+
70
+ audio_path = extract_audio_from_video(video_path, audio_format)
71
+ print(f"Audio extracted in {time.time() - start:.2f}s. Transcribing...")
72
+
73
+ transcription, transcript_path, transcription_status = transcribe_audio(
74
+ audio_path,
75
+ elevenlabs_api_key,
76
+ model_id
77
+ )
78
+
79
+ if not transcription:
80
+ return audio_path, "Audio extracted, but transcription failed", None, transcription_status, None, None, None
81
+
82
+ print(f"Transcription completed in {time.time() - start:.2f}s. Analyzing content...")
83
+
84
+ # Generate summary or quiz from transcription
85
+ formatted_output, json_path, txt_path = analyze_document(
86
+ transcription,
87
+ gemini_api_key,
88
+ language,
89
+ content_type
90
+ )
91
+
92
+ print(f"Total processing time: {time.time() - start:.2f}s")
93
+ return audio_path, "Processing completed successfully", transcript_path, transcription_status, formatted_output, txt_path, json_path
94
+ except Exception as e:
95
+ error_message = f"Error processing video: {str(e)}"
96
+ return None, error_message, None, error_message, error_message, None, None