amirjamali commited on
Commit
aca25cc
·
unverified ·
1 Parent(s): 55dbd8d

Add Dockerfile and Streamlit configuration; implement video download with alternative YouTube sources, enhance error handling, and improve file upload processing

Browse files
Files changed (5) hide show
  1. .vscode/tasks.json +13 -0
  2. Dockerfile +18 -18
  3. README.md +61 -2
  4. requirements.txt +4 -1
  5. src/streamlit_app.py +214 -30
.vscode/tasks.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "Build and Run Accent Detector",
6
+ "type": "shell",
7
+ "command": "docker build -t accent-detector . && docker run -p 8501:8501 --volume ${env:TEMP}/accent-detector:/app/uploads --volume ${env:TEMP}/huggingface-cache:/app/.cache/huggingface accent-detector",
8
+ "group": "build",
9
+ "problemMatcher": [],
10
+ "isBackground": false
11
+ }
12
+ ]
13
+ }
Dockerfile CHANGED
@@ -3,7 +3,13 @@ FROM python:3.9-slim
3
  # Set environment variables
4
  ENV PYTHONUNBUFFERED=1 \
5
  PYTHONDONTWRITEBYTECODE=1 \
6
- MPLCONFIGDIR=/tmp/matplotlib
 
 
 
 
 
 
7
 
8
  WORKDIR /app
9
 
@@ -18,9 +24,11 @@ RUN apt-get update && \
18
  && apt-get clean \
