Skip to content

nnstd/media-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Media Proxy

Optional S3 result storage

Set these environment variables to enable persistent result caching in S3/MinIO. If an object exists, it will be served directly without reprocessing.

  • S3_ENABLED (bool) — enable S3 storage, default false
  • S3_ENDPOINT — e.g. play.min.io:9000 or your MinIO/S3 endpoint
  • S3_ACCESS_KEY_ID
  • S3_SECRET_ACCESS_KEY
  • S3_BUCKET — target bucket
  • S3_SSL (bool) — default true for S3, false for plain MinIO if needed
  • S3_PREFIX — optional key prefix, e.g. media-proxy/

MinIO Go SDK is used under the hood. See the official docs: minio/minio-go.

A high-performance media proxy service built with Go and Fiber that provides secure proxying for images and video preview generation. This service allows you to proxy media content from allowed origins while maintaining security and performance.

Features

  • Image Proxying: Proxy images from allowed origins with optional quality control, WebP conversion, resizing, and rescaling
  • WebP Conversion: Convert any supported image format to WebP with quality optimization
  • Video Preview Generation: Extract frame thumbnails from video files at specific positions (first, middle, last, or custom time)
  • Origin Validation: Whitelist-based origin control for security
  • MIME Type Validation: Strict content type checking for both images and videos
  • Health Checks: Built-in health check endpoint
  • Compression: Automatic response compression
  • Structured Logging: JSON-formatted logging with Zap
  • Path-based Parameters: Clean URL structure with parameters in the path
  • HMAC Signatures: Optional URL signing for enhanced security
  • Image Upload: Direct image upload with processing capabilities

Supported Media Types

Images

  • JPEG (image/jpeg)
  • PNG (image/png)
  • WebP (image/webp)
  • GIF (image/gif)
  • BMP (image/bmp)
  • TIFF (image/tiff)
  • AVIF (image/avif)

