ferhatbou commited on
Commit
6cf7dc2
·
1 Parent(s): 1d0abcd

Fix import issue

Browse files
Files changed (6) hide show
  1. README.md +32 -0
  2. VideoAccentAnalyzer.py +0 -0
  3. app.py +1 -1
  4. requirements.txt +7 -0
  5. setup.sh +3 -0
  6. video_accent_analyzer.py +628 -0
README.md CHANGED
@@ -12,3 +12,35 @@ short_description: 'a tools to automate real hiring decisions. '
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
15
+ # 🎧 Video Accent Analyzer
16
+
17
+ Analyze accents in videos from YouTube, Loom, or uploaded files. Supports multiple English accents.
18
+
19
+ ## Features
20
+ - YouTube video analysis
21
+ - Loom video analysis
22
+ - Direct MP4 link support
23
+ - Local file upload
24
+ - Multiple English accent detection
25
+
26
+ ## Requirements
27
+ - Python 3.8+
28
+ - FFmpeg
29
+ - PyTorch
30
+ - Transformers
31
+
32
+ ## Usage
33
+ 1. Enter a video URL or upload a file
34
+ 2. Get instant accent analysis results
35
+
36
+ """
37
+ Enhanced Video Accent Analyzer
38
+ Supports YouTube, Loom, direct MP4 links, and local video files with improved error handling and features.
39
+
40
+ Usage:
41
+ analyzer = VideoAccentAnalyzer()
42
+ results = analyzer.analyze_video_url("https://example.com/video.mp4", max_duration=30)
43
+ or
44
+ results = analyzer.analyze_local_video("/local/input/video.mp4", max_duration=30)
45
+ analyzer.display_results(results)
46
+ """
VideoAccentAnalyzer.py DELETED
File without changes
app.py CHANGED
@@ -1,5 +1,5 @@
1
  import gradio as gr
2
- from VideoAccentAnalyzer import VideoAccentAnalyzer
3
  import ffmpeg
4
  import os
5
 
 
1
  import gradio as gr
2
+ from video_accent_analyzer import VideoAccentAnalyzer
3
  import ffmpeg
4
  import os
5
 
