sebseb7 a913162b6e u
2025-12-21 23:00:05 +01:00
u
2025-12-21 23:00:05 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00
u
2025-12-21 22:48:26 +01:00

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

cd /path/to/picUpper
npm install

User Management

PicUpper supports multiple users, each with their own API key. Manage users with the CLI tool:

# 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

# 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

GET /health

No authentication required. Returns service status.

Response:

{
  "status": "ok",
  "timestamp": "2025-12-18T12:00:00.000Z",
  "uptime": 1234.56
}

Upload Picture

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:

curl -X POST \
  -H "X-API-Key: your-secret-key" \
  -F "image=@snapshot.jpg" \
  -F "cameraId=front-door" \
  http://localhost:3080/upload

Response:

{
  "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

GET /cameras

List all cameras that have uploaded images.

Headers:

  • X-API-Key: Your API key

Example:

curl -H "X-API-Key: your-secret-key" http://localhost:3080/cameras

Camera Statistics

GET /stats/:cameraId

Get upload statistics for a specific camera.

Example:

curl -H "X-API-Key: your-secret-key" http://localhost:3080/stats/front-door

Response:

{
  "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

GET /settings/:cameraId

Get v4l2 camera settings (focus, exposure, etc.) for a camera. The camclient automatically registers available controls on first connection.

Example:

curl -H "X-API-Key: your-secret-key" http://localhost:3080/settings/front-door

Response:

{
  "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

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:

{
  "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

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:

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:

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:

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:

# 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:

# 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:

#!/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:

* * * * * /path/to/capture-and-upload.sh

License

MIT

Description
No description provided
Readme 247 KiB
Languages
JavaScript 71.2%
HTML 28.8%