Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions analysis/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion analysis/tests/test_server_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/s3_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down
2 changes: 1 addition & 1 deletion docs/s3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/spec-webapp.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 16 additions & 1 deletion frontend/src/embed/EmbedWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<WidgetStep>("upload")
Expand All @@ -56,10 +57,12 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => {
const [errorMessage, setErrorMessage] = useState<string | null>(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<HTMLVideoElement>(null)
const fileInputRef = useRef<HTMLInputElement>(null)

const baseUrl = resolveApiBaseUrl(apiBaseUrl)
const maxUploadSizeMb = Math.round(maxUploadSize / (1024 * 1024))

useEffect(() => {
if (!partnerId) {
Expand All @@ -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])
Expand Down Expand Up @@ -149,6 +155,13 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => {
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
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)
Expand Down Expand Up @@ -312,7 +325,9 @@ const EmbedWidget = ({ partnerId, apiBaseUrl }: EmbedWidgetProps) => {
<Upload className="w-8 h-8 text-slate-400 group-hover:text-blue-400 transition-colors" />
</div>
<p className="text-white font-medium mb-1">Upload your ski video</p>
<p className="text-slate-400 text-sm">MP4, MOV, or WebM</p>
<p className="text-slate-400 text-sm">
MP4, MOV, or WebM (Max. {maxUploadSizeMb} MB)
</p>
</div>
<input
ref={fileInputRef}
Expand Down