requirements.txt CHANGED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ yt-dlp
2
+ librosa
3
+ soundfile
4
+ transformers
5
+ torch
6
+ gradio
7
+ ffmpeg-python
setup.sh CHANGED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ #!/bin/bash
2
+ apt-get update && apt-get install -y ffmpeg
3
+ pip install -r requirements.txt
video_accent_analyzer.py ADDED
@@ -0,0 +1,628 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import sys
4
+ import tempfile
5
+ import subprocess
6
+ import requests
7
+ import json
8
+ import warnings
9
+ import time
10
+ from pathlib import Path
11
+ from urllib.parse import urlparse
12
+ from IPython.display import display, HTML, Audio
13
+ import pandas as pd
14
+ import matplotlib.pyplot as plt
15
+ import seaborn as sns
16
+
17
+ # Suppress warnings for cleaner output
18
+ warnings.filterwarnings('ignore')
19
+
20
+
21
+ def install_if_missing(packages):
22
+ """Install packages if they're not already available in Kaggle"""
23
+ for package in packages:
24
+ try:
25
+ package_name = package.split('==')[0].replace('-', '_')
26
+ if package_name == 'yt_dlp':
27
+ package_name = 'yt_dlp'
28
+ __import__(package_name)
29
+ except ImportError:
30
+ print(f"Installing {package}...")
31
+ subprocess.check_call([sys.executable, "-m", "pip", "install", package, "--quiet"])
32
+
33
+
34
+ # Required packages for Kaggle
35
+ required_packages = [
36
+ "yt-dlp",
37
+ "librosa",
38
+ "soundfile",
39
+ "transformers",
40
+ "torch",
41
+ "matplotlib",
42
+ "seaborn"
43
+ ]
44
+
45
+ print("🔧 Setting up environment...")
46
+ install_if_missing(required_packages)
47
+
48
+ # Now import the packages
49
+ import torch
50
+ from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2ForSequenceClassification
51
+ import librosa
52
+ import soundfile as sf
53
+ import yt_dlp
54
+
55
+
56
+ class VideoAccentAnalyzer:
57
+ def __init__(self, model_name="dima806/multiple_accent_classification"):
58
+ """Initialize the accent analyzer for Kaggle environment"""
59
+ self.model_name = model_name
60
+ # Enhanced accent labels with better mapping
61
+ self.accent_labels = [
62
+ "british", "canadian", "us", "indian", "australian", "neutral"
63
+ ]
64
+ self.accent_display_names = {
65
+ 'british': '🇬🇧 British English',
66
+ 'us': '🇺🇸 American English',
67
+ 'australian': '🇦🇺 Australian English',
68
+ 'canadian': '🇨🇦 Canadian English',
69
+ 'indian': '🇮🇳 Indian English',
70
+ 'neutral': '🌐 Neutral English'
71
+ }
72
+ self.temp_dir = "/tmp/accent_analyzer"
73
+ os.makedirs(self.temp_dir, exist_ok=True)
74
+ self.model_loaded = False
75
+ self._load_model()
76
+
77
+ def _load_model(self):
78
+ """Load the accent classification model with error handling"""
79
+ print("🤖 Loading accent classification model...")
80
+ try:
81
+ self.feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(self.model_name)
82
+ self.model = Wav2Vec2ForSequenceClassification.from_pretrained(self.model_name)
83
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
84
+ self.model.to(self.device)
85
+ self.model.eval() # Set to evaluation mode
86
+ self.model_loaded = True
87
+ print(f"✅ Model loaded successfully on {self.device}")
88
+ except Exception as e:
89
+ print(f"❌ Error loading model: {e}")
90
+ print("💡 Tip: Check your internet connection and Kaggle environment setup")
91
+ raise
92
+
93
+ def _validate_url(self, url):
94
+ """Validate and normalize URL"""
95
+ if not url or not isinstance(url, str):
96
+ return False, "Invalid URL format"
97
+
98
+ url = url.strip()
99
+ if not url.startswith(('http://', 'https://')):
100
+ return False, "URL must start with http:// or https://"
101
+
102
+ return True, url
103
+
104
+ def download_video(self, url, max_duration=None):
105
+ """Download video using yt-dlp with improved error handling"""
106
+ is_valid, result = self._validate_url(url)
107
+ if not is_valid:
108
+ print(f"❌ {result}")
109
+ return None
110
+
111
+ url = result
112
+ output_path = os.path.join(self.temp_dir, "video.%(ext)s")
113
+
114
+ ydl_opts = {
115
+ 'outtmpl': output_path,
116
+ 'format': 'best[height<=720]/best', # Limit quality for faster download
117
+ 'quiet': True,
118
+ 'no_warnings': True,
119
+ 'socket_timeout': 30,
120
+ 'retries': 3,
121
+ }
122
+
123
+ if max_duration:
124
+ ydl_opts['match_filter'] = lambda info: None if info.get('duration',
125
+ 0) <= max_duration * 2 else "Video too long"
126
+
127
+ try:
128
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
129
+ print(f"📥 Downloading video from: {url}")
130
+ start_time = time.time()
131
+ ydl.download([url])
132
+ download_time = time.time() - start_time
133
+
134
+ # Find downloaded file
135
+ for file in os.listdir(self.temp_dir):
136
+ if file.startswith("video."):
137
+ video_path = os.path.join(self.temp_dir, file)
138
+ if self._is_valid_video(video_path):
139
+ print(f"✅ Downloaded valid video: {file} ({download_time:.1f}s)")
140
+ return video_path
141
+ else:
142
+ print("❌ Downloaded file is not a valid video")
143
+ return None
144
+
145
+ except Exception as e:
146
+ print(f"⚠️ yt-dlp failed: {e}")
147
+ return self._try_direct_download(url)
148
+
149
+ def _is_valid_video(self, file_path):
150
+ """Verify video file has valid structure"""
151
+ try:
152
+ result = subprocess.run(
153
+ ['ffprobe', '-v', 'error', '-show_format', '-show_streams', file_path],
154
+ capture_output=True, text=True, timeout=10
155
+ )
156
+ return result.returncode == 0
157
+ except subprocess.TimeoutExpired:
158
+ print("⚠️ Video validation timed out")
159
+ return False
160
+ except Exception as e:
161
+ print(f"⚠️ Video validation error: {e}")
162
+ return False
163
+
164
+ def _try_direct_download(self, url):
165
+ """Enhanced fallback for direct video URLs"""
166
+ try:
167
+ print("🔄 Trying direct download...")
168
+ headers = {
169
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
170
+ }
171
+
172
+ response = requests.get(url, stream=True, timeout=60, headers=headers)
173
+ response.raise_for_status()
174
+
175
+ content_type = response.headers.get("Content-Type", "")
176
+ if "text/html" in content_type:
177
+ print("⚠️ Received HTML instead of video - check URL access")
178
+ return None
179
+
180
+ video_path = os.path.join(self.temp_dir, "video.mp4")
181
+ file_size = 0
182
+
183
+ with open(video_path, 'wb') as f:
184
+ for chunk in response.iter_content(chunk_size=8192):
185
+ if chunk:
186
+ f.write(chunk)
187
+ file_size += len(chunk)
188
+
189
+ print(f"📁 Downloaded {file_size / (1024 * 1024):.1f} MB")
190
+
191
+ if self._is_valid_video(video_path):
192
+ print("✅ Direct download successful")
193
+ return video_path
194
+ else:
195
+ print("❌ Downloaded file is not a valid video")
196
+ return None
197
+
198
+ except Exception as e:
199
+ print(f"❌ Direct download failed: {e}")
200
+ return None
201
+
202
+ def extract_audio(self, video_path, max_duration=None):
203
+ """Extract audio with improved error handling and progress"""
204
+ audio_path = os.path.join(self.temp_dir, "audio.wav")
205
+
206
+ cmd = ['ffmpeg', '-i', video_path, '-vn', '-acodec', 'pcm_s16le',
207
+ '-ar', '16000', '-ac', '1', '-y', '-loglevel', 'error']
208
+
209
+ if max_duration:
210
+ cmd.extend(['-t', str(max_duration)])
211
+ cmd.append(audio_path)
212
+
213
+ try:
214
+ print(f"🎵 Extracting audio (max {max_duration}s)...")
215
+ start_time = time.time()
216
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
217
+ extraction_time = time.time() - start_time
218
+
219
+ if result.returncode == 0 and os.path.exists(audio_path):
220
+ file_size = os.path.getsize(audio_path) / (1024 * 1024)
221
+ print(f"✅ Audio extracted successfully ({extraction_time:.1f}s, {file_size:.1f}MB)")
222
+ return audio_path
223
+ else:
224
+ raise Exception(f"FFmpeg error: {result.stderr}")
225
+
226
+ except subprocess.TimeoutExpired:
227
+ print("❌ Audio extraction timed out")
228
+ return None
229
+ except Exception as e:
230
+ print(f"❌ Audio extraction failed: {e}")
231
+ return None
232
+
233
+ def classify_accent(self, audio_path):
234
+ """Enhanced accent classification with better preprocessing"""
235
+ if not self.model_loaded:
236
+ print("❌ Model not loaded properly")
237
+ return None
238
+
239
+ try:
240
+ print("🔍 Loading and preprocessing audio...")
241
+ audio, sr = librosa.load(audio_path, sr=16000)
242
+
243
+ # Enhanced preprocessing
244
+ if len(audio) == 0:
245
+ print("❌ Empty audio file")
246
+ return None
247
+
248
+ # Remove silence from beginning and end
249
+ audio_trimmed, _ = librosa.effects.trim(audio, top_db=20)
250
+
251
+ # Use multiple chunks for better accuracy if audio is long
252
+ chunk_size = 16000 * 20 # 20 seconds chunks
253
+ chunks = []
254
+
255
+ if len(audio_trimmed) > chunk_size:
256
+ # Split into overlapping chunks
257
+ step_size = chunk_size // 2
258
+ for i in range(0, len(audio_trimmed) - chunk_size + 1, step_size):
259
+ chunks.append(audio_trimmed[i:i + chunk_size])
260
+ if len(audio_trimmed) % step_size != 0:
261
+ chunks.append(audio_trimmed[-chunk_size:])
262
+ else:
263
+ chunks = [audio_trimmed]
264
+
265
+ print(f"🎯 Analyzing {len(chunks)} audio chunk(s)...")
266
+
267
+ all_predictions = []
268
+
269
+ for i, chunk in enumerate(chunks[:3]): # Limit to 3 chunks for efficiency
270
+ inputs = self.feature_extractor(
271
+ chunk,
272
+ sampling_rate=16000,
273
+ return_tensors="pt",
274
+ padding=True,
275
+ max_length=16000 * 20,
276
+ truncation=True
277
+ )
278
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
279
+
280
+ with torch.no_grad():
281
+ outputs = self.model(**inputs)
282
+ logits = outputs.logits
283
+ probabilities = torch.nn.functional.softmax(logits, dim=-1)
284
+ all_predictions.append(probabilities[0].cpu().numpy())
285
+
286
+ # Average predictions across chunks
287
+ avg_probabilities = sum(all_predictions) / len(all_predictions)
288
+ predicted_idx = avg_probabilities.argmax()
289
+ predicted_idx = min(predicted_idx, len(self.accent_labels) - 1)
290
+
291
+ # Calculate English confidence (exclude 'neutral' for this calculation)
292
+ english_accents = ["british", "canadian", "us", "australian", "indian"]
293
+ english_confidence = sum(
294
+ avg_probabilities[i] * 100
295
+ for i, label in enumerate(self.accent_labels)
296
+ if label in english_accents
297
+ )
298
+
299
+ results = {
300
+ 'predicted_accent': self.accent_labels[predicted_idx],
301
+ 'accent_confidence': avg_probabilities[predicted_idx] * 100,
302
+ 'english_confidence': english_confidence,
303
+ 'audio_duration': len(audio) / 16000,
304
+ 'processed_duration': len(audio_trimmed) / 16000,
305
+ 'chunks_analyzed': len(all_predictions),
306
+ 'all_probabilities': {
307
+ self.accent_labels[i]: avg_probabilities[i] * 100
308
+ for i in range(len(self.accent_labels))
309
+ },
310
+ 'is_english_likely': english_confidence > 60,
311
+ 'audio_quality_score': self._assess_audio_quality(audio_trimmed)
312
+ }
313
+
314
+ print(f"✅ Classification complete ({results['chunks_analyzed']} chunks)")
315
+ return results
316
+
317
+ except Exception as e:
318
+ print(f"❌ Classification failed: {e}")
319
+ return None
320
+
321
+ def _assess_audio_quality(self, audio):
322
+ """Assess audio quality for better result interpretation"""
323
+ try:
324
+ # Simple quality metrics
325
+ rms_energy = librosa.feature.rms(y=audio)[0].mean()
326
+ zero_crossing_rate = librosa.feature.zero_crossing_rate(audio)[0].mean()
327
+
328
+ # Normalize to 0-100 scale
329
+ quality_score = min(100, (rms_energy * 1000 + (1 - zero_crossing_rate) * 50))
330
+ return max(0, quality_score)
331
+ except:
332
+ return 50 # Default moderate quality
333
+
334
+ def analyze_video_url(self, url, max_duration=30):
335
+ """Complete pipeline with enhanced error handling"""
336
+ print(f"🎬 Starting analysis of: {url}")
337
+ print(f"⏱️ Max duration: {max_duration} seconds")
338
+
339
+ video_path = self.download_video(url, max_duration)
340
+ if not video_path:
341
+ return {"error": "Failed to download video", "url": url}
342
+
343
+ audio_path = self.extract_audio(video_path, max_duration)
344
+ if not audio_path:
345
+ return {"error": "Failed to extract audio", "url": url}
346
+
347
+ results = self.classify_accent(audio_path)
348
+ if not results:
349
+ return {"error": "Failed to classify accent", "url": url}
350
+
351
+ results.update({
352
+ 'source_url': url,
353
+ 'video_file': os.path.basename(video_path),
354
+ 'audio_file': os.path.basename(audio_path),
355
+ 'analysis_timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
356
+ })
357
+
358
+ return results
359
+
360
+ def analyze_local_video(self, file_path, max_duration=30):
361
+ """Enhanced local video analysis"""
362
+ print(f"🎬 Starting analysis of local file: {file_path}")
363
+ print(f"⏱️ Max duration: {max_duration} seconds")
364
+
365
+ if not os.path.isfile(file_path):
366
+ return {"error": f"File not found: {file_path}"}
367
+
368
+ # Check file size
369
+ file_size = os.path.getsize(file_path) / (1024 * 1024) # MB
370
+ print(f"📁 File size: {file_size:.1f} MB")
371
+
372
+ video_filename = os.path.basename(file_path)
373
+ print(f"✅ Using local video: {video_filename}")
374
+
375
+ audio_path = self.extract_audio(file_path, max_duration)
376
+ if not audio_path:
377
+ return {"error": "Failed to extract audio"}
378
+
379
+ results = self.classify_accent(audio_path)
380
+ if not results:
381
+ return {"error": "Failed to classify accent"}
382
+
383
+ results.update({
384
+ 'source_file': file_path,
385
+ 'video_file': video_filename,
386
+ 'audio_file': os.path.basename(audio_path),
387
+ 'file_size_mb': file_size,
388
+ 'is_local': True,
389
+ 'analysis_timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
390
+ })
391
+
392
+ return results
393
+
394
+ def display_results(self, results):
395
+ """Enhanced results display with visualizations"""
396
+ if 'error' in results:
397
+ display(HTML(
398
+ f"<div style='color: red; font-size: 16px; padding: 10px; border: 1px solid red; border-radius: 5px;'>❌ {results['error']}</div>"))
399
+ return
400
+
401
+ accent = results['predicted_accent']
402
+ confidence = results['accent_confidence']
403
+ english_conf = results['english_confidence']
404
+ duration = results['audio_duration']
405
+ processed_duration = results.get('processed_duration', duration)
406
+ quality_score = results.get('audio_quality_score', 50)
407
+
408
+ accent_display = self.accent_display_names.get(accent, accent.title())
409
+
410
+ # Enhanced HTML display
411
+ html = f"""
412
+ <div style='border: 2px solid #4CAF50; border-radius: 10px; padding: 20px; margin: 10px 0; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);'>
413
+ <h2 style='color: #2E7D32; margin-top: 0; text-align: center;'>🎯 Accent Analysis Results</h2>
414
+
415
+ <div style='display: flex; flex-wrap: wrap; gap: 20px; margin-bottom: 20px;'>
416
+ <div style='flex: 1; min-width: 200px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
417
+ <h3 style='color: #1976D2; margin-top: 0;'>🎭 Primary Classification</h3>
418
+ <p style='font-size: 20px; margin: 5px 0; font-weight: bold;'>{accent_display}</p>
419
+ <p style='margin: 5px 0;'>Confidence: <strong style='color: {"#4CAF50" if confidence >= 70 else "#FF9800" if confidence >= 50 else "#F44336"};'>{confidence:.1f}%</strong></p>
420
+ </div>
421
+
422
+ <div style='flex: 1; min-width: 200px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
423
+ <h3 style='color: #1976D2; margin-top: 0;'>🌍 English Proficiency</h3>
424
+ <p style='font-size: 18px; margin: 5px 0;'><strong style='color: {"#4CAF50" if english_conf >= 70 else "#FF9800" if english_conf >= 50 else "#F44336"};'>{english_conf:.1f}%</strong></p>
425
+ <p style='margin: 5px 0;'>Audio Quality: <strong>{quality_score:.0f}/100</strong></p>
426
+ </div>
427
+
428
+ <div style='flex: 1; min-width: 200px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
429
+ <h3 style='color: #1976D2; margin-top: 0;'>⏱️ Processing Info</h3>
430
+ <p style='margin: 5px 0;'>Duration: <strong>{duration:.1f}s</strong></p>
431
+ <p style='margin: 5px 0;'>Processed: <strong>{processed_duration:.1f}s</strong></p>
432
+ <p style='margin: 5px 0;'>Chunks: <strong>{results.get("chunks_analyzed", 1)}</strong></p>
433
+ </div>
434
+ </div>
435
+
436
+ <div style='background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>
437
+ <h3 style='color: #1976D2; margin-top: 0;'>📊 Assessment</h3>
438
+ <div style='display: flex; flex-wrap: wrap; gap: 10px;'>
439
+ <span style='background: {"#4CAF50" if english_conf >= 70 else "#FF9800" if english_conf >= 50 else "#F44336"}; color: white; padding: 5px 10px; border-radius: 15px; font-size: 14px;'>
440
+ {'✅ Strong English Speaker' if english_conf >= 70 else '⚠️ Moderate English Confidence' if english_conf >= 50 else '❓ Low English Confidence'}
441
+ </span>
442
+ <span style='background: {"#4CAF50" if confidence >= 70 else "#FF9800" if confidence >= 50 else "#F44336"}; color: white; padding: 5px 10px; border-radius: 15px; font-size: 14px;'>
443
+ {'🎯 High Confidence' if confidence >= 70 else '🤔 Moderate Confidence' if confidence >= 50 else '❓ Low Confidence'}
444
+ </span>
445
+ <span style='background: {"#4CAF50" if quality_score >= 70 else "#FF9800" if quality_score >= 40 else "#F44336"}; color: white; padding: 5px 10px; border-radius: 15px; font-size: 14px;'>
446
+ {'🎤 Good Audio Quality' if quality_score >= 70 else '📢 Fair Audio Quality' if quality_score >= 40 else '🔇 Poor Audio Quality'}
447
+ </span>
448
+ </div>
449
+ </div>
450
+ </div>
451
+ """
452
+ display(HTML(html))
453
+
454
+ # Create probability breakdown visualization
455
+ self._plot_probabilities(results['all_probabilities'])
456
+
457
+ # Display detailed breakdown table
458
+ prob_df = pd.DataFrame([
459
+ {
460
+ 'Accent': self.accent_display_names.get(accent, accent.title()),
461
+ 'Probability': f"{prob:.1f}%",
462
+ 'Confidence': '🟢 High' if prob >= 70 else '🟡 Medium' if prob >= 30 else '🔴 Low'
463
+ }
464
+ for accent, prob in sorted(results['all_probabilities'].items(), key=lambda x: x[1], reverse=True)
465
+ ])
466
+
467
+ print("\n📊 Detailed Probability Breakdown:")
468
+ display(prob_df)
469
+
470
+ def _plot_probabilities(self, probabilities):
471
+ """Create a visualization of accent probabilities"""
472
+ try:
473
+ plt.figure(figsize=(10, 6))
474
+
475
+ accents = [self.accent_display_names.get(acc, acc.title()) for acc in probabilities.keys()]
476
+ probs = list(probabilities.values())
477
+
478
+ # Create color map
479
+ colors = ['#4CAF50' if p == max(probs) else '#2196F3' if p >= 20 else '#FFC107' if p >= 10 else '#9E9E9E'
480
+ for p in probs]
481
+
482
+ bars = plt.bar(accents, probs, color=colors, alpha=0.8, edgecolor='black', linewidth=0.5)
483
+
484
+ plt.title('Accent Classification Probabilities', fontsize=16, fontweight='bold', pad=20)
485
+ plt.xlabel('Accent Type', fontsize=12)
486
+ plt.ylabel('Probability (%)', fontsize=12)
487
+ plt.xticks(rotation=45, ha='right')
488
+ plt.grid(axis='y', alpha=0.3)
489
+
490
+ # Add value labels on bars
491
+ for bar, prob in zip(bars, probs):
492
+ height = bar.get_height()
493
+ plt.text(bar.get_x() + bar.get_width() / 2., height + 0.5,
494
+ f'{prob:.1f}%', ha='center', va='bottom', fontweight='bold')
495
+
496
+ plt.tight_layout()
497
+ plt.show()
498
+
499
+ except Exception as e:
500
+ print(f"⚠️ Could not create visualization: {e}")
501
+
502
+ def batch_analyze(self, urls, max_duration=30):
503
+ """Analyze multiple videos with progress tracking"""
504
+ results = []
505
+ failed_count = 0
506
+
507
+ print(f"🚀 Starting batch analysis of {len(urls)} videos")
508
+
509
+ for i, url in enumerate(urls, 1):
510
+ print(f"\n{'=' * 60}")
511
+ print(f"Processing video {i}/{len(urls)}")
512
+
513
+ result = self.analyze_video_url(url, max_duration)
514
+ result['video_index'] = i
515
+
516
+ if 'error' in result:
517
+ failed_count += 1
518
+ print(f"❌ Failed: {result['error']}")
519
+ else:
520
+ print(f"✅ Success: {result['predicted_accent']} ({result['accent_confidence']:.1f}%)")
521
+
522
+ results.append(result)
523
+ self.display_results(result)
524
+
525
+ # Small delay to prevent overwhelming servers
526
+ if i < len(urls):
527
+ time.sleep(1)
528
+
529
+ # Summary
530
+ success_count = len(urls) - failed_count
531
+ print(f"\n📈 Batch Analysis Summary:")
532
+ print(f" ✅ Successful: {success_count}/{len(urls)}")
533
+ print(f" ❌ Failed: {failed_count}/{len(urls)}")
534
+
535
+ return results
536
+
537
+ def export_results(self, results, filename="accent_analysis_results.json"):
538
+ """Export results to JSON file"""
539
+ try:
540
+ with open(filename, 'w') as f:
541
+ json.dump(results, f, indent=2, default=str)
542
+ print(f"💾 Results exported to {filename}")
543
+ except Exception as e:
544
+ print(f"❌ Export failed: {e}")
545
+
546
+ def cleanup(self):
547
+ """Clean up temporary files"""
548
+ try:
549
+ import shutil
550
+ if os.path.exists(self.temp_dir):
551
+ shutil.rmtree(self.temp_dir, ignore_errors=True)
552
+ print("🧹 Cleaned up temporary files")
553
+ except Exception as e:
554
+ print(f"⚠️ Cleanup warning: {e}")
555
+
556
+
557
+ # Helper Functions
558
+ def show_examples():
559
+ """Show usage examples"""
560
+ examples = {
561
+ "YouTube": "https://youtube.com/watch?v=abc123",
562
+ "Loom": "https://www.loom.com/share/abc123def456",
563
+ "Direct MP4": "https://example.com/video.mp4",
564
+ "Local File": "/kaggle/input/dataset/video.mp4"
565
+ }
566
+
567
+ print("\n🎯 Supported Video Formats:")
568
+ for platform, example in examples.items():
569
+ print(f" {platform:12}: {example}")
570
+
571
+ print("\n💡 Usage Tips:")
572
+ print(" • Keep videos under 2 minutes for best results")
573
+ print(" • Ensure clear audio quality")
574
+ print(" • Multiple speakers may affect accuracy")
575
+ print(" • Model works best with sustained speech")
576
+
577
+
578
+ def quick_test_local():
579
+ """Interactive test for local files"""
580
+ print("🔍 Quick Test Mode for Local Files")
581
+ print("📁 Common Kaggle input paths:")
582
+ print(" /kaggle/input/your-dataset/video.mp4")
583
+ print(" /kaggle/input/video-files/sample.mp4")
584
+
585
+ file_path = input("\n📎 Enter full path to your local video: ").strip()
586
+ if not file_path:
587
+ print("❌ No path provided.")
588
+ return None
589
+
590
+ if not os.path.exists(file_path):
591
+ print(f"❌ File not found: {file_path}")
592
+ return None
593
+
594
+ analyzer = VideoAccentAnalyzer()
595
+ try:
596
+ results = analyzer.analyze_local_video(file_path)
597
+ analyzer.display_results(results)
598
+ return results
599
+ finally:
600
+ analyzer.cleanup()
601
+
602
+
603
+ def demo_analysis():
604
+ """Demo function with example usage"""
605
+ print("🎬 Video Accent Analyzer Demo")
606
+ print("=" * 50)
607
+
608
+ # Initialize analyzer
609
+ analyzer = VideoAccentAnalyzer()
610
+
611
+ # Example analysis (replace with actual video URL)
612
+ example_url = "https://example.com/video.mp4" # Replace with real URL
613
+ print(f"\n🎯 Example: Analyzing {example_url}")
614
+
615
+ # Uncomment to run actual analysis
616
+ # results = analyzer.analyze_video_url(example_url, max_duration=30)
617
+ # analyzer.display_results(results)
618
+ # analyzer.cleanup()
619
+
620
+ print("\n📚 To use the analyzer:")
621
+ print("1. analyzer = VideoAccentAnalyzer()")
622
+ print("2. results = analyzer.analyze_video_url('your-url', max_duration=30)")
623
+ print("3. analyzer.display_results(results)")
624
+ print("4. analyzer.cleanup() # Clean up temporary files")
625
+
626
+
627
+ # Show examples on import
628
+ show_examples()