From 915d54081f3642b05fd5db70e59d24c4fbfd6b70 Mon Sep 17 00:00:00 2001 From: Christian Hansen Date: Wed, 21 Jan 2026 14:02:18 +0100 Subject: [PATCH] Set upload limits to 250MB --- README.md | 4 ++-- analysis/server.py | 4 ++-- analysis/tests/test_server_progress.py | 2 +- backend/app/config.py | 2 +- backend/app/s3_service.py | 2 +- docs/s3-migration.md | 2 +- docs/spec-webapp.md | 2 +- frontend/src/embed/EmbedWidget.tsx | 17 ++++++++++++++++- 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 4d0c08ca..d61c56c4 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ output/ski_run_mp4/ ## Limitations -- **Maximum video file size**: 50MB +- **Maximum video file size**: 250MB - **Memory usage**: Videos are processed frame-by-frame to minimize memory usage - **Processing time**: Varies based on video length and resolution @@ -203,7 +203,7 @@ The frontend has been completely rebuilt with a modern tech stack: - **Responsive Design**: Mobile-friendly interface that works on all screen sizes - **Dark Mode**: Professional dark theme optimized for extended use - **Real-time Updates**: Automatic polling for analysis status with live progress indicators -- **Drag & Drop**: Intuitive file upload with video preview before processing (max 50MB) +- **Drag & Drop**: Intuitive file upload with video preview before processing (max 250MB) - **Email Authentication**: Passwordless authentication flow with verification codes - **Results Dashboard**: Interactive results viewer with sidebar navigation and downloadable outputs diff --git a/analysis/server.py b/analysis/server.py index b173e5d7..486a5631 100644 --- a/analysis/server.py +++ b/analysis/server.py @@ -292,12 +292,12 @@ async def run_analysis_subprocess(request: AnalyzeRequest): # Check video file size to prevent OOM file_size_mb = input_path.stat().st_size / (1024 * 1024) - if file_size_mb > 50: # 50MB limit + if file_size_mb > 250: # 250MB limit logger.warning(f"Video file too large: {file_size_mb:.1f}MB") await update_analysis_status( analysis_id, "failed", - error=f"Video file too large ({file_size_mb:.1f}MB). Maximum size is 50MB.", + error=f"Video file too large ({file_size_mb:.1f}MB). Maximum size is 250MB.", ) return diff --git a/analysis/tests/test_server_progress.py b/analysis/tests/test_server_progress.py index 677a56b0..8f2f62de 100644 --- a/analysis/tests/test_server_progress.py +++ b/analysis/tests/test_server_progress.py @@ -45,7 +45,7 @@ async def mock_async_analysis(*args, **kwargs): # Mock file size check for the input video with patch("analysis.server.Path.stat") as mock_stat: mock_stat_result = MagicMock() - mock_stat_result.st_size = 40 * 1024 * 1024 # 40MB (under 50MB limit) + mock_stat_result.st_size = 40 * 1024 * 1024 # 40MB (under 250MB limit) mock_stat.return_value = mock_stat_result # Mock glob to return empty list (no output files to upload) diff --git a/backend/app/config.py b/backend/app/config.py index 391f229b..96292bb1 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -12,7 +12,7 @@ class Settings(BaseSettings): JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "development-secret-key") JWT_ALGORITHM: str = "HS256" JWT_EXPIRATION_HOURS: int = 24 - MAX_UPLOAD_SIZE: int = 100 * 1024 * 1024 # 100MB + MAX_UPLOAD_SIZE: int = 250 * 1024 * 1024 # 250MB ANALYSIS_SERVICE_URL: str = os.getenv( "ANALYSIS_SERVICE_URL", "http://analysis:8080" ) diff --git a/backend/app/s3_service.py b/backend/app/s3_service.py index 34d5efdd..e641ab45 100644 --- a/backend/app/s3_service.py +++ b/backend/app/s3_service.py @@ -103,7 +103,7 @@ def generate_presigned_post( analysis_id: str, filename: str, content_type: str = "video/mp4", - max_size: int = 50 * 1024 * 1024, # 50MB default + max_size: int = settings.MAX_UPLOAD_SIZE, expires_in: int = 900, # 15 minutes ) -> dict: """ diff --git a/docs/s3-migration.md b/docs/s3-migration.md index 70537db9..750c676a 100644 --- a/docs/s3-migration.md +++ b/docs/s3-migration.md @@ -146,7 +146,7 @@ If you encounter CORS errors with S3: 3. Verify SSL settings match environment ### Upload Failures -1. Check file size limits (50MB default) +1. Check file size limits (250MB default) 2. Verify S3 credentials 3. Check bucket permissions 4. Review browser console for errors diff --git a/docs/spec-webapp.md b/docs/spec-webapp.md index a9a68eab..e8fff46a 100644 --- a/docs/spec-webapp.md +++ b/docs/spec-webapp.md @@ -204,7 +204,7 @@ The system will run as a set of four services orchestrated by Docker Compose. A #### **8.2. Infrastructure as Code (Docker & Caddy)** -1. **Caddyfile:** Manages traffic and auto-HTTPS for `poser.pro`. It must have a `max_size 50MB` directive for `request_body`. +1. **Caddyfile:** Manages traffic and auto-HTTPS for `poser.pro`. It must have a `max_size 250MB` directive for `request_body`. 2. **`docker-compose.prod.yml`:** Defines the four production services. ```yaml services: diff --git a/frontend/src/embed/EmbedWidget.tsx b/frontend/src/embed/EmbedWidget.tsx index b4a236dc..7e647837 100644 --- a/frontend/src/embed/EmbedWidget.tsx +++ b/frontend/src/embed/EmbedWidget.tsx @@ -34,6 +34,7 @@ type EmbedWidgetProps = { } const DEFAULT_MAX_TRIM_SECONDS = 20 +const DEFAULT_MAX_UPLOAD_SIZE = 250 * 1024 * 1024 const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => { const [step, setStep] = useState("upload") @@ -56,10 +57,12 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => { const [errorMessage, setErrorMessage] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) const [maxTrimSeconds, setMaxTrimSeconds] = useState(DEFAULT_MAX_TRIM_SECONDS) + const [maxUploadSize, setMaxUploadSize] = useState(DEFAULT_MAX_UPLOAD_SIZE) const videoRef = useRef(null) const fileInputRef = useRef(null) const baseUrl = resolveApiBaseUrl(apiBaseUrl) + const maxUploadSizeMb = Math.round(maxUploadSize / (1024 * 1024)) useEffect(() => { if (!partnerId) { @@ -71,6 +74,9 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => { if (config.max_trim_seconds) { setMaxTrimSeconds(config.max_trim_seconds) } + if (config.max_upload_size) { + setMaxUploadSize(config.max_upload_size) + } }) .catch(() => {}) }, [baseUrl, partnerId]) @@ -149,6 +155,13 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => { const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file && file.type.startsWith("video/")) { + if (file.size > maxUploadSize) { + setErrorMessage( + `Please upload a video smaller than ${maxUploadSizeMb} MB.` + ) + e.target.value = "" + return + } setVideoFile(file) const url = URL.createObjectURL(file) setVideoUrl(url) @@ -312,7 +325,9 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => {

Upload your ski video

-

MP4, MOV, or WebM

+

+ MP4, MOV, or WebM (Max. {maxUploadSizeMb} MB) +