342 lines
7.7 KiB
Markdown
342 lines
7.7 KiB
Markdown
# 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
|