Spaces:
Running
Running
Commit
·
9c75488
1
Parent(s):
0343553
updated
Browse files- app.py +31 -8
- link_processor.py +62 -25
- requirements.txt +1 -1
- 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 |
-
|
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 |
-
|
|
|
146 |
|
147 |
st.info("Separating vocals...")
|
148 |
audio_proc = AudioProcessor(audio_path, process_dir)
|
149 |
if not audio_proc.run_demucs():
|
150 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
189 |
-
|
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
|
|
|
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=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
def download_from_url(self, url):
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
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 |
-
#
|
36 |
future = self.executor.submit(ydl.download, [url])
|
37 |
-
future.result()
|
38 |
|
39 |
-
|
40 |
-
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
except Exception as e:
|
43 |
-
|
44 |
-
|
|
|
45 |
|
46 |
def __del__(self):
|
47 |
-
|
|
|
|
|
|
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==
|
|
|
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 |
-
|
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", "
|
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 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
return None
|
57 |
|
58 |
# Verify the output audio file exists and is not empty
|
59 |
-
if not self.temp_audio.exists()
|
60 |
-
st.error("Audio extraction failed: Output file is missing
|
61 |
return None
|
62 |
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
64 |
return str(self.temp_audio)
|
65 |
|
66 |
except Exception as e:
|
67 |
-
st.error(f"
|
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):
|