Spaces:
Sleeping
Sleeping
Commit
·
49e20ff
1
Parent(s):
c9b5d14
retryng
Browse files- app/services/encoder_service.py +58 -47
app/services/encoder_service.py
CHANGED
|
@@ -9,6 +9,7 @@ import re
|
|
| 9 |
import threading
|
| 10 |
import queue
|
| 11 |
import signal
|
|
|
|
| 12 |
|
| 13 |
# Configure logging
|
| 14 |
logging.basicConfig(level=logging.INFO)
|
|
@@ -100,7 +101,7 @@ class EncoderService:
|
|
| 100 |
return {'status': 'failed', 'error': str(e)}
|
| 101 |
|
| 102 |
def _encode_video(self, filename, job_id):
|
| 103 |
-
"""Internal method to handle video encoding"""
|
| 104 |
try:
|
| 105 |
upload_path = Path(os.getenv('UPLOAD_FOLDER', 'uploads'))
|
| 106 |
encoded_path = Path(os.getenv('ENCODED_FOLDER', 'encoded'))
|
|
@@ -122,13 +123,17 @@ class EncoderService:
|
|
| 122 |
raise Exception("Could not determine video duration")
|
| 123 |
|
| 124 |
for quality, settings in qualities.items():
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
self.jobs[job_id].update({
|
| 127 |
'status': 'processing',
|
| 128 |
'current_quality': quality,
|
| 129 |
'progress': (completed_steps / total_steps) * 100
|
| 130 |
})
|
| 131 |
-
|
| 132 |
output_file = output_dir / f"{output_name}_{quality}.mp4"
|
| 133 |
|
| 134 |
# FFmpeg command with optimized settings for web streaming
|
|
@@ -146,59 +151,65 @@ class EncoderService:
|
|
| 146 |
'-vf', f"scale={settings['width']}:{settings['height']}",
|
| 147 |
'-g', settings['keyframe'],
|
| 148 |
'-keyint_min', settings['keyframe'],
|
| 149 |
-
'-sc_threshold', '0',
|
| 150 |
'-c:a', 'aac',
|
| 151 |
'-b:a', settings['audio_bitrate'],
|
| 152 |
-
'-ar', '48000',
|
| 153 |
-
'-ac', '2',
|
| 154 |
-
'-movflags', '+faststart',
|
| 155 |
'-progress', 'pipe:1',
|
| 156 |
str(output_file)
|
| 157 |
]
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
|
|
|
|
|
|
| 165 |
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
'quality': quality,
|
| 182 |
-
'path': str(output_file),
|
| 183 |
-
'settings': settings
|
| 184 |
-
})
|
| 185 |
-
completed_steps += 1
|
| 186 |
-
else:
|
| 187 |
-
error_output = process.stderr.read()
|
| 188 |
-
logger.error(f"FFmpeg error: {error_output}")
|
| 189 |
-
raise Exception(f"FFmpeg failed for quality {quality}")
|
| 190 |
-
|
| 191 |
-
except Exception as e:
|
| 192 |
-
logger.error(f"Error encoding {quality}: {str(e)}")
|
| 193 |
-
self.jobs[job_id].update({
|
| 194 |
-
'status': 'failed',
|
| 195 |
-
'error': str(e)
|
| 196 |
-
})
|
| 197 |
-
return
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
self.jobs[job_id].update({
|
| 204 |
'status': 'completed',
|
|
|
|
| 9 |
import threading
|
| 10 |
import queue
|
| 11 |
import signal
|
| 12 |
+
import time # Added for retry delays
|
| 13 |
|
| 14 |
# Configure logging
|
| 15 |
logging.basicConfig(level=logging.INFO)
|
|
|
|
| 101 |
return {'status': 'failed', 'error': str(e)}
|
| 102 |
|
| 103 |
def _encode_video(self, filename, job_id):
|
| 104 |
+
"""Internal method to handle video encoding with retries and enhanced error handling"""
|
| 105 |
try:
|
| 106 |
upload_path = Path(os.getenv('UPLOAD_FOLDER', 'uploads'))
|
| 107 |
encoded_path = Path(os.getenv('ENCODED_FOLDER', 'encoded'))
|
|
|
|
| 123 |
raise Exception("Could not determine video duration")
|
| 124 |
|
| 125 |
for quality, settings in qualities.items():
|
| 126 |
+
max_retries = 3
|
| 127 |
+
attempts = 0
|
| 128 |
+
success = False
|
| 129 |
+
|
| 130 |
+
while attempts < max_retries and not success:
|
| 131 |
+
logger.info(f"Starting encoding for quality {quality}, attempt {attempts+1}")
|
| 132 |
self.jobs[job_id].update({
|
| 133 |
'status': 'processing',
|
| 134 |
'current_quality': quality,
|
| 135 |
'progress': (completed_steps / total_steps) * 100
|
| 136 |
})
|
|
|
|
| 137 |
output_file = output_dir / f"{output_name}_{quality}.mp4"
|
| 138 |
|
| 139 |
# FFmpeg command with optimized settings for web streaming
|
|
|
|
| 151 |
'-vf', f"scale={settings['width']}:{settings['height']}",
|
| 152 |
'-g', settings['keyframe'],
|
| 153 |
'-keyint_min', settings['keyframe'],
|
| 154 |
+
'-sc_threshold', '0',
|
| 155 |
'-c:a', 'aac',
|
| 156 |
'-b:a', settings['audio_bitrate'],
|
| 157 |
+
'-ar', '48000',
|
| 158 |
+
'-ac', '2',
|
| 159 |
+
'-movflags', '+faststart',
|
| 160 |
'-progress', 'pipe:1',
|
| 161 |
str(output_file)
|
| 162 |
]
|
| 163 |
|
| 164 |
+
try:
|
| 165 |
+
process = subprocess.Popen(
|
| 166 |
+
cmd,
|
| 167 |
+
stdout=subprocess.PIPE,
|
| 168 |
+
stderr=subprocess.PIPE,
|
| 169 |
+
universal_newlines=True
|
| 170 |
+
)
|
| 171 |
+
self.active_processes[job_id] = process
|
| 172 |
|
| 173 |
+
# Monitor FFmpeg progress
|
| 174 |
+
while True:
|
| 175 |
+
line = process.stdout.readline()
|
| 176 |
+
if line == '' and process.poll() is not None:
|
| 177 |
+
break
|
| 178 |
+
if line:
|
| 179 |
+
progress = self._parse_ffmpeg_progress(line, duration)
|
| 180 |
+
if progress is not None:
|
| 181 |
+
quality_progress = ((completed_steps + progress / 100) / total_steps) * 100
|
| 182 |
+
self.jobs[job_id]['progress'] = quality_progress
|
| 183 |
|
| 184 |
+
if process.returncode == 0:
|
| 185 |
+
logger.info(f"Encoding successful for quality {quality} on attempt {attempts+1}")
|
| 186 |
+
outputs.append({
|
| 187 |
+
'quality': quality,
|
| 188 |
+
'path': str(output_file),
|
| 189 |
+
'settings': settings
|
| 190 |
+
})
|
| 191 |
+
success = True
|
| 192 |
+
completed_steps += 1
|
| 193 |
+
else:
|
| 194 |
+
error_output = process.stderr.read()
|
| 195 |
+
logger.error(f"FFmpeg error on quality {quality}, attempt {attempts+1}: {error_output}")
|
| 196 |
+
raise Exception(f"FFmpeg failed for quality {quality}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
|
| 198 |
+
except Exception as e:
|
| 199 |
+
logger.error(f"Error encoding {quality} on attempt {attempts+1}: {str(e)}")
|
| 200 |
+
attempts += 1
|
| 201 |
+
if attempts < max_retries:
|
| 202 |
+
logger.info(f"Retrying encoding for quality {quality} (attempt {attempts+1} of {max_retries})")
|
| 203 |
+
time.sleep(2) # Wait before retrying
|
| 204 |
+
else:
|
| 205 |
+
self.jobs[job_id].update({
|
| 206 |
+
'status': 'failed',
|
| 207 |
+
'error': f"Failed encoding {quality} after {max_retries} attempts: {str(e)}"
|
| 208 |
+
})
|
| 209 |
+
return
|
| 210 |
+
finally:
|
| 211 |
+
if job_id in self.active_processes:
|
| 212 |
+
del self.active_processes[job_id]
|
| 213 |
|
| 214 |
self.jobs[job_id].update({
|
| 215 |
'status': 'completed',
|