Skip to content

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:

VariableDescriptionRequiredDefault
APP_ALLOWED_ORIGINSComma-separated list of allowed hostnamesNoEmpty (allows all)
APP_ADDRESSAddress to listen onNo:3000
APP_PREFORKEnable preforkingNofalse
APP_METRICSEnable metricsNotrue
APP_WEBPDefault to WebP conversionNofalse
APP_CACHE_TTL_SECONDSCache TTL in secondsNo1800 (30 minutes)
APP_CACHE_MAX_COSTCache max cost in bytesNo1073741824 (1GB)
APP_CACHE_NUM_COUNTERSCache num countersNo10000000 (10M)
APP_CACHE_BUFFER_ITEMSCache buffer itemsNo64
APP_TOKENToken for image upload authenticationNoEmpty
APP_HMAC_KEYHMAC key for URL signingNoEmpty
APP_UPLOADING_ENABLEDEnable video uploading to S3Nofalse
APP_CHUNK_SIZEChunk size for multi-part uploads (bytes)No83886080 (80MB)
REDIS_ENABLEDEnable Redis for multi-part upload trackingNofalse
REDIS_ADDRRedis server addressNolocalhost:6379
REDIS_PASSWORDRedis passwordNoEmpty
REDIS_DBRedis database numberNo0

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 trackingexport 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)

constcrypto=require('crypto');functiongenerateVideoUploadSignature(hmacKey,deadline,location){// Create message: deadline|locationconstmessage=`${deadline}|${location}`;// Generate HMAC-SHA256 signatureconsthmac=crypto.createHmac('sha256',hmacKey);hmac.update(message);constsignature=hmac.digest('hex');// Encode location in base64 URL-safe formatconstlocationEncoded=Buffer.from(location).toString('base64').replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'');return{ signature, locationEncoded, deadline };}// UsageconsthmacKey=process.env.APP_HMAC_KEY;constdeadline=Math.floor(Date.now()/1000)+(5*60);// 5 minutesconstlocation='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 APIconstformData=newFormData();formData.append('video',videoFile);constuploadUrl=newURL('http://localhost:3000/videos');uploadUrl.searchParams.set('deadline',deadline);uploadUrl.searchParams.set('location',locationEncoded);uploadUrl.searchParams.set('signature',signature);constresponse=awaitfetch(uploadUrl,{method: 'POST',body: formData});constresult=awaitresponse.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 encodingecho -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 OpenSSLecho -n "https://example.com/image.jpg"| openssl dgst -sha256 -hmac "your-hmac-key" -binary | xxd -p
# Using Python
python3 -c "import hmacimport hashliburl = '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
curl -X POST "http://localhost:3000/images/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 minutesconst 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 "video=@video.mp4"

HTML Integration

<!-- Path-based format (recommended) --><imgsrc="http://localhost:3000/images/q:80/webp/aHR0cHM6Ly9leGFtcGxlLmNvbS9pbWFnZS5qcGc=" alt="WebP optimized image"><!-- Video thumbnail --><imgsrc="http://localhost:3000/videos/preview/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" alt="Video thumbnail"><!-- Video thumbnail from middle of video --><imgsrc="http://localhost:3000/videos/preview/fp:half/aHR0cHM6Ly9leGFtcGxlLmNvbS92aWRlby5tcDQ=" alt="Video thumbnail from middle"><!-- Video proxy for HTML5 video player --><videocontrols><sourcesrc="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 variablesexport 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 sales@nnstd.dev.

About

A media proxy server with FFmpeg support

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages