: concrete examples vs. vague statements - Completeness: covers all aspects of the question - STAR method (for behavioral questions): Situation, Task, Action, Result
Return complete analysis as valid JSON.
"""
response = self.analyzer.analyze_segment(segment_frames, segment_audio)
results.append(json.loads(response))
# Respect rate limits (15 RPM on free tier)
time.sleep(4)
return self._generate_report(results)
def _generate_report(self, segment_results):
"""
Aggregates segment scores into final report card
"""
report = {
'timestamp': datetime.now().isoformat(),
'overall_scores': {},
'segment_breakdown': segment_results,
'trends': {},
'feedback': {}
}
# Calculate average scores across all segments
metrics = ['eye_contact', 'body_language', 'pacing', 'tone',
'filler_words', 'clarity', 'answer_quality']
for metric in metrics:
scores = [seg.get(metric, 0) for seg in segment_results]
report['overall_scores'][metric] = {
'average': sum(scores) / len(scores),
'min': min(scores),
'max': max(scores),
'trend': 'improving' if scores[-1] > scores[0] else 'declining'
}
# Generate actionable feedback
report['feedback'] = self._generate_feedback(report['overall_scores'])
return report
def _generate_feedback(self, scores):
"""
Creates specific improvement recommendations
"""
feedback = {
'strengths': [],
'priority_improvements': [],
'specific_tips': []
}
# Identify top 2 strengths (scores >= 8)
strong_areas = [(k, v['average']) for k, v in scores.items()
if v['average'] >= 8.0]
strong_areas.sort(key=lambda x: x[1], reverse=True)
feedback['strengths'] = [area[0] for area in strong_areas[:2]]
# Identify top 2-3 improvement areas (scores < 7)
weak_areas = [(k, v['average']) for k, v in scores.items()
if v['average'] < 7.0]
weak_areas.sort(key=lambda x: x[1])
feedback['priority_improvements'] = [area[0] for area in weak_areas[:3]]
# Generate specific tips based on weak areas
tip_map = {
'eye_contact': "Practice looking at the camera lens, not your own image on screen. Try the 5-second rule: hold eye contact for 5 seconds, then briefly look away.",
'pacing': "Your speaking rate needs adjustment. Record yourself and count words per minute, aiming for 140-160 WPM. Use a metronome app to practice consistent pacing.",
'filler_words': "You're overusing filler words. Try pausing silently instead of saying 'um' or 'uh.' Practice with a friend who snaps their fingers every time you use a filler.",
'tone': "Your pitch variance is limited. Practice emphasizing key words in your answers. Read children's books aloud to exaggerate vocal variety, then dial it back to natural levels.",
'answer_quality': "Structure your answers using STAR method: Situation, Task, Action, Result. Write out 10 common interview questions and script STAR responses for each.",
'body_language': "You're showing nervous habits. Before interviews, do power poses for 2 minutes. Keep hands visible and use purposeful gestures to emphasize points.",
'clarity': "Work on articulation. Practice tongue twisters daily. Slow down slightly and finish your sentences with clear, downward inflections."
}
for area in feedback['priority_improvements']:
if area in tip_map:
feedback['specific_tips'].append(tip_map[area])
return feedback
def _create_wav_from_chunks(self, audio_chunks):
"""Combines audio chunks into WAV file bytes"""
# Implementation details for WAV file creation
pass
def _extract_audio_segment(self, audio_data, start_sec, duration_sec):
"""Extracts specific time range from audio data"""
# Implementation details for audio segmentation
pass
How Do You Handle the Free Tier Rate Limits and Optimize Costs
The free tier's 15 requests per minute limit means you'll need 4-second delays between API calls when processing a multi-segment interview. For a 5-minute interview (5 segments), total processing time will be around 60 seconds (8-12 seconds per request plus 4-second delays). That's still faster than real-time analysis.
You can optimize by reducing frame sampling. Instead of sending all 60 frames from a 1-minute segment, send every third frame (20 frames total). Testing shows this reduces processing time by about 30% with minimal impact on visual analysis accuracy, and honestly, most people won't notice the difference in scoring.
For longer practice sessions, implement local caching. Store processed segments so you don't re-analyze the same footage if you want to review it later. You can also batch multiple questions into a single longer segment (up to 60 seconds) rather than analyzing each 30-second answer separately.
The free tier's 1,500 requests per day allows roughly 300 five-minute interview sessions daily, which is more than sufficient for personal use. If you're building this for a team or coaching service, you'll need to track usage per user and implement queuing to stay within limits.
How Do You Build the User Interface and Display Results
A simple Streamlit interface works well for this application. You'll create a page with a video preview window, question display area, recording controls, and results dashboard. Streamlit handles the webcam integration through its built-in components, which simplifies the frontend code significantly.
The results dashboard should display your five core metrics as progress bars with color coding (green for 8-10, yellow for 6-7, red for below 6). Include a timeline view that shows score variations across segments, so you can see if you got stronger or weaker as the interview progressed. Most people fade after the 3-minute mark.
For each metric, provide expandable detail sections with specific observations from Gemini's analysis. If the system flagged a moment where you looked away for 8 seconds, show the timestamp and a thumbnail of that frame. If it detected 7 filler words in a 60-second segment, display the transcript with those words highlighted.
Here's a basic Streamlit implementation:
import streamlit as st
import time
def main():
st.title("AI Interview Coach")
st.write("Practice your interview skills with real-time feedback")
# Sidebar for configuration
with st.sidebar:
st.header("Settings")
api_key = st.text_input("Gemini API Key", type="password")
st.header("Interview Questions")
questions = []
num_questions = st.number_input("Number of questions", 1, 10, 3)
for i in range(num_questions):
q = st.text_area(f"Question {i+1}",
value=f"Tell me about a challenging project you worked on.")
questions.append(q)
# Main area
if not api_key:
st.warning("Please enter your Gemini API key in the sidebar")
return
coach = InterviewCoach(api_key)
col1, col2 = st.columns([2, 1])
with col1:
st.header("Interview Session")
# Video preview
camera = st.camera_input("Camera Preview")
if st.button("Start Interview", type="primary"):
with st.spinner("Recording and analyzing..."):
results = coach.conduct_interview(questions, duration_minutes=len(questions))
st.session_state['results'] = results
with col2:
st.header("Quick Tips")
st.info("""
- Look directly at the camera
- Speak at 140-160 words per minute
- Use STAR method for behavioral questions
- Limit filler words to 3 per minute
- Sit up straight with open posture
""")
# Results display
if 'results' in st.session_state:
display_results(st.session_state['results'])
def display_results(results):
st.header("Performance Report Card")
# Overall scores
st.subheader("Overall Scores")
scores = results['overall_scores']
col1, col2, col3 = st.columns(3)
with col1:
display_metric_card("Eye Contact", scores['eye_contact'])
display_metric_card("Pacing", scores['pacing'])
with col2:
display_metric_card("Tone", scores['tone'])
display_metric_card("Filler Words", scores['filler_words'])
with col3:
display_metric_card("Body Language", scores['body_language'])
display_metric_card("Answer Quality", scores['answer_quality'])
# Trends
st.subheader("Performance Trends")
trend_data = []
for metric, data in scores.items():
trend_data.append({
'Metric': metric.replace('_', ' ').title(),
'Trend': '📈' if data['trend'] == 'improving' else '📉',
'Change': f"{data['max'] - data['min']:.1f} points"
})
st.table(trend_data)
# Feedback
st.subheader("Personalized Feedback")
feedback = results['feedback']
if feedback['strengths']:
st.success(f"**Your Strengths:** {', '.join(feedback['strengths'])}")
if feedback['priority_improvements']:
st.warning(f"**Focus On:** {', '.join(feedback['priority_improvements'])}")
st.subheader("Action Items")
for i, tip in enumerate(feedback['specific_tips'], 1):
st.write(f"{i}. {tip}")
# Detailed segment breakdown
with st.expander("View Detailed Segment Analysis"):
for i, segment in enumerate(results['segment_breakdown'], 1):
st.write(f"**Segment {i}**")
st.json(segment)
def display_metric_card(name, score_data):
avg_score = score_data['average']
# Color coding
if avg_score >= 8:
color = "green"
elif avg_score >= 6:
color = "orange"
else:
color = "red"
st.metric(
label=name,
value=f"{avg_score:.1f}/10",
delta=f"{score_data['trend']}"
)
st.progress(avg_score / 10)
if __name__ == "__main__":
main()
What Are Common Issues and How Do You Debug Them
The most frequent problem is API timeout errors when processing longer video segments. If a request takes more than 60 seconds, it'll fail. Solution: reduce frame sampling from 1 FPS to 0.5 FPS (one frame every 2 seconds), which cuts processing time roughly in half.
Audio sync issues can occur if your video and audio capture threads drift out of alignment. You'll notice this when Gemini's transcript doesn't match what you actually said at specific timestamps. Fix this by adding timestamp markers to both streams and implementing a synchronization check every 10 seconds during recording.
Low-quality scores across all metrics often indicate poor lighting or audio input levels. Before starting an interview, run a quick calibration check: capture 5 seconds of footage and verify that faces are clearly visible and audio levels peak around 70-80% of maximum. Bad input data will tank your scores no matter how good your actual performance is.
Rate limit errors (429 status codes) mean you're exceeding 15 requests per minute. Add exponential backoff: if a request fails, wait 5 seconds and retry, doubling the wait time with each subsequent failure up to a maximum of 60 seconds.
Here's error handling code:
import time
from google.api_core import exceptions
def analyze_with_retry(analyzer, frames, audio, max_retries=3):
"""
Wrapper for API calls with exponential backoff
"""
for attempt in range(max_retries):
try:
result = analyzer.analyze_segment(frames, audio)
return result
except exceptions.ResourceExhausted:
# Rate limit hit
wait_time = min(5 * (2 ** attempt), 60)
print(f"Rate limit reached. Waiting {wait_time} seconds...")
time.sleep(wait_time)
except exceptions.DeadlineExceeded:
# Request timeout
print("Request timeout. Reducing frame count...")
frames = frames[::2] # Use every other frame
except Exception as e:
print(f"Unexpected error: {e}")
if attempt == max_retries - 1:
raise
return None
def validate_input_quality(frame, audio_chunk):
"""
Checks if input data meets minimum quality standards
"""
issues = []
# Check frame brightness
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
brightness = gray.mean()
if brightness < 50:
issues.append("Lighting too dark (increase brightness)")
elif brightness > 200:
issues.append("Overexposed video (reduce brightness)")
# Check audio levels
audio_array = np.frombuffer(audio_chunk, dtype=np.int16)
audio_level = np.abs(audio_array).mean()
max_level = np.iinfo(np.int16).max
if audio_level < max_level * 0.1:
issues.append("Audio too quiet (move closer to mic)")
elif audio_level > max_level * 0.9:
issues.append("Audio clipping (reduce input volume)")
return issues
How Can You Extend This System for Advanced Features
You can add question-specific scoring by training custom evaluation criteria for different interview types. Technical interviews need different metrics than behavioral interviews. For technical questions, you'd instruct Gemini to check for specific concepts, algorithms, or frameworks in the answer. For behavioral questions, you'd weight STAR method compliance more heavily.
Comparative analysis lets you track improvement over multiple practice sessions. Store results in a local SQLite database with timestamps, then generate progress charts showing how your scores change over weeks of practice. You'll see which areas improve fastest and which remain stubborn weak points.
Real interviewer simulation adds another layer: use Gemini to generate follow-up questions based on your answers, creating a dynamic conversation instead of a static question list. The system analyzes your response, identifies areas that need clarification, and asks a relevant follow-up. This mimics real interview dynamics better than pre-written questions.
Peer comparison (anonymized) could work if you're building this for a study group or bootcamp. Aggregate scores across users to show percentile rankings: "Your pacing is in the 78th percentile among users." This gives context for whether a score of 7.2 is actually good or needs work.
Here's a framework for question-specific scoring:
class QuestionTypeScorer:
def __init__(self):
self.scoring_templates = {
'behavioral': {
'required_elements': ['situation', 'task', 'action', 'result'],
'weights': {
'structure': 0.3,
'specificity': 0.3,
Get a free AI-powered SEO audit of your site
We'll crawl your site, benchmark your local pack, and hand you a prioritized fix list in minutes. No call required.
Run my free audit