19
  && rm -rf /var/lib/apt/lists/*
20
 
21
- # Create necessary directories
22
- RUN mkdir -p /app/tmp_model /tmp/matplotlib /app/uploads
23
- RUN chmod -R 777 /app/uploads /app/tmp_model /tmp/matplotlib
 
 
24
 
25
  # Copy requirements first (for better caching)
26
  COPY requirements.txt .
@@ -34,20 +42,12 @@ RUN pip install --no-cache-dir --upgrade pip && \
34
  # Copy source code
35
  COPY src/ ./src/
36
 
37
- # Set up Streamlit configuration
38
- RUN mkdir -p .streamlit
39
- RUN echo "[server]" > ./.streamlit/config.toml && \
40
- echo "port = 8501" >> ./.streamlit/config.toml && \
41
- echo "address = \"0.0.0.0\"" >> ./.streamlit/config.toml && \
42
- echo "headless = true" >> ./.streamlit/config.toml && \
43
- echo "maxUploadSize = 200" >> ./.streamlit/config.toml && \
44
- echo "enableXsrfProtection = false" >> ./.streamlit/config.toml && \
45
- echo "" >> ./.streamlit/config.toml && \
46
- echo "[browser]" >> ./.streamlit/config.toml && \
47
- echo "gatherUsageStats = false" >> ./.streamlit/config.toml && \
48
- echo "" >> ./.streamlit/config.toml && \
49
- echo "[runner]" >> ./.streamlit/config.toml && \
50
- echo "fastReruns = true" >> ./.streamlit/config.toml
51
 
52
  # Expose port
53
  EXPOSE 8501
 
3
  # Set environment variables
4
  ENV PYTHONUNBUFFERED=1 \
5
  PYTHONDONTWRITEBYTECODE=1 \
6
+ MPLCONFIGDIR=/tmp/matplotlib \
7
+ TRANSFORMERS_CACHE=/app/.cache/huggingface \
8
+ HF_HOME=/app/.cache/huggingface \
9
+ XDG_CACHE_HOME=/app/.cache \
10
+ PYTHONIOENCODING=utf-8 \
11
+ TOKENIZERS_PARALLELISM=false \
12
+ HF_HUB_DISABLE_SYMLINKS_WARNING=1
13
 
14
  WORKDIR /app
15
 
 
24
  && apt-get clean \
25
  && rm -rf /var/lib/apt/lists/*
26
 
27
+ # Create necessary directories with proper permissions
28
+ RUN mkdir -p /app/tmp_model /tmp/matplotlib /app/uploads /app/.cache/huggingface /app/.streamlit /app/.config /root/.cache/huggingface
29
+ RUN chmod -R 777 /app/uploads /app/tmp_model /tmp/matplotlib /app/.cache /app/.streamlit /app/.config /root/.cache
30
+ # Create symbolic link to ensure both user and root can access cache
31
+ RUN ln -sf /app/.cache/huggingface /root/.cache/huggingface
32
 
33
  # Copy requirements first (for better caching)
34
  COPY requirements.txt .
 
42
  # Copy source code
43
  COPY src/ ./src/
44
 
45
+ # Copy the Streamlit directory with configuration
46
+ COPY .streamlit/ ./.streamlit/
47
+
48
+ # Set proper permissions on the Streamlit configuration
49
+ RUN chmod -R 755 ./.streamlit && \
50
+ chmod 644 ./.streamlit/config.toml
 
 
 
 
 
 
 
 
51
 
52
  # Expose port
53
  EXPOSE 8501
README.md CHANGED
@@ -42,10 +42,16 @@ This app analyzes a speaker's English accent from video URLs or audio uploads, p
42
 
43
  If you encounter errors like `Sign in to confirm you're not a bot` when using YouTube videos:
44
 
45
- 1. **Use a different video source**:
46
  - Try using Loom, Vimeo, or direct MP4 links instead of YouTube
 
47
 
48
- 2. **Using cookies for YouTube authentication**:
 
 
 
 
 
49
  - The app supports uploading a cookies.txt file for YouTube authentication
50
  - You can export cookies from your browser using browser extensions like "Get cookies.txt"
51
  - Or use yt-dlp's built-in browser cookie extraction:
@@ -58,6 +64,13 @@ If you encounter errors like `Sign in to confirm you're not a bot` when using Yo
58
  ```
59
  - Upload the generated cookies file in the app interface
60
 
 
 
 
 
 
 
 
61
  ## Technology Stack
62
  - **Audio Processing**: FFmpeg, Librosa
63
  - **ML Models**: SpeechBrain, Transformers
@@ -92,6 +105,52 @@ If you encounter 403 Forbidden errors when uploading files:
92
  3. For longer files, consider extracting just the speech segment
93
  4. If uploading an MP4 video, ensure it's not encrypted or DRM-protected
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  ## Powered By
96
  - [SpeechBrain](https://huggingface.co/speechbrain/lang-id-commonlanguage_ecapa)
97
  - [Hugging Face Transformers](https://huggingface.co/speechbrain/lang-id-voxlingua107-ecapa)
 
42
 
43
  If you encounter errors like `Sign in to confirm you're not a bot` when using YouTube videos:
44
 
45
+ 1. **Use a different video source** (RECOMMENDED):
46
  - Try using Loom, Vimeo, or direct MP4 links instead of YouTube
47
+ - These sources are more reliable and have fewer authentication requirements
48
 
49
+ 2. **Use alternative YouTube frontends**:
50
+ - The app now automatically tries alternative YouTube frontends (Invidious, Piped)
51
+ - Make sure the "Try alternative YouTube source" checkbox is checked
52
+ - These frontend services can bypass some YouTube restrictions
53
+
54
+ 3. **Using cookies for YouTube authentication**:
55
  - The app supports uploading a cookies.txt file for YouTube authentication
56
  - You can export cookies from your browser using browser extensions like "Get cookies.txt"
57
  - Or use yt-dlp's built-in browser cookie extraction:
 
64
  ```
65
  - Upload the generated cookies file in the app interface
66
 
67
+ 4. **Update yt-dlp for better YouTube support**:
68
+ - If running outside Docker, update yt-dlp to the latest version:
69
+ ```
70
+ pip install -U yt-dlp
71
+ ```
72
+ - YouTube frequently updates its anti-bot measures, and newer yt-dlp versions often have fixes
73
+
74
  ## Technology Stack
75
  - **Audio Processing**: FFmpeg, Librosa
76
  - **ML Models**: SpeechBrain, Transformers
 
105
  3. For longer files, consider extracting just the speech segment
106
  4. If uploading an MP4 video, ensure it's not encrypted or DRM-protected
107
 
108
+ ### Troubleshooting Hugging Face Cache Issues
109
+
110
+ If you encounter errors related to Hugging Face model loading or cache permissions:
111
+
112
+ 1. **Ensure proper volume mounts and permissions**:
113
+ ```bash
114
+ # Create local directories with proper permissions
115
+ mkdir -p /tmp/accent-detector /tmp/huggingface-cache
116
+ chmod 777 /tmp/accent-detector /tmp/huggingface-cache
117
+
118
+ # Run with multiple volume mounts for both uploads and cache
119
+ docker run -p 8501:8501 \
120
+ --volume /tmp/accent-detector:/app/uploads \
121
+ --volume /tmp/huggingface-cache:/app/.cache/huggingface \
122
+ accent-detector
123
+ ```
124
+
125
+ On Windows:
126
+ ```powershell
127
+ # Create directories
128
+ mkdir -Force C:\temp\accent-detector
129
+ mkdir -Force C:\temp\huggingface-cache
130
+
131
+ # Run with volume mounts
132
+ docker run -p 8501:8501 `
133
+ --volume C:\temp\accent-detector:/app/uploads `
134
+ --volume C:\temp\huggingface-cache:/app/.cache/huggingface `
135
+ accent-detector
136
+ ```
137
+
138
+ 2. **Reset the container and cache**:
139
+ ```bash
140
+ # Stop any running containers
141
+ docker stop $(docker ps -q --filter ancestor=accent-detector)
142
+
143
+ # Remove the container
144
+ docker rm $(docker ps -a -q --filter ancestor=accent-detector)
145
+
146
+ # Optionally clear the cache directories
147
+ rm -rf /tmp/accent-detector/* /tmp/huggingface-cache/*
148
+
149
+ # Rebuild and run
150
+ docker build --no-cache -t accent-detector .
151
+ docker run -p 8501:8501 --volume /tmp/accent-detector:/app/uploads --volume /tmp/huggingface-cache:/app/.cache/huggingface accent-detector
152
+ ```
153
+
154
  ## Powered By
155
  - [SpeechBrain](https://huggingface.co/speechbrain/lang-id-commonlanguage_ecapa)
156
  - [Hugging Face Transformers](https://huggingface.co/speechbrain/lang-id-voxlingua107-ecapa)
requirements.txt CHANGED
@@ -1,5 +1,8 @@
1
  streamlit==1.31.0
2
- yt_dlp==2023.11.16
 
 
 
3
  # Use a specific stable version of SpeechBrain
4
  speechbrain==0.5.14
5
  torch==2.0.1
 
1
  streamlit==1.31.0
2
+ # Updated yt-dlp for better YouTube support
3
+ yt_dlp==2024.03.10
4
+ requests==2.32.0
5
+ beautifulsoup4==4.12.2
6
  # Use a specific stable version of SpeechBrain
7
  speechbrain==0.5.14
8
  torch==2.0.1
src/streamlit_app.py CHANGED
@@ -91,43 +91,183 @@ ENGLISH_ACCENTS = {
91
 
92
  def download_video(url, video_path="video.mp4", cookies_file=None):
93
  """Download a video from a URL"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  ydl_opts = {
95
  "outtmpl": video_path,
96
  "quiet": False,
97
- "no_warnings": False,
98
- "verbose": True # More detailed output for debugging
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
- # Only use cookies if explicitly provided via file upload
102
- # Don't try to access browser cookies in Docker container
103
  if cookies_file and os.path.exists(cookies_file):
104
  ydl_opts["cookiefile"] = cookies_file
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  try:
107
- # Special handling for YouTube URLs to try without cookies first
108
- is_youtube = "youtube" in url.lower() or "youtu.be" in url.lower()
109
-
110
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
111
- ydl.download([url])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
 
 
 
 
 
 
 
 
 
 
 
113
  if os.path.exists(video_path):
114
  return True
115
  else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  st.error(f"Video downloaded but file not found: {video_path}")
117
  return False
 
118
  except Exception as e:
119
  error_msg = str(e)
120
  st.error(f"Download error: {error_msg}")
121
 
122
  # Provide specific guidance based on error type
123
- if is_youtube and ("bot" in error_msg.lower() or "sign in" in error_msg.lower()):
124
- st.warning("YouTube requires authentication. Please upload a cookies.txt file or try a direct video link.")
 
 
 
 
 
125
  elif "not find" in error_msg.lower() and "cookies" in error_msg.lower():
126
  st.warning("Browser cookies could not be accessed. Please upload a cookies.txt file.")
127
  elif "network" in error_msg.lower() or "timeout" in error_msg.lower():
128
  st.warning("Network error. Please check your internet connection and try again.")
 
 
 
 
 
 
129
 
130
  return False
 
 
 
 
 
 
 
 
 
131
 
132
  def extract_audio(video_path="video.mp4", audio_path="audio.wav"):
133
  """Extract audio from video file using ffmpeg"""
@@ -334,15 +474,18 @@ def process_uploaded_audio(uploaded_file):
334
  timestamp = str(int(time.time()))
335
  file_extension = os.path.splitext(uploaded_file.name)[1].lower()
336
 
337
- # Write the uploaded file to disk with proper extension
338
- temp_input_path = f"uploaded_audio_{timestamp}{file_extension}"
 
 
 
 
339
  with open(temp_input_path, "wb") as f:
340
  f.write(uploaded_file.getbuffer())
341
-
342
- # For MP4 files, extract the audio using ffmpeg
343
  if file_extension == ".mp4":
344
  st.info("Extracting audio from video file...")
345
- audio_path = f"extracted_audio_{timestamp}.wav"
346
  try:
347
  subprocess.run(
348
  ['ffmpeg', '-i', temp_input_path, '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', audio_path],
@@ -356,8 +499,23 @@ def process_uploaded_audio(uploaded_file):
356
  st.error(f"ffmpeg output: {e.stderr.decode('utf-8')}")
357
  raise
358
  else:
359
- # For audio files, use them directly
360
- audio_path = temp_input_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
  detector = AccentDetector()
363
  results = detector.analyze_audio(audio_path)
@@ -415,6 +573,11 @@ with tab1:
415
  url = st.text_input("Enter a public video URL",
416
  placeholder="https://www.loom.com/..., https://vimeo.com/..., or direct MP4 link")
417
 
 
 
 
 
 
418
  # Recommend alternative sources
419
  st.caption("⚠️ **Note**: YouTube videos often require authentication. For best results, use Loom, Vimeo or direct video links.")
420
 
@@ -533,17 +696,16 @@ with tab2:
533
  help="Support for WAV, MP3, M4A, OGG, FLAC and MP4 formats",
534
  accept_multiple_files=False)
535
 
536
- if uploaded_file is not None:
537
- # Show a preview of the audio
538
  st.markdown("#### Audio Preview:")
539
  st.audio(uploaded_file)
540
-
541
  st.markdown("#### Ready for Analysis")
542
  col1, col2 = st.columns([1, 3])
543
  with col1:
544
  analyze_button = st.button("Analyze Audio", type="primary", use_container_width=True)
545
  with col2:
546
  st.caption("Tip: 15-30 seconds of clear speech works best for accent detection")
 
547
  if analyze_button:
548
  with st.spinner("Analyzing audio... (this may take 15-30 seconds)"):
549
  try:
@@ -553,14 +715,37 @@ with tab2:
553
  st.error(f"File size ({file_size_mb:.1f}MB) is too large. Maximum allowed is 190MB.")
554
  st.info("Tip: Try trimming your audio to just the speech segment for better results.")
555
  else:
 
 
 
556
  # Check the file type and inform user about processing steps
557
  file_extension = os.path.splitext(uploaded_file.name)[1].lower()
558
  if file_extension == '.mp4':
559
  st.info("Processing video file - extracting audio track...")
 
 
 
 
560
 
561
- # Process the file
562
- results = process_uploaded_audio(uploaded_file)
 
 
563
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  # Display results
565
  st.success("✅ Analysis Complete!")
566
 
@@ -579,20 +764,19 @@ with tab2:
579
  with col2:
580
  if results['audio_viz']:
581
  st.pyplot(results['audio_viz'])
582
-
583
- except subprocess.CalledProcessError as e:
584
  st.error("Error processing audio file")
585
  st.error(f"FFmpeg error: {e.stderr.decode('utf-8') if e.stderr else str(e)}")
586
  st.info("Troubleshooting tips:\n"
587
- "• Try a different audio file format (WAV or MP3 recommended)\n"
588
- "• Make sure the file is not corrupted\n"
589
- "• Try a shorter audio clip")
590
 
591
  except PermissionError as e:
592
  st.error(f"Permission error: {str(e)}")
593
  st.info("The app doesn't have permission to access or create temporary files. "
594
- "This could be due to Docker container permissions. "
595
- "Contact the administrator or try using a different file.")
596
 
597
  except OSError as e:
598
  st.error(f"System error: {str(e)}")
 
91
 
92
  def download_video(url, video_path="video.mp4", cookies_file=None):
93
  """Download a video from a URL"""
94
+
95
+ # Determine if this is a YouTube URL
96
+ is_youtube = "youtube" in url.lower() or "youtu.be" in url.lower()
97
+
98
+ # Create a unique directory for each download to avoid permission issues
99
+ timestamp = str(int(time.time()))
100
+
101
+ # Use proper temp directory for Windows or Linux
102
+ if os.name == 'nt': # Windows
103
+ temp_dir = os.path.join(os.environ.get('TEMP', 'C:\\temp'), f"video_download_{timestamp}")
104
+ else: # Linux/Mac
105
+ temp_dir = f"/tmp/video_download_{timestamp}"
106
+
107
+ os.makedirs(temp_dir, exist_ok=True)
108
+
109
+ # Set correct permissions for the temp directory
110
+ try:
111
+ os.chmod(temp_dir, 0o777) # Full permissions for all users
112
+ except Exception as e:
113
+ st.warning(f"Could not set directory permissions: {str(e)}. Continuing anyway.")
114
+
115
+ # Use the temp directory for the video path
116
+ if not os.path.isabs(video_path):
117
+ video_path = os.path.join(temp_dir, video_path)
118
+
119
  ydl_opts = {
120
  "outtmpl": video_path,
121
  "quiet": False,
122
+ "verbose": True, # More detailed output for debugging
123
+ "format": "bestaudio/best", # Prefer audio formats since we only need audio
124
+ "postprocessors": [{
125
+ "key": "FFmpegExtractAudio",
126
+ "preferredcodec": "wav",
127
+ }] if is_youtube else [], # Extract audio directly for YouTube
128
+ "noplaylist": True,
129
+ "extractor_retries": 5, # Increased from 3 to 5
130
+ "socket_timeout": 45, # Increased from 30 to 45
131
+ "retry_sleep_functions": {
132
+ "http": lambda n: 5 * (n + 1), # 5, 10, 15, 20, 25 seconds
133
+ },
134
+ "nocheckcertificate": True, # Skip HTTPS certificate validation
135
+ "ignoreerrors": False, # Don't ignore errors (we want to handle them)
136
  }
137
 
138
+ # Add cookies if provided
 
139
  if cookies_file and os.path.exists(cookies_file):
140
  ydl_opts["cookiefile"] = cookies_file
141
+ st.info("Using provided cookies file for authentication")
142
+
143
+ # Set permissions on cookies file to make sure it's readable
144
+ try:
145
+ os.chmod(cookies_file, 0o644) # Read-write for owner, read-only for others
146
+ except Exception as e:
147
+ st.warning(f"Could not set permissions on cookies file: {str(e)}. Continuing anyway.")
148
+
149
+ # Setup environment variables for cache directories
150
+ os.environ['HOME'] = temp_dir # Set HOME to our temp dir for YouTube-DL cache
151
+ os.environ['XDG_CACHE_HOME'] = os.path.join(temp_dir, '.cache') # For Linux
152
+ os.environ['APPDATA'] = temp_dir # For Windows
153
 
154
  try:
155
+ if is_youtube:
156
+ st.info("Attempting to download from YouTube. This might take longer...")
157
+
158
+ # List of alternative YouTube frontends to try
159
+ youtube_alternatives = [
160
+ (url, "Standard YouTube"),
161
+ (url.replace("youtube.com", "yewtu.be"), "Invidious (yewtu.be)"),
162
+ (url.replace("youtube.com", "piped.video"), "Piped"),
163
+ (url.replace("youtube.com", "inv.riverside.rocks"), "Invidious (riverside)")
164
+ ]
165
+
166
+ # If youtu.be is used, create proper alternatives
167
+ if "youtu.be" in url.lower():
168
+ video_id = url.split("/")[-1].split("?")[0]
169
+ youtube_alternatives = [
170
+ (url, "Standard YouTube"),
171
+ (f"https://yewtu.be/watch?v={video_id}", "Invidious (yewtu.be)"),
172
+ (f"https://piped.video/watch?v={video_id}", "Piped"),
173
+ (f"https://inv.riverside.rocks/watch?v={video_id}", "Invidious (riverside)")
174
+ ]
175
+
176
+ success = False
177
+
178
+ for alt_url, alt_name in youtube_alternatives:
179
+ if alt_url == url and alt_name != "Standard YouTube":
180
+ continue # Skip redundant first entry
181
+
182
+ st.info(f"Trying {alt_name}... Please wait.")
183
+
184
+ try:
185
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
186
+ ydl.download([alt_url])
187
+
188
+ # If we get here without exception, it worked
189
+ st.success(f"Successfully downloaded using {alt_name}")
190
+ success = True
191
+ break
192
+
193
+ except Exception as download_error:
194
+ error_msg = str(download_error)
195
+ st.warning(f"{alt_name} download attempt failed: {error_msg}")
196
+
197
+ # Break early if it's a permission issue to avoid trying alternatives
198
+ if "permission" in error_msg.lower() or "access" in error_msg.lower():
199
+ st.error("Permission error detected. Stopping download attempts.")
200
+ raise download_error
201
 
202
+ # If all attempts failed
203
+ if not success:
204
+ st.error("All YouTube download methods failed.")
205
+ return False
206
+
207
+ else:
208
+ # For non-YouTube URLs
209
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
210
+ ydl.download([url])
211
+
212
+ # Check if download was successful
213
  if os.path.exists(video_path):
214
  return True
215
  else:
216
+ # Look for any downloaded files in the temp directory - more comprehensive search
217
+ downloaded_files = []
218
+ for root, _, files in os.walk(temp_dir):
219
+ for file in files:
220
+ if file.endswith(('.mp4', '.mp3', '.wav', '.m4a')):
221
+ downloaded_files.append(os.path.join(root, file))
222
+
223
+ if downloaded_files:
224
+ # Use the first media file found
225
+ first_file = downloaded_files[0]
226
+ try:
227
+ # Copy instead of move to avoid cross-device link issues
228
+ import shutil
229
+ shutil.copy(first_file, video_path)
230
+ return True
231
+ except Exception as copy_error:
232
+ st.error(f"Error copying downloaded file: {str(copy_error)}")
233
+ return False
234
+
235
  st.error(f"Video downloaded but file not found: {video_path}")
236
  return False
237
+
238
  except Exception as e:
239
  error_msg = str(e)
240
  st.error(f"Download error: {error_msg}")
241
 
242
  # Provide specific guidance based on error type
243
+ if is_youtube and ("bot" in error_msg.lower() or "sign in" in error_msg.lower() or "403" in error_msg):
244
+ st.warning("⚠️ YouTube requires authentication. Please try one of these solutions:")
245
+ st.markdown("""
246
+ 1. **Upload a cookies.txt file** using the file uploader above
247
+ 2. **Try a different video source** like Loom, Vimeo or direct MP3/WAV files
248
+ 3. **Use the Audio Upload tab** instead of YouTube URLs
249
+ """)
250
  elif "not find" in error_msg.lower() and "cookies" in error_msg.lower():
251
  st.warning("Browser cookies could not be accessed. Please upload a cookies.txt file.")
252
  elif "network" in error_msg.lower() or "timeout" in error_msg.lower():
253
  st.warning("Network error. Please check your internet connection and try again.")
254
+ elif "permission" in error_msg.lower():
255
+ st.warning("Permission error. The application doesn't have access to create or write files in the temporary directory.")
256
+ st.info("Try running the Docker container with the proper volume mounts: `docker run -p 8501:8501 --volume /tmp/accent-detector:/app/uploads accent-detector`")
257
+ elif "not found" in error_msg.lower() and "ffmpeg" in error_msg.lower():
258
+ st.error("FFmpeg is not installed or not found in PATH.")
259
+ st.info("If running locally, please install FFmpeg. If using Docker, the container may be misconfigured.")
260
 
261
  return False
262
+ finally:
263
+ # Clean up temp directory if it still exists
264
+ try:
265
+ if os.path.exists(temp_dir) and ("tmp" in temp_dir or "temp" in temp_dir.lower()):
266
+ import shutil
267
+ shutil.rmtree(temp_dir)
268
+ except Exception as cleanup_error:
269
+ st.warning(f"Could not clean up temporary directory: {str(cleanup_error)}")
270
+ pass
271
 
272
  def extract_audio(video_path="video.mp4", audio_path="audio.wav"):
273
  """Extract audio from video file using ffmpeg"""
 
474
  timestamp = str(int(time.time()))
475
  file_extension = os.path.splitext(uploaded_file.name)[1].lower()
476
 
477
+ # Create an uploads directory if it doesn't exist
478
+ uploads_dir = os.path.join(os.getcwd(), "uploads")
479
+ os.makedirs(uploads_dir, exist_ok=True)
480
+
481
+ # Write the uploaded file to disk with proper extension in the uploads directory
482
+ temp_input_path = os.path.join(uploads_dir, f"uploaded_audio_{timestamp}{file_extension}")
483
  with open(temp_input_path, "wb") as f:
484
  f.write(uploaded_file.getbuffer())
485
+ # For MP4 files, extract the audio using ffmpeg
 
486
  if file_extension == ".mp4":
487
  st.info("Extracting audio from video file...")
488
+ audio_path = os.path.join(uploads_dir, f"extracted_audio_{timestamp}.wav")
489
  try:
490
  subprocess.run(
491
  ['ffmpeg', '-i', temp_input_path, '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', audio_path],
 
499
  st.error(f"ffmpeg output: {e.stderr.decode('utf-8')}")
500
  raise
501
  else:
502
+ # For audio files, process based on format
503
+ if file_extension in [".mp3", ".m4a", ".ogg", ".flac"]:
504
+ # Convert to WAV for better compatibility
505
+ audio_path = os.path.join(uploads_dir, f"converted_audio_{timestamp}.wav")
506
+ try:
507
+ subprocess.run(
508
+ ['ffmpeg', '-i', temp_input_path, '-ar', '16000', '-ac', '1', '-c:a', 'pcm_s16le', audio_path],
509
+ check=True,
510
+ capture_output=True
511
+ )
512
+ # Keep original file for reference but continue with WAV
513
+ except subprocess.CalledProcessError as e:
514
+ st.warning(f"Conversion warning: {e}. Using original file.")
515
+ audio_path = temp_input_path
516
+ else:
517
+ # For already WAV files, use them directly
518
+ audio_path = temp_input_path
519
 
520
  detector = AccentDetector()
521
  results = detector.analyze_audio(audio_path)
 
573
  url = st.text_input("Enter a public video URL",
574
  placeholder="https://www.loom.com/..., https://vimeo.com/..., or direct MP4 link")
575
 
576
+ # Add alternative invidious frontend option for YouTube
577
+ use_alternative = st.checkbox("Try alternative YouTube source (for authentication issues)",
578
+ value=True,
579
+ help="Uses an alternative frontend (Invidious) that may bypass YouTube restrictions")
580
+
581
  # Recommend alternative sources
582
  st.caption("⚠️ **Note**: YouTube videos often require authentication. For best results, use Loom, Vimeo or direct video links.")
583
 
 
696
  help="Support for WAV, MP3, M4A, OGG, FLAC and MP4 formats",
697
  accept_multiple_files=False)
698
 
699
+ if uploaded_file is not None: # Show a preview of the audio
 
700
  st.markdown("#### Audio Preview:")
701
  st.audio(uploaded_file)
 
702
  st.markdown("#### Ready for Analysis")
703
  col1, col2 = st.columns([1, 3])
704
  with col1:
705
  analyze_button = st.button("Analyze Audio", type="primary", use_container_width=True)
706
  with col2:
707
  st.caption("Tip: 15-30 seconds of clear speech works best for accent detection")
708
+
709
  if analyze_button:
710
  with st.spinner("Analyzing audio... (this may take 15-30 seconds)"):
711
  try:
 
715
  st.error(f"File size ({file_size_mb:.1f}MB) is too large. Maximum allowed is 190MB.")
716
  st.info("Tip: Try trimming your audio to just the speech segment for better results.")
717
  else:
718
+ # Create a progress bar to show processing stages
719
+ progress_bar = st.progress(0)
720
+
721
  # Check the file type and inform user about processing steps
722
  file_extension = os.path.splitext(uploaded_file.name)[1].lower()
723
  if file_extension == '.mp4':
724
  st.info("Processing video file - extracting audio track...")
725
+ elif file_extension in ['.mp3', '.m4a', '.ogg', '.flac']:
726
+ st.info(f"Processing {file_extension} audio file...")
727
+
728
+ progress_bar.progress(25, text="Saving file...")
729
 
730
+ # First save the file to a known location to bypass 403 errors
731
+ # Create an uploads directory if it doesn't exist
732
+ uploads_dir = os.path.join(os.getcwd(), "uploads")
733
+ os.makedirs(uploads_dir, exist_ok=True)
734
 
735
+ # Save the file first to avoid streaming it multiple times
736
+ temp_file_path = os.path.join(uploads_dir, f"temp_{int(time.time())}_{uploaded_file.name}")
737
+ with open(temp_file_path, "wb") as f:
738
+ f.write(uploaded_file.getbuffer())
739
+
740
+ progress_bar.progress(50, text="Analyzing audio...")
741
+
742
+ # Now process from the saved file
743
+ with open(temp_file_path, "rb") as f:
744
+ # Create a new UploadedFile object from the saved file
745
+ file_content = f.read()
746
+ results = process_uploaded_audio(uploaded_file)
747
+
748
+ progress_bar.progress(100, text="Analysis complete!")
749
  # Display results
750
  st.success("✅ Analysis Complete!")
751
 
 
764
  with col2:
765
  if results['audio_viz']:
766
  st.pyplot(results['audio_viz'])
767
+ except subprocess.CalledProcessError as e:
 
768
  st.error("Error processing audio file")
769
  st.error(f"FFmpeg error: {e.stderr.decode('utf-8') if e.stderr else str(e)}")
770
  st.info("Troubleshooting tips:\n"
771
+ "• Try a different audio file format (WAV or MP3 recommended)\n"
772
+ "• Make sure the file is not corrupted\n"
773
+ "• Try a shorter audio clip")
774
 
775
  except PermissionError as e:
776
  st.error(f"Permission error: {str(e)}")
777
  st.info("The app doesn't have permission to access or create temporary files. "
778
+ "This could be due to Docker container permissions. "
779
+ "Contact the administrator or try using a different file.")
780
 
781
  except OSError as e:
782
  st.error(f"System error: {str(e)}")