Cuong2004 commited on
Commit
bdf4f9d
·
1 Parent(s): 3c174a1

Remove YouTube functionality completely - file upload only

Browse files
Dockerfile CHANGED
@@ -15,12 +15,6 @@ RUN apt-get update && apt-get install -y \
15
  libavcodec-dev \
16
  libavformat-dev \
17
  libswscale-dev \
18
- curl \
19
- wget \
20
- ca-certificates \
21
- dnsutils \
22
- iputils-ping \
23
- net-tools \
24
  && rm -rf /var/lib/apt/lists/*
25
 
26
  # Copy requirements first to leverage Docker cache
@@ -58,13 +52,10 @@ ENV HF_HOME=/tmp/cache/huggingface
58
  # DNS and network settings
59
  ENV PYTHONDONTWRITEBYTECODE=1
60
  ENV PYTHONPATH=/app
61
- # Configure DNS servers and network settings
62
- ENV DNS_SERVERS="8.8.8.8 1.1.1.1 208.67.222.222"
63
- ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
64
- ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
65
- # Network and DNS environment variables
66
- ENV PYTHONHTTPSVERIFY=0
67
- ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
68
 
69
  # Expose port
70
  EXPOSE 5000
 
15
  libavcodec-dev \
16
  libavformat-dev \
17
  libswscale-dev \
 
 
 
 
 
 
18
  && rm -rf /var/lib/apt/lists/*
19
 
20
  # Copy requirements first to leverage Docker cache
 
52
  # DNS and network settings
53
  ENV PYTHONDONTWRITEBYTECODE=1
54
  ENV PYTHONPATH=/app
55
+ # Basic environment variables
56
+ ENV PYTHONUNBUFFERED=1
57
+ ENV FLASK_APP=app.py
58
+ ENV FLASK_ENV=production
 
 
 
59
 
60
  # Expose port
61
  EXPOSE 5000
app.py CHANGED
@@ -25,24 +25,8 @@ os.environ['XDG_CACHE_HOME'] = '/tmp/cache'
25
  os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
26
  os.environ['PYTHONPATH'] = '/app'
27
 
28
- # Test network connectivity on startup
29
- try:
30
- from utils.youtube_downloader import test_network_connectivity, check_dns_resolution, setup_network_environment
31
-
32
- # Setup network environment first
33
- setup_network_environment()
34
-
35
- if test_network_connectivity():
36
- logger.info("✅ Network connectivity test passed")
37
- else:
38
- logger.warning("⚠️ Network connectivity test failed")
39
-
40
- if check_dns_resolution():
41
- logger.info("✅ DNS resolution test passed")
42
- else:
43
- logger.warning("⚠️ DNS resolution test failed")
44
- except Exception as e:
45
- logger.warning(f"Could not run network tests: {e}")
46
 
47
  # Create cache directories if they don't exist
48
  os.makedirs('/tmp/cache/torch', exist_ok=True)
@@ -55,7 +39,8 @@ import uuid
55
  from werkzeug.utils import secure_filename
56
 
57
  from models.video_processor import VideoProcessor
58
- from utils.youtube_downloader import download_youtube_video
 
59
 
60
  # Configure clean logging
61
  # logging.basicConfig(
@@ -106,10 +91,11 @@ def allowed_file(filename):
106
  return '.' in filename and \
107
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
108
 
109
- def is_youtube_url(url):
110
- """Check if URL is a YouTube video"""
111
- youtube_domains = ['youtube.com', 'youtu.be', 'www.youtube.com', 'm.youtube.com']
112
- return any(domain in url.lower() for domain in youtube_domains)
 
113
 
114
  @app.route('/')
115
  def index():
@@ -118,7 +104,7 @@ def index():
118
 
119
  @app.route('/upload', methods=['POST'])
120
  def upload_video():
121
- """Handle video upload or YouTube URL"""
122
  global video_processor
123
 
124
  # Check if video processor is available, try to initialize if not
@@ -137,36 +123,23 @@ def upload_video():
137
  try:
138
  video_path = None
139
 
140
- # Check if it's a YouTube URL
141
- youtube_url = request.form.get('youtube_url', '').strip()
142
- if youtube_url:
143
- if not is_youtube_url(youtube_url):
144
- return jsonify({'error': 'Invalid YouTube URL'}), 400
145
-
146
- logger.info(f"Processing YouTube URL: {youtube_url}")
147
- try:
148
- video_path = download_youtube_video(youtube_url, app.config['UPLOAD_FOLDER'])
149
- except Exception as e:
150
- logger.error(f"YouTube download error: {str(e)}")
151
- return jsonify({'error': f'Failed to download YouTube video: {str(e)}'}), 400
152
 
153
- # Check if it's a file upload
154
- elif 'video' in request.files:
155
- file = request.files['video']
156
- if file.filename == '':
157
- return jsonify({'error': 'No file selected'}), 400
158
-
159
- if file and allowed_file(file.filename):
160
- # Generate unique filename
161
- filename = secure_filename(file.filename)
162
- unique_filename = f"{uuid.uuid4()}_{filename}"
163
- video_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
164
- file.save(video_path)
165
- logger.info(f"File uploaded: {video_path}")
166
- else:
167
- return jsonify({'error': 'Invalid file format'}), 400
168
  else:
169
- return jsonify({'error': 'No video file or YouTube URL provided'}), 400
170
 
171
  # Process video
172
  logger.info(f"Starting video processing: {video_path}")
 
25
  os.environ['PYTHONDONTWRITEBYTECODE'] = '1'
26
  os.environ['PYTHONPATH'] = '/app'
27
 
28
+ # Network tests removed - YouTube functionality disabled
29
+ logger.info("📹 YouTube download functionality disabled")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  # Create cache directories if they don't exist
32
  os.makedirs('/tmp/cache/torch', exist_ok=True)
 
39
  from werkzeug.utils import secure_filename
40
 
41
  from models.video_processor import VideoProcessor
42
+ # YouTube downloader removed
43
+ # from utils.youtube_downloader import download_youtube_video
44
 
45
  # Configure clean logging
46
  # logging.basicConfig(
 
91
  return '.' in filename and \
92
  filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
93
 
94
+ # YouTube functionality removed
95
+ # def is_youtube_url(url):
96
+ # """Check if URL is a YouTube video"""
97
+ # youtube_domains = ['youtube.com', 'youtu.be', 'www.youtube.com', 'm.youtube.com']
98
+ # return any(domain in url.lower() for domain in youtube_domains)
99
 
100
  @app.route('/')
101
  def index():
 
104
 
105
  @app.route('/upload', methods=['POST'])
106
  def upload_video():
107
+ """Handle video upload only (YouTube functionality disabled)"""
108
  global video_processor
109
 
110
  # Check if video processor is available, try to initialize if not
 
123
  try:
124
  video_path = None
125
 
126
+ # Handle file upload only
127
+ if 'video' not in request.files:
128
+ return jsonify({'error': 'No video file provided'}), 400
129
+
130
+ file = request.files['video']
131
+ if file.filename == '':
132
+ return jsonify({'error': 'No file selected'}), 400
 
 
 
 
 
133
 
134
+ if file and allowed_file(file.filename):
135
+ # Generate unique filename
136
+ filename = secure_filename(file.filename)
137
+ unique_filename = f"{uuid.uuid4()}_{filename}"
138
+ video_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
139
+ file.save(video_path)
140
+ logger.info(f"File uploaded: {video_path}")
 
 
 
 
 
 
 
 
141
  else:
142
+ return jsonify({'error': 'Invalid file format. Please upload MP4, AVI, MOV, MKV, or other supported formats.'}), 400
143
 
144
  # Process video
145
  logger.info(f"Starting video processing: {video_path}")
requirements-flexible.txt CHANGED
@@ -19,7 +19,7 @@ numpy>=1.24.0,<2.0.0
19
  pandas>=2.0.0
20
 
21
  # YouTube Downloads
22
- yt-dlp>=2024.1.0
23
 
24
  # Utilities
25
  tqdm>=4.65.0
 
19
  pandas>=2.0.0
20
 
21
  # YouTube Downloads
22
+ # yt-dlp removed - YouTube functionality disabled
23
 
24
  # Utilities
25
  tqdm>=4.65.0
requirements.txt CHANGED
@@ -19,7 +19,7 @@ numpy==1.26.0
19
  pandas==2.1.0
20
 
21
  # YouTube Downloads
22
- yt-dlp==2024.1.6
23
 
24
  # Utilities
25
  tqdm==4.66.1
 
19
  pandas==2.1.0
20
 
21
  # YouTube Downloads
22
+ # yt-dlp removed - YouTube functionality disabled
23
 
24
  # Utilities
25
  tqdm==4.66.1
static/js/script.js CHANGED
@@ -6,7 +6,6 @@ document.addEventListener('DOMContentLoaded', function() {
6
  const resultsSection = document.getElementById('results');
7
  const errorSection = document.getElementById('error');
8
  const videoInput = document.getElementById('video');
9
- const youtubeInput = document.getElementById('youtube_url');
10
 
11
  // Exercise names for display
12
  const ACTIONS = [
@@ -17,28 +16,14 @@ document.addEventListener('DOMContentLoaded', function() {
17
  "shoulder press", "squat", "t bar row", "tricep Pushdown", "tricep dips"
18
  ];
19
 
20
- // Clear other input when one is selected
21
- videoInput.addEventListener('change', function() {
22
- if (this.files.length > 0) {
23
- youtubeInput.value = '';
24
- }
25
- });
26
-
27
- youtubeInput.addEventListener('input', function() {
28
- if (this.value.trim()) {
29
- videoInput.value = '';
30
- }
31
- });
32
-
33
  uploadForm.addEventListener('submit', async function(e) {
34
  e.preventDefault();
35
 
36
  // Validate inputs
37
  const hasFile = videoInput.files.length > 0;
38
- const hasYouTube = youtubeInput.value.trim();
39
 
40
- if (!hasFile && !hasYouTube) {
41
- showError('Please select a video file or provide a YouTube URL.');
42
  return;
43
  }
44
 
@@ -49,14 +34,7 @@ document.addEventListener('DOMContentLoaded', function() {
49
 
50
  try {
51
  const formData = new FormData();
52
-
53
- if (hasFile) {
54
- formData.append('video', videoInput.files[0]);
55
- }
56
-
57
- if (hasYouTube) {
58
- formData.append('youtube_url', youtubeInput.value.trim());
59
- }
60
 
61
  const response = await fetch('/upload', {
62
  method: 'POST',
 
6
  const resultsSection = document.getElementById('results');
7
  const errorSection = document.getElementById('error');
8
  const videoInput = document.getElementById('video');
 
9
 
10
  // Exercise names for display
11
  const ACTIONS = [
 
16
  "shoulder press", "squat", "t bar row", "tricep Pushdown", "tricep dips"
17
  ];
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  uploadForm.addEventListener('submit', async function(e) {
20
  e.preventDefault();
21
 
22
  // Validate inputs
23
  const hasFile = videoInput.files.length > 0;
 
24
 
25
+ if (!hasFile) {
26
+ showError('Please select a video file to upload.');
27
  return;
28
  }
29
 
 
34
 
35
  try {
36
  const formData = new FormData();
37
+ formData.append('video', videoInput.files[0]);
 
 
 
 
 
 
 
38
 
39
  const response = await fetch('/upload', {
40
  method: 'POST',
templates/index.html CHANGED
@@ -10,26 +10,17 @@
10
  <div class="container">
11
  <header>
12
  <h1>🏋️ Gym Exercise Recognition</h1>
13
- <p>Upload a video or provide a YouTube link to recognize gym exercises</p>
14
  </header>
15
 
16
  <div class="upload-section">
17
  <form id="uploadForm" enctype="multipart/form-data">
18
  <div class="input-group">
19
  <label for="video">Upload Video File:</label>
20
- <input type="file" id="video" name="video" accept="video/*">
21
  <small>Supported formats: MP4, AVI, MOV, MKV, WebM, WMV, FLV, M4V, 3GP</small>
22
  </div>
23
 
24
- <div class="or-divider">
25
- <span>OR</span>
26
- </div>
27
-
28
- <div class="input-group">
29
- <label for="youtube_url">YouTube URL:</label>
30
- <input type="url" id="youtube_url" name="youtube_url" placeholder="https://www.youtube.com/watch?v=...">
31
- </div>
32
-
33
  <button type="submit" id="submitBtn">
34
  <span id="btnText">Analyze Video</span>
35
  <span id="loadingSpinner" class="spinner" style="display: none;"></span>
 
10
  <div class="container">
11
  <header>
12
  <h1>🏋️ Gym Exercise Recognition</h1>
13
+ <p>Upload a video file to recognize gym exercises</p>
14
  </header>
15
 
16
  <div class="upload-section">
17
  <form id="uploadForm" enctype="multipart/form-data">
18
  <div class="input-group">
19
  <label for="video">Upload Video File:</label>
20
+ <input type="file" id="video" name="video" accept="video/*" required>
21
  <small>Supported formats: MP4, AVI, MOV, MKV, WebM, WMV, FLV, M4V, 3GP</small>
22
  </div>
23
 
 
 
 
 
 
 
 
 
 
24
  <button type="submit" id="submitBtn">
25
  <span id="btnText">Analyze Video</span>
26
  <span id="loadingSpinner" class="spinner" style="display: none;"></span>
utils/youtube_downloader.py DELETED
@@ -1,422 +0,0 @@
1
- import os
2
- import re
3
- import uuid
4
- import logging
5
- import socket
6
- from urllib.parse import urlparse, parse_qs
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
- def check_dns_resolution():
11
- """Check if DNS resolution is working"""
12
- try:
13
- # Try multiple DNS servers
14
- dns_servers = ['8.8.8.8', '1.1.1.1', '208.67.222.222', '9.9.9.9']
15
-
16
- for dns_server in dns_servers:
17
- try:
18
- # Try to resolve YouTube with specific timeout
19
- socket.setdefaulttimeout(10)
20
- ip = socket.gethostbyname('www.youtube.com')
21
- logger.info(f"DNS resolution successful: www.youtube.com -> {ip}")
22
- return True
23
- except Exception as e:
24
- logger.warning(f"DNS server {dns_server} failed: {e}")
25
- continue
26
-
27
- logger.error("All DNS servers failed")
28
- return False
29
-
30
- except Exception as e:
31
- logger.error(f"DNS resolution failed: {e}")
32
- return False
33
-
34
- def setup_network_environment():
35
- """Setup network environment variables"""
36
- try:
37
- import os
38
-
39
- # Set DNS servers via environment
40
- os.environ['DNS_SERVERS'] = '8.8.8.8 1.1.1.1 208.67.222.222'
41
-
42
- # Disable SSL verification for testing
43
- os.environ['PYTHONHTTPSVERIFY'] = '0'
44
- os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
45
- os.environ['SSL_CERT_FILE'] = '/etc/ssl/certs/ca-certificates.crt'
46
- os.environ['CURL_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
47
-
48
- logger.info("Network environment variables set")
49
- return True
50
- except Exception as e:
51
- logger.warning(f"Could not set network environment: {e}")
52
- return False
53
-
54
- def test_network_connectivity():
55
- """Test basic network connectivity"""
56
- try:
57
- import subprocess
58
- import shutil
59
-
60
- # Check if ping is available
61
- if shutil.which('ping'):
62
- # Try to ping Google DNS with shorter timeout
63
- result = subprocess.run(['ping', '-c', '1', '-W', '5', '8.8.8.8'],
64
- capture_output=True, text=True, timeout=8)
65
- if result.returncode == 0:
66
- logger.info("Network connectivity test passed")
67
- return True
68
- else:
69
- logger.warning(f"Ping test failed: {result.stderr}")
70
- return False
71
- else:
72
- logger.warning("Ping not available, skipping network test")
73
- return True
74
-
75
- except subprocess.TimeoutExpired:
76
- logger.warning("Ping test timed out, but continuing...")
77
- return True # Don't fail on timeout
78
- except Exception as e:
79
- logger.warning(f"Network connectivity test failed: {e}")
80
- return True # Don't fail on network test errors
81
-
82
- def extract_video_id(url):
83
- """Extract YouTube video ID from various URL formats"""
84
- patterns = [
85
- r'(?:v=|\/)([0-9A-Za-z_-]{11}).*',
86
- r'(?:embed\/)([0-9A-Za-z_-]{11})',
87
- r'(?:vi\/)([0-9A-Za-z_-]{11})',
88
- r'(?:v\/)([0-9A-Za-z_-]{11})',
89
- r'(?:\/|%3D)([0-9A-Za-z_-]{11})',
90
- r'(?:watch.*v=)([0-9A-Za-z_-]{11})',
91
- r'^([0-9A-Za-z_-]{11})$'
92
- ]
93
-
94
- for pattern in patterns:
95
- match = re.search(pattern, url)
96
- if match:
97
- return match.group(1)
98
-
99
- return None
100
-
101
- def download_youtube_video(url, output_dir):
102
- """
103
- Download YouTube video using yt-dlp
104
- Returns: path to downloaded video file
105
- """
106
- try:
107
- import yt_dlp
108
- except ImportError:
109
- raise ImportError("yt-dlp is required for YouTube downloads. Install with: pip install yt-dlp")
110
-
111
- # Setup network environment
112
- setup_network_environment()
113
-
114
- # Check network connectivity first (but don't fail if it doesn't work)
115
- if not test_network_connectivity():
116
- logger.warning("Network connectivity test failed, but continuing with download attempt...")
117
-
118
- # Check DNS resolution
119
- if not check_dns_resolution():
120
- logger.warning("DNS resolution failed, but continuing with download attempt...")
121
- # Don't fail immediately, try the download anyway
122
-
123
- # Try alternative DNS resolution methods
124
- try:
125
- import socket
126
- import requests
127
-
128
- # Method 1: Try with requests library
129
- try:
130
- response = requests.get('https://www.youtube.com', timeout=10)
131
- if response.status_code == 200:
132
- logger.info("YouTube connectivity confirmed via requests")
133
- else:
134
- logger.warning(f"YouTube response status: {response.status_code}")
135
- except Exception as e:
136
- logger.warning(f"Requests method failed: {e}")
137
-
138
- # Method 2: Try with custom DNS resolution
139
- try:
140
- # Try to resolve with different methods
141
- socket.setdefaulttimeout(10)
142
- ip = socket.gethostbyname('www.youtube.com')
143
- logger.info(f"Manual DNS resolution successful: {ip}")
144
- except Exception as e:
145
- logger.warning(f"Manual DNS resolution failed: {e}")
146
-
147
- except Exception as e:
148
- logger.warning(f"Alternative DNS methods failed: {e}")
149
-
150
- # Extract video ID for unique filename
151
- video_id = extract_video_id(url)
152
- if not video_id:
153
- raise ValueError("Invalid YouTube URL - cannot extract video ID")
154
-
155
- # Create unique filename with simpler pattern
156
- unique_id = str(uuid.uuid4())[:8]
157
- output_filename = f"youtube_{video_id}_{unique_id}.%(ext)s"
158
- output_path = os.path.join(output_dir, output_filename)
159
-
160
- # Also create a backup simple filename
161
- simple_filename = f"video_{unique_id}.%(ext)s"
162
- simple_output_path = os.path.join(output_dir, simple_filename)
163
-
164
- # yt-dlp options with multiple output templates and enhanced network settings
165
- ydl_opts = {
166
- 'outtmpl': [
167
- output_path, # Primary template
168
- simple_output_path, # Backup simple template
169
- os.path.join(output_dir, '%(title)s.%(ext)s'), # Title-based template
170
- os.path.join(output_dir, '%(id)s.%(ext)s'), # ID-based template
171
- ],
172
- 'format': 'best[height<=720]/best', # Prefer 720p or lower for faster processing
173
- 'noplaylist': True,
174
- 'quiet': False, # Enable output for debugging
175
- 'no_warnings': False, # Show warnings for debugging
176
- 'nocheckcertificate': True, # Skip SSL certificate verification
177
- 'ignoreerrors': True, # Continue on download errors
178
- 'socket_timeout': 120, # Increase socket timeout significantly
179
- 'retries': 10, # More retries
180
- 'fragment_retries': 10, # More fragment retries
181
- 'progress_hooks': [lambda d: logger.info(f"Download progress: {d.get('status', 'unknown')}") if d.get('status') == 'finished' else None],
182
- 'http_headers': {
183
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
184
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
185
- 'Accept-Language': 'en-US,en;q=0.5',
186
- 'Accept-Encoding': 'gzip, deflate',
187
- 'Connection': 'keep-alive',
188
- },
189
- 'extractor_args': {
190
- 'youtube': {
191
- 'skip': ['dash', 'live'],
192
- 'player_client': ['android', 'web'],
193
- }
194
- },
195
- }
196
-
197
- logger.info(f"Downloading YouTube video: {url}")
198
-
199
- try:
200
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
201
- # Get video info first
202
- try:
203
- info = ydl.extract_info(url, download=False)
204
- if info is None:
205
- raise ValueError("Could not extract video information from URL")
206
-
207
- duration = info.get('duration', 0)
208
-
209
- # Check duration limit (10 minutes max for demo)
210
- if duration > 600: # 10 minutes
211
- raise ValueError(f"Video too long ({duration}s). Please use videos shorter than 10 minutes.")
212
-
213
- logger.info(f"Video info extracted: duration={duration}s")
214
-
215
- except Exception as info_error:
216
- logger.warning(f"Could not extract video info: {info_error}")
217
- # Continue with download anyway
218
- info = None
219
-
220
- # Download the video
221
- logger.info("Starting video download...")
222
- logger.info(f"Output template: {output_path}")
223
- logger.info(f"Output directory: {output_dir}")
224
-
225
- # List files before download
226
- try:
227
- files_before = os.listdir(output_dir)
228
- logger.info(f"Files before download: {files_before}")
229
- except Exception as e:
230
- logger.warning(f"Could not list files before download: {e}")
231
-
232
- ydl.download([url])
233
-
234
- # List files after download
235
- try:
236
- files_after = os.listdir(output_dir)
237
- logger.info(f"Files after download: {files_after}")
238
- except Exception as e:
239
- logger.warning(f"Could not list files after download: {e}")
240
-
241
- # Try fallback download if no file found
242
- if not any(f.lower().endswith(('.mp4', '.mkv', '.webm', '.avi', '.m4v', '.mov')) for f in files_after):
243
- logger.warning("No video file found, trying fallback download...")
244
-
245
- # Try with simpler options
246
- fallback_opts = {
247
- 'outtmpl': os.path.join(output_dir, '%(id)s.%(ext)s'),
248
- 'format': 'best',
249
- 'noplaylist': True,
250
- 'quiet': False,
251
- 'no_warnings': False,
252
- 'nocheckcertificate': True,
253
- 'ignoreerrors': True,
254
- }
255
-
256
- try:
257
- with yt_dlp.YoutubeDL(fallback_opts) as ydl_fallback:
258
- ydl_fallback.download([url])
259
-
260
- # List files after fallback download
261
- try:
262
- files_after_fallback = os.listdir(output_dir)
263
- logger.info(f"Files after fallback download: {files_after_fallback}")
264
- except Exception as e:
265
- logger.warning(f"Could not list files after fallback download: {e}")
266
-
267
- except Exception as fallback_error:
268
- logger.error(f"Fallback download also failed: {fallback_error}")
269
-
270
- # Try curl as last resort
271
- logger.warning("Trying curl as last resort...")
272
- try:
273
- import subprocess
274
- curl_cmd = [
275
- 'curl', '-L', '-o',
276
- os.path.join(output_dir, f'{video_id}.mp4'),
277
- '--connect-timeout', '30',
278
- '--max-time', '300',
279
- '--retry', '3',
280
- '--retry-delay', '2',
281
- url
282
- ]
283
- result = subprocess.run(curl_cmd, capture_output=True, text=True, timeout=300)
284
- if result.returncode == 0:
285
- logger.info("Curl download successful")
286
- else:
287
- logger.error(f"Curl download failed: {result.stderr}")
288
- except Exception as curl_error:
289
- logger.error(f"Curl download also failed: {curl_error}")
290
-
291
- # Find the actual downloaded file
292
- logger.info("Searching for downloaded file...")
293
-
294
- downloaded_file = None
295
-
296
- # Method 1: Try all possible output templates
297
- possible_templates = [
298
- output_path.replace('.%(ext)s', ''),
299
- simple_output_path.replace('.%(ext)s', ''),
300
- os.path.join(output_dir, video_id),
301
- os.path.join(output_dir, f"youtube_{video_id}"),
302
- os.path.join(output_dir, f"video_{unique_id}"),
303
- ]
304
-
305
- possible_extensions = ['.mp4', '.mkv', '.webm', '.avi', '.m4v', '.mov']
306
-
307
- for template in possible_templates:
308
- for ext in possible_extensions:
309
- candidate = template + ext
310
- if os.path.exists(candidate) and os.path.isfile(candidate):
311
- downloaded_file = candidate
312
- logger.info(f"Found file with template {template} + {ext}: {candidate}")
313
- break
314
- if downloaded_file:
315
- break
316
-
317
- # Method 2: Search by pattern in output directory
318
- if not downloaded_file:
319
- patterns = [
320
- f"youtube_{video_id}",
321
- f"video_{unique_id}",
322
- video_id,
323
- "youtube_",
324
- "video_"
325
- ]
326
-
327
- try:
328
- files_in_dir = os.listdir(output_dir)
329
- logger.info(f"Files in directory: {files_in_dir}")
330
-
331
- for pattern in patterns:
332
- for file in files_in_dir:
333
- if (file.startswith(pattern) and
334
- not file.endswith('.part') and
335
- any(file.lower().endswith(ext) for ext in possible_extensions)):
336
- candidate = os.path.join(output_dir, file)
337
- if os.path.isfile(candidate):
338
- downloaded_file = candidate
339
- logger.info(f"Found file by pattern '{pattern}': {candidate}")
340
- break
341
- if downloaded_file:
342
- break
343
- except Exception as e:
344
- logger.warning(f"Error listing directory: {e}")
345
-
346
- # Method 3: Find any video file in directory (last resort)
347
- if not downloaded_file:
348
- logger.info("Searching for any video file in directory...")
349
- try:
350
- for file in os.listdir(output_dir):
351
- if any(file.lower().endswith(ext) for ext in possible_extensions):
352
- candidate = os.path.join(output_dir, file)
353
- if os.path.isfile(candidate) and not file.endswith('.part'):
354
- downloaded_file = candidate
355
- logger.info(f"Found video file: {candidate}")
356
- break
357
- except Exception as e:
358
- logger.warning(f"Error searching for video files: {e}")
359
-
360
- if not downloaded_file or not os.path.exists(downloaded_file):
361
- logger.error(f"Downloaded file not found. Searched in: {output_dir}")
362
- logger.error(f"Video ID: {video_id}")
363
- logger.error(f"Unique ID: {unique_id}")
364
- logger.error(f"Possible templates: {possible_templates}")
365
- raise FileNotFoundError("Downloaded file not found after exhaustive search")
366
-
367
- logger.info(f"YouTube video downloaded: {downloaded_file}")
368
- return downloaded_file
369
-
370
- except Exception as e:
371
- logger.error(f"YouTube download failed: {str(e)}")
372
-
373
- # Provide more specific error messages
374
- if "Video unavailable" in str(e):
375
- raise ValueError("This video is unavailable or private. Please check the URL.")
376
- elif "Sign in" in str(e):
377
- raise ValueError("This video requires sign-in. Please use a public video.")
378
- elif "copyright" in str(e).lower():
379
- raise ValueError("This video has copyright restrictions and cannot be downloaded.")
380
- elif "DNS" in str(e) or "resolve" in str(e):
381
- raise ConnectionError("Network connection failed. Please check your internet connection.")
382
- else:
383
- # Clean up any partial downloads
384
- try:
385
- base_path = output_path.replace('.%(ext)s', '')
386
- for ext in ['.mp4', '.mkv', '.webm', '.avi', '.part']:
387
- candidate = base_path + ext
388
- if os.path.exists(candidate):
389
- os.remove(candidate)
390
- logger.info(f"Cleaned up partial download: {candidate}")
391
- except Exception as cleanup_error:
392
- logger.warning(f"Could not clean up partial downloads: {cleanup_error}")
393
-
394
- raise
395
-
396
- def get_video_info(url):
397
- """Get basic info about YouTube video without downloading"""
398
- try:
399
- import yt_dlp
400
-
401
- ydl_opts = {
402
- 'quiet': True,
403
- 'no_warnings': True,
404
- 'nocheckcertificate': True,
405
- 'ignoreerrors': True,
406
- }
407
-
408
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
409
- info = ydl.extract_info(url, download=False)
410
- if info is None:
411
- logger.error("Could not extract video information")
412
- return None
413
-
414
- return {
415
- 'title': info.get('title', 'Unknown'),
416
- 'duration': info.get('duration', 0),
417
- 'uploader': info.get('uploader', 'Unknown'),
418
- 'view_count': info.get('view_count', 0)
419
- }
420
- except Exception as e:
421
- logger.error(f"Failed to get video info: {str(e)}")
422
- return None