Omar-youssef commited on
Commit
9c75488
·
1 Parent(s): 0343553
Files changed (4) hide show
  1. app.py +31 -8
  2. link_processor.py +62 -25
  3. requirements.txt +1 -1
  4. video_processor.py +37 -23
app.py CHANGED
@@ -133,33 +133,56 @@ def process_url(url):
133
  video_path = downloader.download_from_url(url)
134
 
135
  if not video_path:
136
- raise RuntimeError("Video download failed")
137
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  st.info("Processing video...")
139
  process_dir = Path(temp_dir) / "output"
140
  process_dir.mkdir(exist_ok=True)
141
 
142
  video_proc = VideoProcessor(video_path, process_dir)
143
  audio_path = video_proc.extract_audio()
 
144
  if not audio_path:
145
- raise RuntimeError("Audio extraction failed")
 
146
 
147
  st.info("Separating vocals...")
148
  audio_proc = AudioProcessor(audio_path, process_dir)
149
  if not audio_proc.run_demucs():
150
- raise RuntimeError("Demucs processing failed")
 
151
 
152
  vocals_path = audio_proc.get_vocals_path()
153
  no_vocals_path = audio_proc.get_no_vocals_path()
154
 
155
  if not vocals_path or not no_vocals_path:
156
- raise FileNotFoundError("Processed audio files not found")
 
157
 
158
  st.info("Creating final video...")
159
  final_video_path = video_proc.combine_video_audio(vocals_path)
160
 
161
  if not final_video_path:
162
- raise RuntimeError("Final video creation failed")
 
163
 
164
  col1, col2 = st.columns(2)
165
  if final_video_path:
@@ -185,8 +208,8 @@ def process_url(url):
185
 
186
  except Exception as e:
187
  st.error(f"An error occurred: {str(e)}")
188
- reset_processing_state()
189
- raise
190
 
191
 
192
  # Page config
 
133
  video_path = downloader.download_from_url(url)
134
 
135
  if not video_path:
136
+ st.error("Video download failed")
137
+ return
138
+
139
+ # Add extensive logging and checks
140
+ #st.write(f"Downloaded video path: {video_path}")
141
+ #st.write(f"Video file exists: {os.path.exists(video_path)}")
142
+
143
+ try:
144
+ video_file_size = os.path.getsize(video_path)
145
+ #
146
+ #(f"Video file size: {video_file_size} bytes")
147
+
148
+ if video_file_size == 0:
149
+ st.error("Downloaded video file is empty")
150
+ return
151
+ except Exception as size_error:
152
+ st.error(f"Error checking file size: {size_error}")
153
+ return
154
+
155
+ # Rest of the processing remains the same...
156
  st.info("Processing video...")
157
  process_dir = Path(temp_dir) / "output"
158
  process_dir.mkdir(exist_ok=True)
159
 
160
  video_proc = VideoProcessor(video_path, process_dir)
161
  audio_path = video_proc.extract_audio()
162
+
163
  if not audio_path:
164
+ st.error("Audio extraction failed")
165
+ return
166
 
167
  st.info("Separating vocals...")
168
  audio_proc = AudioProcessor(audio_path, process_dir)
169
  if not audio_proc.run_demucs():
170
+ st.error("Demucs processing failed")
171
+ return
172
 
173
  vocals_path = audio_proc.get_vocals_path()
174
  no_vocals_path = audio_proc.get_no_vocals_path()
175
 
176
  if not vocals_path or not no_vocals_path:
177
+ st.error("Processed audio files not found")
178
+ return
179
 
180
  st.info("Creating final video...")
181
  final_video_path = video_proc.combine_video_audio(vocals_path)
182
 
183
  if not final_video_path:
184
+ st.error("Final video creation failed")
185
+ return
186
 
187
  col1, col2 = st.columns(2)
188
  if final_video_path:
 
208
 
209
  except Exception as e:
210
  st.error(f"An error occurred: {str(e)}")
211
+ import traceback
212
+ traceback.print_exc() # This will print the full traceback
213
 
214
 
215
  # Page config
link_processor.py CHANGED
@@ -2,46 +2,83 @@ from pathlib import Path
2
  import yt_dlp
3
  import os
4
  from concurrent.futures import ThreadPoolExecutor
5
- import streamlit as st
 
6
 
7
 
8
  class LinkDownloader:
9
- def __init__(self, output_dir):
10
  self.output_dir = Path(output_dir)
11
  self.output_dir.mkdir(parents=True, exist_ok=True)
12
- self.executor = ThreadPoolExecutor(max_workers=2)
 
 
 
 
 
 
 
