# PicUpper - Webcam Picture Upload Service A Node.js service that receives pictures from webcams and stores them in a structured way for creating time-lapse movies. ## Features - **Multi-camera support**: Handle uploads from multiple webcams simultaneously - **API key authentication**: Secure your upload endpoint - **Time-based storage**: Images organized by camera/year/month/day for easy time-lapse creation - **High-frequency uploads**: Handles uploads up to every second ## Installation ```bash cd /path/to/picUpper npm install ``` ## User Management PicUpper supports multiple users, each with their own API key. Manage users with the CLI tool: ```bash # Add a new user (returns the API key - save it!) node manage-users.js add webcam1 "Front door camera" node manage-users.js add backyard "Backyard camera" # List all users node manage-users.js list # Regenerate API key for a user node manage-users.js regenerate webcam1 # Disable/enable a user node manage-users.js disable webcam1 node manage-users.js enable webcam1 # Remove a user node manage-users.js remove webcam1 ``` Users are stored in `users.json`. Each user has an individual API key that can be used for authentication. ## Configuration | Variable | Required | Description | |----------|----------|-------------| | `PICUPPER_PORT` | Yes | Port the service listens on | | `PICUPPER_UPLOAD_DIR` | No | Custom upload directory (default: `./uploads`) | | `PICUPPER_USERS_FILE` | No | Custom users file path (default: `./users.json`) | ## Running the Service ```bash # First, create at least one user node manage-users.js add mywebcam "My webcam" # Start the service export PICUPPER_PORT=3080 npm start ``` ## API Reference ### Health Check ```bash GET /health ``` No authentication required. Returns service status. **Response:** ```json { "status": "ok", "timestamp": "2025-12-18T12:00:00.000Z", "uptime": 1234.56 } ``` ### Upload Picture ```bash POST /upload ``` Upload a picture from a webcam. **Headers:** - `X-API-Key`: Your API key **Form Data:** - `image`: The image file (JPEG, PNG, WebP, or GIF) - `cameraId`: Identifier for the camera (optional, default: "default") **Example with curl:** ```bash curl -X POST \ -H "X-API-Key: your-secret-key" \ -F "image=@snapshot.jpg" \ -F "cameraId=front-door" \ http://localhost:3080/upload ``` **Response:** ```json { "success": true, "cameraId": "front-door", "filename": "front-door_2025-12-18T12-00-00-000Z.jpg", "path": "/path/to/uploads/front-door/2025/12/18/front-door_2025-12-18T12-00-00-000Z.jpg", "size": 45678, "timestamp": "2025-12-18T12:00:00.000Z" } ``` ### List Cameras ```bash GET /cameras ``` List all cameras that have uploaded images. **Headers:** - `X-API-Key`: Your API key **Example:** ```bash curl -H "X-API-Key: your-secret-key" http://localhost:3080/cameras ``` ### Camera Statistics ```bash GET /stats/:cameraId ``` Get upload statistics for a specific camera. **Example:** ```bash curl -H "X-API-Key: your-secret-key" http://localhost:3080/stats/front-door ``` **Response:** ```json { "cameraId": "front-door", "totalImages": 1440, "totalSizeBytes": 72000000, "totalSizeMB": "68.66", "oldestImage": "2025-12-01T00:00:00.000Z", "newestImage": "2025-12-18T12:00:00.000Z" } ``` ### Get Camera Settings ```bash GET /settings/:cameraId ``` Get v4l2 camera settings (focus, exposure, etc.) for a camera. The camclient automatically registers available controls on first connection. **Example:** ```bash curl -H "X-API-Key: your-secret-key" http://localhost:3080/settings/front-door ``` **Response:** ```json { "cameraId": "front-door", "availableControls": [ {"name": "brightness", "type": "int", "min": -64, "max": 64, "step": 1, "default": 0}, {"name": "exposure_auto", "type": "menu", "min": 0, "max": 3, "default": 3, "options": {"1": "Manual Mode", "3": "Aperture Priority Mode"}} ], "values": { "brightness": 10, "focus_absolute": 40 }, "config": { "rotation": null, "crop": null, "ocr": null, "chartLabel": null, "insertBrightnessToDb": false }, "updatedAt": "2025-12-21T22:00:00.000Z", "updatedBy": "webcam1" } ``` ### Register Available Controls ```bash POST /settings/:cameraId/available ``` Called by the camclient to register all available v4l2 controls with their metadata. The camclient parses `v4l2-ctl -L` output and reports the schema. **Request Body:** ```json { "controls": [ {"name": "brightness", "type": "int", "min": -64, "max": 64, "step": 1, "default": 0}, {"name": "exposure_auto", "type": "menu", "options": {"1": "Manual", "3": "Auto"}, "default": 3} ], "currentValues": { "brightness": 0, "exposure_auto": 3 } } ``` ### Update Camera Settings ```bash PUT /settings/:cameraId ``` Update camera v4l2 control values or config settings (rotation, crop, ocr). Settings are applied by the capture client via v4l2-ctl. **Example:** ```bash curl -X PUT \ -H "X-API-Key: your-secret-key" \ -H "Content-Type: application/json" \ -d '{"focus_absolute": 40, "exposure_absolute": 250}' \ http://localhost:3080/settings/front-door ``` ## Storage Structure Images are stored in a hierarchical directory structure optimized for time-lapse processing: ``` uploads/ └── {cameraId}/ └── {YYYY}/ └── {MM}/ └── {DD}/ └── {cameraId}_{timestamp}.jpg ``` This structure allows files to be concatenated in chronological order for movie creation. ## Nginx Configuration **Current deployment:** `https://dev.seedheads.de/picUploadApi/` To expose the API under `/picUploadApi/` on your website, add this to your nginx configuration: ```nginx location /picUploadApi/ { # Strip the /picUploadApi prefix when proxying rewrite ^/picUploadApi/(.*)$ /$1 break; proxy_pass http://127.0.0.1:3080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Important for file uploads client_max_body_size 5M; proxy_request_buffering off; } ``` After adding, reload nginx: ```bash sudo nginx -t && sudo systemctl reload nginx ``` Now you can access the API at `https://yourwebsite.com/picUploadApi/upload`. ## Creating Time-Lapse Movies Once you have collected images, you can create a time-lapse movie using ffmpeg: ```bash # Navigate to a specific day's images cd uploads/front-door/2025/12/18 # Create a video at 30fps (each frame shows for 1/30th of a second) ffmpeg -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p -r 30 timelapse.mp4 # Or create a slower video at 10fps ffmpeg -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p -r 10 timelapse-slow.mp4 ``` For multi-day time-lapses: ```bash # Create a file list find uploads/front-door/2025/12 -name '*.jpg' | sort > filelist.txt # Convert to ffmpeg format sed "s/^/file '/; s/$/'/" filelist.txt > ffmpeg-list.txt # Create video from list ffmpeg -f concat -safe 0 -i ffmpeg-list.txt -c:v libx264 -pix_fmt yuv420p -r 30 december-timelapse.mp4 ``` ## Webcam Upload Script Example Here's a simple bash script to capture and upload from a webcam: ```bash #!/bin/bash # capture-and-upload.sh API_URL="http://localhost:3080/upload" API_KEY="your-secret-key" CAMERA_ID="webcam1" TEMP_FILE="/tmp/webcam_snapshot.jpg" # Capture from webcam (requires fswebcam) fswebcam -r 1920x1080 --no-banner "$TEMP_FILE" # Upload to PicUpper curl -s -X POST \ -H "X-API-Key: $API_KEY" \ -F "image=@$TEMP_FILE" \ -F "cameraId=$CAMERA_ID" \ "$API_URL" rm -f "$TEMP_FILE" ``` Run it every minute with cron: ```bash * * * * * /path/to/capture-and-upload.sh ``` ## License MIT