Spaces:
Paused
Paused
Prathamesh Sarjerao Vaidya
commited on
Commit
·
4f3a209
1
Parent(s):
484e5b0
made changes
Browse files- Dockerfile +18 -40
- backend/app/main.py +16 -11
- backend/app/video_processor.py +4 -17
- frontend/index.html +17 -7
- frontend/js/app.js +25 -3
Dockerfile
CHANGED
|
@@ -1,16 +1,13 @@
|
|
| 1 |
-
# Optimized Dockerfile
|
| 2 |
-
# Fixes MediaPipe models and FFmpeg H.264 encoder issues
|
| 3 |
-
|
| 4 |
FROM python:3.10-slim
|
| 5 |
|
| 6 |
-
# Set environment variables
|
| 7 |
ENV PYTHONUNBUFFERED=1 \
|
| 8 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 9 |
PIP_NO_CACHE_DIR=1 \
|
| 10 |
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 11 |
DEBIAN_FRONTEND=noninteractive
|
| 12 |
|
| 13 |
-
# Install system dependencies
|
| 14 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 15 |
libgl1 \
|
| 16 |
libglib2.0-0 \
|
|
@@ -20,13 +17,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 20 |
libgomp1 \
|
| 21 |
ffmpeg \
|
| 22 |
libavcodec-extra \
|
|
|
|
| 23 |
curl \
|
|
|
|
| 24 |
&& rm -rf /var/lib/apt/lists/*
|
| 25 |
|
| 26 |
# Verify FFmpeg has H.264 encoder
|
| 27 |
-
RUN ffmpeg -codecs | grep h264 || echo "
|
| 28 |
|
| 29 |
-
# Create app directory
|
| 30 |
WORKDIR /app
|
| 31 |
|
| 32 |
# Copy and install Python dependencies
|
|
@@ -34,58 +32,38 @@ COPY backend/requirements.txt .
|
|
| 34 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 35 |
pip install --no-cache-dir -r requirements.txt
|
| 36 |
|
| 37 |
-
# Pre-download MediaPipe models
|
| 38 |
RUN echo "Downloading MediaPipe Pose models..." && \
|
| 39 |
python3 -c "import mediapipe as mp; \
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
print('✅
|
| 44 |
-
|
| 45 |
-
pose1 = mp.solutions.pose.Pose(model_complexity=1, min_detection_confidence=0.5); \
|
| 46 |
-
pose1.close(); \
|
| 47 |
-
print('✅ Full model downloaded'); \
|
| 48 |
-
print('Downloading Heavy model...'); \
|
| 49 |
-
pose2 = mp.solutions.pose.Pose(model_complexity=2, min_detection_confidence=0.5); \
|
| 50 |
-
pose2.close(); \
|
| 51 |
-
print('✅ Heavy model downloaded'); \
|
| 52 |
-
print('✅ All models pre-downloaded successfully');" && \
|
| 53 |
-
echo "MediaPipe models downloaded successfully"
|
| 54 |
|
| 55 |
-
# Set
|
| 56 |
RUN chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe
|
| 57 |
|
| 58 |
-
# Create
|
| 59 |
RUN mkdir -p /app/uploads /app/outputs /app/logs
|
| 60 |
|
| 61 |
-
# Copy application
|
| 62 |
COPY backend/app /app/app
|
| 63 |
COPY frontend /app/frontend
|
| 64 |
-
|
| 65 |
-
# Copy startup script
|
| 66 |
COPY startup.sh /app/startup.sh
|
| 67 |
RUN chmod +x /app/startup.sh
|
| 68 |
|
| 69 |
# Create non-root user
|
| 70 |
-
RUN useradd -m -u 1000 appuser
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
RUN chown -R appuser:appuser /app
|
| 74 |
-
|
| 75 |
-
# IMPORTANT: Keep MediaPipe models readable by non-root user
|
| 76 |
-
RUN chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe && \
|
| 77 |
chown -R root:root /usr/local/lib/python3.10/site-packages/mediapipe
|
| 78 |
|
| 79 |
-
# Switch to non-root user
|
| 80 |
USER appuser
|
| 81 |
|
| 82 |
-
|
| 83 |
-
EXPOSE 7860
|
| 84 |
-
EXPOSE 8000
|
| 85 |
|
| 86 |
-
# Health check
|
| 87 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \
|
| 88 |
CMD python -c "import requests; requests.get('http://localhost:7860/health')" || exit 1
|
| 89 |
|
| 90 |
-
# Start application
|
| 91 |
CMD ["/app/startup.sh"]
|
|
|
|
| 1 |
+
# Optimized Dockerfile with full H.264 support
|
|
|
|
|
|
|
| 2 |
FROM python:3.10-slim
|
| 3 |
|
|
|
|
| 4 |
ENV PYTHONUNBUFFERED=1 \
|
| 5 |
PYTHONDONTWRITEBYTECODE=1 \
|
| 6 |
PIP_NO_CACHE_DIR=1 \
|
| 7 |
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
| 8 |
DEBIAN_FRONTEND=noninteractive
|
| 9 |
|
| 10 |
+
# Install system dependencies with full ffmpeg and x264
|
| 11 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 12 |
libgl1 \
|
| 13 |
libglib2.0-0 \
|
|
|
|
| 17 |
libgomp1 \
|
| 18 |
ffmpeg \
|
| 19 |
libavcodec-extra \
|
| 20 |
+
libx264-dev \
|
| 21 |
curl \
|
| 22 |
+
wget \
|
| 23 |
&& rm -rf /var/lib/apt/lists/*
|
| 24 |
|
| 25 |
# Verify FFmpeg has H.264 encoder
|
| 26 |
+
RUN ffmpeg -codecs 2>/dev/null | grep -i h264 || echo "H.264 codec not available, will use fallback"
|
| 27 |
|
|
|
|
| 28 |
WORKDIR /app
|
| 29 |
|
| 30 |
# Copy and install Python dependencies
|
|
|
|
| 32 |
RUN pip install --no-cache-dir --upgrade pip && \
|
| 33 |
pip install --no-cache-dir -r requirements.txt
|
| 34 |
|
| 35 |
+
# Pre-download MediaPipe models
|
| 36 |
RUN echo "Downloading MediaPipe Pose models..." && \
|
| 37 |
python3 -c "import mediapipe as mp; \
|
| 38 |
+
pose0 = mp.solutions.pose.Pose(model_complexity=0, min_detection_confidence=0.5); pose0.close(); \
|
| 39 |
+
pose1 = mp.solutions.pose.Pose(model_complexity=1, min_detection_confidence=0.5); pose1.close(); \
|
| 40 |
+
pose2 = mp.solutions.pose.Pose(model_complexity=2, min_detection_confidence=0.5); pose2.close(); \
|
| 41 |
+
print('✅ All models pre-downloaded');" && \
|
| 42 |
+
echo "MediaPipe models downloaded"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
+
# Set permissions
|
| 45 |
RUN chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe
|
| 46 |
|
| 47 |
+
# Create directories
|
| 48 |
RUN mkdir -p /app/uploads /app/outputs /app/logs
|
| 49 |
|
| 50 |
+
# Copy application
|
| 51 |
COPY backend/app /app/app
|
| 52 |
COPY frontend /app/frontend
|
|
|
|
|
|
|
| 53 |
COPY startup.sh /app/startup.sh
|
| 54 |
RUN chmod +x /app/startup.sh
|
| 55 |
|
| 56 |
# Create non-root user
|
| 57 |
+
RUN useradd -m -u 1000 appuser && \
|
| 58 |
+
chown -R appuser:appuser /app && \
|
| 59 |
+
chmod -R 755 /usr/local/lib/python3.10/site-packages/mediapipe && \
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
chown -R root:root /usr/local/lib/python3.10/site-packages/mediapipe
|
| 61 |
|
|
|
|
| 62 |
USER appuser
|
| 63 |
|
| 64 |
+
EXPOSE 7860 8000
|
|
|
|
|
|
|
| 65 |
|
|
|
|
| 66 |
HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \
|
| 67 |
CMD python -c "import requests; requests.get('http://localhost:7860/health')" || exit 1
|
| 68 |
|
|
|
|
| 69 |
CMD ["/app/startup.sh"]
|
backend/app/main.py
CHANGED
|
@@ -503,25 +503,30 @@ async def download_video(session_id: str):
|
|
| 503 |
if not output_path.exists():
|
| 504 |
raise HTTPException(status_code=404, detail="Output file not found")
|
| 505 |
|
| 506 |
-
#
|
|
|
|
|
|
|
|
|
|
| 507 |
def iterfile():
|
| 508 |
with open(output_path, mode="rb") as file_like:
|
| 509 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
return StreamingResponse(
|
| 512 |
iterfile(),
|
| 513 |
-
media_type="video/mp4",
|
| 514 |
headers={
|
| 515 |
-
"Accept-Ranges": "bytes",
|
| 516 |
-
"Content-
|
|
|
|
|
|
|
|
|
|
| 517 |
}
|
| 518 |
)
|
| 519 |
-
|
| 520 |
-
# return FileResponse(
|
| 521 |
-
# path=output_path,
|
| 522 |
-
# media_type="video/mp4",
|
| 523 |
-
# filename=f"analyzed_{session['filename']}"
|
| 524 |
-
# )
|
| 525 |
|
| 526 |
|
| 527 |
@app.websocket("/ws/{session_id}")
|
|
|
|
| 503 |
if not output_path.exists():
|
| 504 |
raise HTTPException(status_code=404, detail="Output file not found")
|
| 505 |
|
| 506 |
+
# Get file size for Content-Length header
|
| 507 |
+
file_size = output_path.stat().st_size
|
| 508 |
+
|
| 509 |
+
# Use StreamingResponse with proper headers for browser video playback
|
| 510 |
def iterfile():
|
| 511 |
with open(output_path, mode="rb") as file_like:
|
| 512 |
+
chunk_size = 8192 # 8KB chunks
|
| 513 |
+
while True:
|
| 514 |
+
chunk = file_like.read(chunk_size)
|
| 515 |
+
if not chunk:
|
| 516 |
+
break
|
| 517 |
+
yield chunk
|
| 518 |
|
| 519 |
return StreamingResponse(
|
| 520 |
iterfile(),
|
| 521 |
+
media_type="video/mp4",
|
| 522 |
headers={
|
| 523 |
+
"Accept-Ranges": "bytes",
|
| 524 |
+
"Content-Length": str(file_size),
|
| 525 |
+
"Content-Disposition": f'inline; filename="analyzed_{session["filename"]}"',
|
| 526 |
+
"Cache-Control": "no-cache",
|
| 527 |
+
"X-Content-Type-Options": "nosniff"
|
| 528 |
}
|
| 529 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 530 |
|
| 531 |
|
| 532 |
@app.websocket("/ws/{session_id}")
|
backend/app/video_processor.py
CHANGED
|
@@ -112,25 +112,12 @@ class VideoProcessor:
|
|
| 112 |
# Open video for reading
|
| 113 |
cap = cv2.VideoCapture(str(video_path))
|
| 114 |
|
| 115 |
-
# Setup video writer
|
| 116 |
-
# # fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 117 |
-
# fourcc = cv2.VideoWriter_fourcc(*Config.OUTPUT_VIDEO_CODEC)
|
| 118 |
-
# out = cv2.VideoWriter(
|
| 119 |
-
# str(output_path),
|
| 120 |
-
# fourcc,
|
| 121 |
-
# video_info['fps'],
|
| 122 |
-
# (video_info['width'], video_info['height'])
|
| 123 |
-
# )
|
| 124 |
-
|
| 125 |
-
# if not out.isOpened():
|
| 126 |
-
# raise ValueError(f"Cannot create output video: {output_path}")
|
| 127 |
-
|
| 128 |
# Setup video writer with codec fallback
|
| 129 |
codecs_to_try = [
|
| 130 |
-
('
|
| 131 |
-
('
|
| 132 |
-
('XVID', 'Xvid'),
|
| 133 |
-
('MJPG', 'Motion JPEG')
|
| 134 |
]
|
| 135 |
|
| 136 |
out = None
|
|
|
|
| 112 |
# Open video for reading
|
| 113 |
cap = cv2.VideoCapture(str(video_path))
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
# Setup video writer with codec fallback
|
| 116 |
codecs_to_try = [
|
| 117 |
+
('avc1', 'H.264'), # Try H.264 first
|
| 118 |
+
('mp4v', 'MPEG-4'), # Fallback to MPEG-4
|
| 119 |
+
('XVID', 'Xvid'),
|
| 120 |
+
('MJPG', 'Motion JPEG')
|
| 121 |
]
|
| 122 |
|
| 123 |
out = None
|
frontend/index.html
CHANGED
|
@@ -100,16 +100,26 @@
|
|
| 100 |
</div>
|
| 101 |
<div class="video-container">
|
| 102 |
<h3>Analyzed Video</h3>
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
Your browser does not support the video tag.
|
| 106 |
</video>
|
| 107 |
-
<div class="video-fallback" id="videoFallback" style="display: none; background: #ffebee; padding:
|
| 108 |
-
<p
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
</div>
|
| 110 |
-
<!-- <button class="btn btn-download" id="downloadBtn">
|
| 111 |
-
💾 Download
|
| 112 |
-
</button> -->
|
| 113 |
</div>
|
| 114 |
</div>
|
| 115 |
</div>
|
|
|
|
| 100 |
</div>
|
| 101 |
<div class="video-container">
|
| 102 |
<h3>Analyzed Video</h3>
|
| 103 |
+
<video
|
| 104 |
+
id="analyzedVideo"
|
| 105 |
+
controls
|
| 106 |
+
preload="metadata"
|
| 107 |
+
playsinline
|
| 108 |
+
style="max-width: 100%; height: auto; background: #000;">
|
| 109 |
Your browser does not support the video tag.
|
| 110 |
</video>
|
| 111 |
+
<div class="video-fallback" id="videoFallback" style="display: none; background: #ffebee; padding: 15px; border-radius: 8px; margin-top: 10px; text-align: center;">
|
| 112 |
+
<p style="margin: 0 0 10px 0; color: #c62828; font-weight: 500;">
|
| 113 |
+
⚠️ Video cannot be played in browser
|
| 114 |
+
</p>
|
| 115 |
+
<a
|
| 116 |
+
id="downloadFallback"
|
| 117 |
+
href="#"
|
| 118 |
+
download
|
| 119 |
+
style="display: inline-block; padding: 10px 20px; background: #ef4444; color: white; text-decoration: none; border-radius: 6px; font-weight: bold; transition: background 0.3s;">
|
| 120 |
+
💾 Download Video Instead
|
| 121 |
+
</a>
|
| 122 |
</div>
|
|
|
|
|
|
|
|
|
|
| 123 |
</div>
|
| 124 |
</div>
|
| 125 |
</div>
|
frontend/js/app.js
CHANGED
|
@@ -322,7 +322,6 @@ async function handleAnalysisComplete(message) {
|
|
| 322 |
|
| 323 |
} catch (error) {
|
| 324 |
console.error('Error fetching results:', error);
|
| 325 |
-
// Fallback to message results if API call fails
|
| 326 |
AppState.results = message.results;
|
| 327 |
}
|
| 328 |
|
|
@@ -330,11 +329,34 @@ async function handleAnalysisComplete(message) {
|
|
| 330 |
elements.processingSection.style.display = 'none';
|
| 331 |
elements.resultsSection.style.display = 'block';
|
| 332 |
|
| 333 |
-
// Load analyzed video
|
| 334 |
const videoUrl = `${API_BASE_URL}/api/download/${AppState.sessionId}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
elements.analyzedVideo.src = videoUrl;
|
|
|
|
| 336 |
|
| 337 |
-
// Display results
|
| 338 |
displayResults(AppState.results);
|
| 339 |
|
| 340 |
// Setup video sync
|
|
|
|
| 322 |
|
| 323 |
} catch (error) {
|
| 324 |
console.error('Error fetching results:', error);
|
|
|
|
| 325 |
AppState.results = message.results;
|
| 326 |
}
|
| 327 |
|
|
|
|
| 329 |
elements.processingSection.style.display = 'none';
|
| 330 |
elements.resultsSection.style.display = 'block';
|
| 331 |
|
| 332 |
+
// Load analyzed video with proper error handling
|
| 333 |
const videoUrl = `${API_BASE_URL}/api/download/${AppState.sessionId}`;
|
| 334 |
+
|
| 335 |
+
// Set up error handler BEFORE setting src
|
| 336 |
+
elements.analyzedVideo.onerror = (e) => {
|
| 337 |
+
console.error("Analyzed video failed to load:", e);
|
| 338 |
+
console.log("Video URL:", videoUrl);
|
| 339 |
+
elements.videoFallback.style.display = 'block';
|
| 340 |
+
elements.analyzedVideo.style.display = 'none';
|
| 341 |
+
document.getElementById('downloadFallback').href = videoUrl;
|
| 342 |
+
document.getElementById('downloadFallback').download = `analyzed_${AppState.uploadedFile?.name || 'video.mp4'}`;
|
| 343 |
+
};
|
| 344 |
+
|
| 345 |
+
// Set up success handler
|
| 346 |
+
elements.analyzedVideo.onloadedmetadata = () => {
|
| 347 |
+
console.log("✅ Analyzed video loaded successfully");
|
| 348 |
+
console.log("Video duration:", elements.analyzedVideo.duration);
|
| 349 |
+
console.log("Video dimensions:", elements.analyzedVideo.videoWidth, 'x', elements.analyzedVideo.videoHeight);
|
| 350 |
+
elements.videoFallback.style.display = 'none';
|
| 351 |
+
elements.analyzedVideo.style.display = 'block';
|
| 352 |
+
};
|
| 353 |
+
|
| 354 |
+
// Set video source
|
| 355 |
+
console.log("Loading analyzed video from:", videoUrl);
|
| 356 |
elements.analyzedVideo.src = videoUrl;
|
| 357 |
+
elements.analyzedVideo.load(); // Force reload
|
| 358 |
|
| 359 |
+
// Display results
|
| 360 |
displayResults(AppState.results);
|
| 361 |
|
| 362 |
// Setup video sync
|