13
 
14
  def download_from_url(self, url):
15
- try:
16
- temp_filepath = str(self.output_dir / 'downloaded_video.%(ext)s')
17
-
18
- ydl_opts = {
19
- 'format': 'best[height<=1080]',
20
- 'outtmpl': temp_filepath,
21
- 'quiet': True,
22
- 'no_warnings': True,
23
- 'extract_audio': False,
24
- 'concurrent_fragment_downloads': 3,
25
- 'file_access_retries': 3,
26
- 'retries': 3,
27
- 'socket_timeout': 30,
28
- 'nocheckcertificate': True,
29
- }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
 
32
  info = ydl.extract_info(url, download=False)
33
  ext = info.get('ext', 'mp4')
34
 
35
- # Download in a separate thread
36
  future = self.executor.submit(ydl.download, [url])
37
- future.result()
38
 
39
- actual_filepath = str(self.output_dir / f"downloaded_video.{ext}")
40
- return actual_filepath if os.path.exists(actual_filepath) else None
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  except Exception as e:
43
- print(f"Download error: {e}")
44
- return None
 
45
 
46
  def __del__(self):
47
- self.executor.shutdown(wait=False)
 
 
 
2
  import yt_dlp
3
  import os
4
  from concurrent.futures import ThreadPoolExecutor
5
+ import logging
6
+ import shutil
7
 
8
 
9
  class LinkDownloader:
10
+ def __init__(self, output_dir, max_workers=2):
11
  self.output_dir = Path(output_dir)
12
  self.output_dir.mkdir(parents=True, exist_ok=True)
13
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
14
+
15
+ # Setup logging
16
+ logging.basicConfig(
17
+ level=logging.INFO,
18
+ format='%(asctime)s - %(levelname)s - %(message)s'
19
+ )
20
+ self.logger = logging.getLogger(__name__)
21
 
22
  def download_from_url(self, url):
23
+ """Downloads a video from the given URL using yt_dlp."""
24
+ # Create a unique subdirectory to prevent file conflicts
25
+ unique_download_dir = self.output_dir / "download"
26
+ unique_download_dir.mkdir(parents=True, exist_ok=True)
27
+
28
+ temp_filepath = str(unique_download_dir / 'downloaded_video.%(ext)s')
 
 
 
 
 
 
 
 
 
29
 
30
+ ydl_opts = {
31
+ 'format': 'best[height<=1080]',
32
+ 'outtmpl': temp_filepath,
33
+ 'quiet': False,
34
+ 'no_warnings': True,
35
+ 'extract_audio': False,
36
+ 'concurrent_fragment_downloads': 3,
37
+ 'file_access_retries': 3,
38
+ 'retries': 3,
39
+ 'socket_timeout': 30,
40
+ 'nocheckcertificate': True,
41
+ }
42
+
43
+ try:
44
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
45
+ # Get video information
46
  info = ydl.extract_info(url, download=False)
47
  ext = info.get('ext', 'mp4')
48
 
49
+ # Submit download task to executor
50
  future = self.executor.submit(ydl.download, [url])
51
+ future.result() # Wait for download to complete
52
 
53
+ # Find the downloaded file
54
+ downloaded_files = list(unique_download_dir.glob(f'downloaded_video.{ext}'))
55
 
56
+ if not downloaded_files:
57
+ self.logger.error("No files found after download.")
58
+ return None
59
+
60
+ # Take the first file (should be the only one)
61
+ actual_filepath = str(downloaded_files[0])
62
+
63
+ if os.path.exists(actual_filepath):
64
+ # Copy the file to the main output directory to prevent deletion
65
+ final_filepath = str(self.output_dir / f"downloaded_video.{ext}")
66
+ shutil.copy2(actual_filepath, final_filepath)
67
+
68
+ self.logger.info(f"Download completed: {final_filepath}")
69
+ return final_filepath
70
+ else:
71
+ self.logger.error("File not found after download.")
72
+ return None
73
+
74
+ except yt_dlp.DownloadError as e:
75
+ self.logger.error(f"yt_dlp error: {e}")
76
  except Exception as e:
77
+ self.logger.error(f"Unexpected error: {e}")
78
+
79
+ return None
80
 
81
  def __del__(self):
82
+ """Ensure proper shutdown of the executor."""
83
+ self.executor.shutdown(wait=True)
84
+ self.logger.info("ThreadPoolExecutor shut down.")
requirements.txt CHANGED
@@ -7,4 +7,4 @@ demucs==4.0.1
7
  soundfile==0.12.1
8
  diffq==0.2.4
9
  ffmpeg-python==0.2.0