Documents

  • PDF (application/pdf)
  • EPUB (application/epub+zip)
  • MOBI (application/x-mobipocket-ebook)
  • DOCX (application/vnd.openxmlformats-officedocument.wordprocessingml.document)
  • XLSX (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
  • PPTX (application/vnd.openxmlformats-officedocument.presentationml.presentation)

Videos

  • MP4 (video/mp4)
  • OGG (video/ogg)
  • WebM (video/webm)
  • QuickTime (video/quicktime)
  • AVI (video/x-msvideo)
  • Matroska (video/x-matroska)
  • FLV (video/x-flv)
  • M4V (video/x-m4v)

Installation

Prerequisites

  • Go 1.24 or later
  • FFmpeg libraries (for video processing)

Clone and Build

git clone <repository-url>
cd media-proxy
go mod download
go build -o media-proxy

Docker

docker build -t media-proxy .
docker run -p 3000:3000 -e APP_ALLOWED_ORIGINS=example.com,cdn.example.com media-proxy

Configuration

The service is configured via environment variables:

Variable Description Required Default
APP_ALLOWED_ORIGINS Comma-separated list of allowed hostnames No Empty (allows all)
APP_ADDRESS Address to listen on No :3000
APP_PREFORK Enable preforking No false
APP_METRICS Enable metrics No true
APP_WEBP Default to WebP conversion No false
APP_CACHE_TTL_SECONDS Cache TTL in seconds No 1800 (30 minutes)
APP_CACHE_MAX_COST Cache max cost in bytes No 1073741824 (1GB)
APP_CACHE_NUM_COUNTERS Cache num counters No 10000000 (10M)
APP_CACHE_BUFFER_ITEMS Cache buffer items No 64
APP_TOKEN Token for image upload authentication No Empty
APP_HMAC_KEY HMAC key for URL signing No Empty
APP_UPLOADING_ENABLED Enable video uploading to S3 No false
APP_CHUNK_SIZE Chunk size for multi-part uploads (bytes) No 83886080 (80MB)
REDIS_ENABLED Enable Redis for multi-part upload tracking No false
REDIS_ADDR Redis server address No localhost:6379
REDIS_PASSWORD Redis password No Empty
REDIS_DB Redis database number No 0

Example Configuration

export APP_ALLOWED_ORIGINS="example.com,cdn.example.com,media.example.org"
export APP_TOKEN="your-upload-token"
export APP_HMAC_KEY="your-hmac-secret-key"
export APP_UPLOADING_ENABLED=true  # Enable video uploads

# Optional: Redis for multi-part upload tracking
export REDIS_ENABLED=true
export REDIS_ADDR="localhost:6379"
export REDIS_PASSWORD=""
export REDIS_DB=0
export APP_CHUNK_SIZE=83886080  # 80MB chunks

API Endpoints

Health Check

GET /health

Returns the health status of the service.

Image Proxy

New Path-based Format (Recommended)

GET /images/q:<quality>/w:<width>/h:<height>/s:<scale>/i:<interpolation>/webp/sig:<signature>/{base64-encoded-url}

Path Parameters:

  • q or quality: Image quality for optimization (1-100, default: 100)
  • w or width: Width of the image (default: 0)
  • h or height: Height of the image (default: 0)
  • s or scale: Scale factor for the image (0-1, default: 0)
  • i or interpolation: Interpolation method for resizing (0-5, default: 5)
  • webp: Force conversion to WebP format (flag, no value needed)
  • sig or signature: HMAC signature for URL validation (optional)
  • {base64-encoded-url}: Base64 URL-encoded image URL (required)

Interpolation methods:

  • 0: Nearest-neighbor interpolation
  • 1: Bilinear interpolation
  • 2: Bicubic interpolation
  • 3: Mitchell-Netravali interpolation
  • 4: Lanczos2 interpolation
  • 5: Lanczos3 interpolation

Examples:

# Basic image proxy (original format)
curl "http://localhost:3000/images/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc="

# Convert to WebP with quality optimization
curl "http://localhost:3000/images/q:85/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc="

# Resize image with specific dimensions
curl "http://localhost:3000/images/w:800/h:600/q:90/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc="

# Rescale image with custom interpolation
curl "http://localhost:3000/images/s:0.5/i:2/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc="

# With HMAC signature for security
curl "http://localhost:3000/images/sig:abc123/q:75/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc="

Video Preview

Path-based Format

GET /videos/preview/q:<quality>/w:<width>/h:<height>/s:<scale>/i:<interpolation>/fp:<framePosition>/webp/sig:<signature>/{base64-encoded-url}

Path Parameters:

  • q or quality: Image quality for optimization (1-100, default: 100)
  • w or width: Width of the image (default: 0)
  • h or height: Height of the image (default: 0)
  • s or scale: Scale factor for the image (0-1, default: 0)
  • i or interpolation: Interpolation method for resizing (0-5, default: 5)
  • fp or framePosition: Frame position to extract (default: "first")
  • webp: Force conversion to WebP format (flag, no value needed)
  • sig or signature: HMAC signature for URL validation (optional)
  • {base64-encoded-url}: Base64 URL-encoded video URL (required)

Frame Position Options:

  • first: Extract the first frame (default)
  • half: Extract a frame from the middle of the video
  • last: Extract the last frame
  • 30.5: Extract a frame at 30.5 seconds (supports decimal seconds)

Examples:

# Basic video preview (first frame)
curl "http://localhost:3000/videos/preview/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="

# Video preview from the middle of the video
curl "http://localhost:3000/videos/preview/fp:half/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="

# Video preview from the last frame
curl "http://localhost:3000/videos/preview/fp:last/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="

# Video preview from 30 seconds into the video
curl "http://localhost:3000/videos/preview/fp:30/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="

# Video preview from the middle with resizing and WebP conversion
curl "http://localhost:3000/videos/preview/fp:half/w:320/h:240/q:85/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ="

Response:

  • Returns a JPEG or WebP thumbnail of the extracted frame
  • Content-Type: image/jpeg or image/webp (if WebP conversion is enabled)
  • Cache-Control: public, max-age=3600
  • Validates that the URL origin is in the allowed list
  • Validates that the content type is a supported video format

Video Proxy

Path-based Format

GET /videos/sig:<signature>/{base64-encoded-url}

Path Parameters:

  • sig or signature: HMAC signature for URL validation (optional for HTTP/HTTPS origins, required for S3 explicit locations)
  • {base64-encoded-url}: Base64 URL-encoded video URL or S3 location (required)

Features:

  • Supports HTTP Range requests for video streaming (partial content)
  • Proxies raw video bytes from HTTP/HTTPS origins
  • Supports proxying from S3/MinIO storage (if explicit location provided)
  • Forwards relevant headers (Content-Type, Accept-Ranges, Content-Length, Content-Range)
  • Returns appropriate HTTP status codes (200 OK or 206 Partial Content)

Examples:

# Proxy video from HTTP origin
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -o video.mp4

# Proxy video with Range request (first 1MB)
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -H "Range: bytes=0-1048575" \
  -o video_partial.mp4

# Proxy video from S3 location (requires signature)
curl "http://localhost:3000/videos/sig:abc123/aHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL2J1Y2tldC92aWRlby5tcDQ=" \
  -o video.mp4

Response:

  • Returns raw video bytes
  • Content-Type: Original video content type (e.g., video/mp4)
  • Accept-Ranges: bytes
  • Supports Content-Range header for partial content responses (206 Partial Content)
  • Validates that the URL origin is in the allowed list (for HTTP/HTTPS origins)
  • Validates HMAC signature for S3 explicit locations

Video Upload

Upload videos directly to S3 storage with secure signature-based authentication.

Configuration

export APP_UPLOADING_ENABLED=true
export APP_HMAC_KEY="your-secret-hmac-key"
export S3_ENABLED=true
export S3_ENDPOINT="s3.amazonaws.com"
export S3_ACCESS_KEY_ID="your-access-key"
export S3_SECRET_ACCESS_KEY="your-secret-key"
export S3_BUCKET="your-bucket"
export S3_SSL=true
export APP_MAX_VIDEO_SIZE_MB=100

Endpoint

POST /videos?deadline={unix_timestamp}&location={base64_location}&signature={hmac_signature}

Query Parameters:

  • deadline: Unix timestamp when the upload permission expires (required)
  • location: Base64 URL-safe encoded S3 object key where the video will be stored (required)
  • signature: HMAC-SHA256 signature of deadline|location (required)

Form Data:

  • video: The video file to upload (required)

Response (201 Created):

{
  "location": "videos/user123/my-video.mp4",
  "size": 1048576
}

Generate Upload Signature (Node.js)

const crypto = require('crypto');

function generateVideoUploadSignature(hmacKey, deadline, location) {
    // Create message: deadline|location
    const message = `${deadline}|${location}`;
    
    // Generate HMAC-SHA256 signature
    const hmac = crypto.createHmac('sha256', hmacKey);
    hmac.update(message);
    const signature = hmac.digest('hex');
    
    // Encode location in base64 URL-safe format
    const locationEncoded = Buffer.from(location)
        .toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
    
    return { signature, locationEncoded, deadline };
}

// Usage
const hmacKey = process.env.APP_HMAC_KEY;
const deadline = Math.floor(Date.now() / 1000) + (5 * 60); // 5 minutes
const location = 'videos/user123/my-video.mp4';

const { signature, locationEncoded } = generateVideoUploadSignature(
    hmacKey,
    deadline,
    location
);

console.log(`deadline=${deadline}`);
console.log(`location=${locationEncoded}`);
console.log(`signature=${signature}`);

Upload Video Example

# Using cURL
curl -X POST \
  "http://localhost:3000/videos?deadline=1730678400&location=dmlkZW9zL3VzZXIxMjMvbXktdmlkZW8ubXA0&signature=abc123..." \
  -F "video=@/path/to/video.mp4"
// Using JavaScript/Fetch API
const formData = new FormData();
formData.append('video', videoFile);

const uploadUrl = new URL('http://localhost:3000/videos');
uploadUrl.searchParams.set('deadline', deadline);
uploadUrl.searchParams.set('location', locationEncoded);
uploadUrl.searchParams.set('signature', signature);

const response = await fetch(uploadUrl, {
    method: 'POST',
    body: formData
});

const result = await response.json();
console.log('Upload result:', result);
// Output: { success: true, location: "videos/user123/my-video.mp4", size: 1048576 }

Security Features

  • Deadline-based expiration: Upload permissions expire after the specified deadline
  • HMAC signature validation: Prevents unauthorized uploads
  • S3 location control: Videos are uploaded to predetermined S3 paths
  • MIME type validation: Only video files are accepted
  • File size limits: Configure APP_MAX_VIDEO_SIZE_MB to prevent large uploads
  • Path sanitization: Prevents directory traversal and invalid characters

For more details, see VIDEO_UPLOAD.md.

Multi-Part Video Upload

For large video files, the service supports multi-part uploads with progress tracking via Redis.

For detailed documentation including API endpoints, parameters, examples, and security model, see MULTIPART_UPLOAD.md.

URL Encoding for Path-based Format

For the new path-based format, you need to base64 URL-encode your image/video URLs:

# Example URL encoding
echo -n "https://example.com/image.jpg" | base64 -w 0 | sed 's/+/-/g' | sed 's/\//_/g' | sed 's/=//g'
# Result: aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc

HMAC Signature Generation

To generate HMAC signatures for secure URL validation:

# Using OpenSSL
echo -n "https://example.com/image.jpg" | openssl dgst -sha256 -hmac "your-hmac-key" -binary | xxd -p

# Using Python
python3 -c "
import hmac
import hashlib
url = 'https://example.com/image.jpg'
key = 'your-hmac-key'
signature = hmac.new(key.encode(), url.encode(), hashlib.sha256).hexdigest()
print(signature)
"

Usage Examples

Basic Image Proxying

# Path-based format (recommended)
curl "http://localhost:3000/images/q:85/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS9waG90by5qcGc="

Video Preview Generation

# Path-based format - first frame
curl "http://localhost:3000/videos/preview/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -H "Accept: image/jpeg" \
  -o thumbnail.jpg

# Path-based format - frame from middle of video
curl "http://localhost:3000/videos/preview/fp:half/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -H "Accept: image/jpeg" \
  -o thumbnail.jpg

Video Proxying

# Proxy full video
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -o video.mp4

# Proxy video with Range request
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" \
  -H "Range: bytes=0-1048575" \
  -o video_partial.mp4

Image Upload

# Path-based format (recommended)
curl -X POST "http://localhost:3000/images/upload/q:90/webp/t:your-token/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc=" \
  --data-binary @image.jpg

Video Upload

# Generate signature (Node.js example)
node -e "
const crypto = require('crypto');
const hmacKey = 'your-secret-hmac-key';
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes
const location = 'videos/user123/my-video.mp4';
const message = \`\${deadline}|\${location}\`;
const signature = crypto.createHmac('sha256', hmacKey).update(message).digest('hex');
const locationEncoded = Buffer.from(location).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log(\`deadline=\${deadline}&location=\${locationEncoded}&signature=\${signature}\`);
"

# Upload video using generated parameters
curl -X POST \
  "http://localhost:3000/videos?deadline=1730678400&location=dmlkZW9zL3VzZXIxMjMvbXktdmlkZW8ubXA0&signature=abc123..." \
  -F "[email protected]"

HTML Integration

<!-- Path-based format (recommended) -->
<img src="http://localhost:3000/images/q:80/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc=" 
     alt="WebP optimized image">


<!-- Video thumbnail -->
<img src="http://localhost:3000/videos/preview/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" 
     alt="Video thumbnail">

<!-- Video thumbnail from middle of video -->
<img src="http://localhost:3000/videos/preview/fp:half/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" 
     alt="Video thumbnail from middle">

<!-- Video proxy for HTML5 video player -->
<video controls>
  <source src="http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" type="video/mp4">
</video>

Security Features

  1. Origin Validation: Only URLs from configured allowed origins are processed
  2. MIME Type Validation: Strict content type checking prevents processing of non-media files
  3. URL Parsing: Robust URL validation prevents malformed requests
  4. No Direct File Access: Service only processes HTTP/HTTPS URLs
  5. HMAC Signatures: Optional URL signing for enhanced security
  6. Token Authentication: Required authentication for image upload operations

Development

Running Locally

# Set environment variables
export APP_ALLOWED_ORIGINS="localhost,127.0.0.1,example.com"
export APP_TOKEN="your-upload-token"
export APP_HMAC_KEY="your-hmac-secret-key"

# Run the service
go run .

The service will start on port 3000.

Testing

# Test path-based image proxy
curl "http://localhost:3000/images/aHR0cHM6Ly9odHRwYmluLm9yZy9pbWFnZS9qcGVn"
# Test path-based video preview (first frame)
curl "http://localhost:3000/videos/preview/aHR0cHM6Ly9leGFtcGxlLmNvbS9zYW1wbGUubXA0"

# Test path-based video preview (middle frame)
curl "http://localhost:3000/videos/preview/fp:half/aHR0cHM6Ly9leGFtcGxlLmNvbS9zYW1wbGUubXA0"

# Test video proxy
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS9zYW1wbGUubXA0" \
  -o test_video.mp4

# Test video proxy with Range request
curl "http://localhost:3000/videos/aHR0cHM6Ly9leGFtcGxlLmNvbS9zYW1wbGUubXA0" \
  -H "Range: bytes=0-1048575" \
  -o test_video_partial.mp4

# Test health check
curl "http://localhost:3000/health"

License

Media-server is licensed under the GNU Affero General Public License v3.0 and as commercial software. For commercial licensing, please contact us at [email protected].