10
- yt-dlp==2023.10.07
 
7
  soundfile==0.12.1
8
  diffq==0.2.4
9
  ffmpeg-python==0.2.0
10
+ yt-dlp==2024.12.23
video_processor.py CHANGED
@@ -21,51 +21,65 @@ class VideoProcessor:
21
 
22
  # Print file size and check if it's not empty
23
  file_size = os.path.getsize(self.input_video)
24
- print(f"Input video file size: {file_size} bytes")
25
  if file_size == 0:
26
  st.error("Input video file is empty")
27
  return None
28
 
 
 
 
29
  # Construct FFmpeg command with detailed logging
30
  command = [
31
  "ffmpeg",
32
- "-v", "verbose", # Enable verbose logging
33
  "-i", str(self.input_video),
34
  "-vn", # No video
35
- "-acodec", "pcm_s16le",
36
- "-ar", "44100",
 
37
  "-y", # Overwrite output file if it exists
38
  str(self.temp_audio)
39
  ]
40
 
41
  # Run FFmpeg command and capture output
42
- process = subprocess.run(
43
- command,
44
- capture_output=True,
45
- text=True,
46
- check=False # Don't raise exception immediately
47
- )
48
-
49
- # Print FFmpeg output for debugging
50
- print("FFmpeg stdout:", process.stdout)
51
- print("FFmpeg stderr:", process.stderr)
52
-
53
- # Check if the command was successful
54
- if process.returncode != 0:
55
- st.error(f"FFmpeg error: {process.stderr}")
 
 
 
 
 
 
56
  return None
57
 
58
  # Verify the output audio file exists and is not empty
59
- if not self.temp_audio.exists() or os.path.getsize(self.temp_audio) == 0:
60
- st.error("Audio extraction failed: Output file is missing or empty")
61
  return None
62
 
63
- print(f"Audio extracted successfully to {self.temp_audio}")
 
 
 
 
 
64
  return str(self.temp_audio)
65
 
66
  except Exception as e:
67
- st.error(f"Error during audio extraction: {str(e)}")
68
- print(f"Exception details: {str(e)}")
69
  return None
70
 
71
  def combine_video_audio(self, vocals_path):
 
21
 
22
  # Print file size and check if it's not empty
23
  file_size = os.path.getsize(self.input_video)
24
+ #st.info(f"Input video file size: {file_size} bytes")
25
  if file_size == 0:
26
  st.error("Input video file is empty")
27
  return None
28
 
29
+ # Ensure output directory exists
30
+ self.temp_audio.parent.mkdir(parents=True, exist_ok=True)
31
+
32
  # Construct FFmpeg command with detailed logging
33
  command = [
34
  "ffmpeg",
35
+ "-v", "error", # Change to error to reduce verbose output
36
  "-i", str(self.input_video),
37
  "-vn", # No video
38
+ "-acodec", "pcm_s16le", # 16-bit PCM
39
+ "-ar", "44100", # 44.1kHz sample rate
40
+ "-ac", "2", # Stereo
41
  "-y", # Overwrite output file if it exists
42
  str(self.temp_audio)
43
  ]
44
 
45
  # Run FFmpeg command and capture output
46
+ try:
47
+ # Use subprocess.run with timeout to prevent hanging
48
+ process = subprocess.run(
49
+ command,
50
+ capture_output=True,
51
+ text=True,
52
+ timeout=60 # 60 seconds timeout
53
+ )
54
+
55
+ # Check for any errors
56
+ if process.returncode != 0:
57
+ st.error(f"FFmpeg error: {process.stderr}")
58
+ st.error(f"FFmpeg command: {' '.join(command)}")
59
+ return None
60
+
61
+ except subprocess.TimeoutExpired:
62
+ st.error("Audio extraction timed out")
63
+ return None
64
+ except Exception as e:
65
+ st.error(f"Unexpected error during FFmpeg execution: {str(e)}")
66
  return None
67
 
68
  # Verify the output audio file exists and is not empty
69
+ if not self.temp_audio.exists():
70
+ st.error("Audio extraction failed: Output file is missing")
71
  return None
72
 
73
+ file_size = os.path.getsize(self.temp_audio)
74
+ if file_size == 0:
75
+ st.error("Audio extraction failed: Output file is empty")
76
+ return None
77
+
78
+ #st.success(f"Audio extracted successfully to {self.temp_audio}")
79
  return str(self.temp_audio)
80
 
81
  except Exception as e:
82
+ st.error(f"Unexpected error during audio extraction: {str(e)}")
 
83
  return None
84
 
85
  def combine_video_audio(self, vocals_path):