A FastAPI-based media server for uploading, serving, compressing, and managing files with rate limiting, authentication, and modular architecture.
Note: This project is about 1.5 years old. While it may not be the most modern or polished, it gets the job done reliably.
- File Upload & Download: Securely upload and serve files with unique filenames and expiration support.
- Image Compression: Compress images on upload, auto-rotate based on EXIF, and convert formats.
- Rate Limiting: Prevent abuse with per-IP rate limiting on API endpoints.
- Admin Authorization: Protect sensitive routes with a master key.
- Database: Async SQLite database for file metadata and management.
- Logging: Colorful, timestamped logs with file output and WebSocket support.
- Modular Routing: Auto-loads routes and pages from folders for easy extensibility.
- Configurable: Uses environment variables for all key settings.
main.py # FastAPI app entrypoint, mounts static, loads routers/pages
modules/
functions.py # Utility functions (date parsing, admin auth)
rate_limiter.py # RateLimiter class for per-IP request limiting
storage.py # Async file upload/delete to remote storage
pages/
home.py, upload.py # Page routers for HTML endpoints
routes/
upload.py # API for file upload (with admin, rate limit)
file.py # API for serving files (view/download)
delete.py # API for deleting files (admin only)
compression.py # API for image compression
services/
database.py # Async SQLite DB connection pool, file metadata
logging.py # Logger class with color, file, and WebSocket support
settings/
config.py # Loads environment variables, API/storage config
static/ # HTML templates, static assets, error pages
storage/ # Uploaded files (local storage)
- Initializes FastAPI app, mounts static files, loads routers from
routes/andpages/. - Custom error handlers for 404, 429, etc.
fetch_date(date_str): Parses date strings to UTC datetime.authorize_admin(request): Checks for valid admin token in headers.
RateLimiter(times, seconds): Per-IP request limiting as FastAPI dependency.
upload_file(bytes, filename, expires_at): Async upload to remote storage server.delete_image(image_url): Async delete from remote storage.
DatabaseConnectionPool: Async connection pool for SQLite.files_db: Handles file metadata CRUD.
Logger: Colorful, timestamped logs to file/console, supports WebSocket.
- Loads environment variables from
.envinsecrets/. ApiConfig,StorageConfig: Centralized config for API and storage.
POST /api/upload— Upload a file (admin, rate-limited)GET /file/{file_path}— Serve/download a fileDELETE /api/delete/{file_path}— Delete a file (admin)POST /api/compress— Compress an image
import requests
url = "http://localhost:8000/api/upload"
headers = {"Authorization": "Bearer <API_MASTER_KEY>"}
files = {"file": ("example.txt", open("example.txt", "rb"))}
response = requests.post(url, headers=headers, files=files)
print(response.json())const axios = require("axios");
const fs = require("fs");
const FormData = require("form-data");
const form = new FormData();
form.append("file", fs.createReadStream("example.txt"));
axios
.post("http://localhost:8000/api/upload", form, {
headers: {
...form.getHeaders(),
Authorization: "Bearer <API_MASTER_KEY>",
},
})
.then((res) => console.log(res.data))
.catch((err) => console.error(err.response?.data || err));Replace
<file_path>with the actual file path or ID returned from the upload response.
import requests
url = "http://localhost:8000/api/delete/<file_path>"
headers = {"Authorization": "Bearer <API_MASTER_KEY>"}
response = requests.delete(url, headers=headers)
print(response.json())const axios = require("axios");
axios
.delete("http://localhost:8000/api/delete/<file_path>", {
headers: { Authorization: "Bearer <API_MASTER_KEY>" },
})
.then((res) => console.log(res.data))
.catch((err) => console.error(err.response?.data || err));To compress an image, send a POST request to:
POST /api/compress/{file_path}
with a JSON body containing any of the following parameters:
quality(int, default 75): Compression quality (1-100)max_width(int, optional): Resize image to this width (maintains aspect ratio)to_webp(bool, default False): Convert to WebP formatlossless(bool, default False): Use lossless compression (WebP only)
import requests
url = "http://localhost:8000/api/compress/<file_path>"
headers = {"Authorization": "Bearer <API_MASTER_KEY>", "Content-Type": "application/json"}
data = {
"quality": 80,
"max_width": 1024,
"to_webp": True,
"lossless": False
}
response = requests.post(url, headers=headers, json=data)
print(response.json())const axios = require("axios");
const url = "http://localhost:8000/api/compress/<file_path>";
const data = {
quality: 80,
max_width: 1024,
to_webp: true,
lossless: false,
};
axios
.post(url, data, {
headers: {
Authorization: "Bearer <API_MASTER_KEY>",
"Content-Type": "application/json",
},
})
.then((res) => console.log(res.data))
.catch((err) => console.error(err.response?.data || err));- Clone the repository:
git clone https://github.com/AdnanBinPulok/SimpleMediaServer
cd MediaServer\ Public- Install dependencies:
pip install -r requirements.txt- Set up environment variables:
- Copy or create a
.envfile in thesecrets/directory. Seesettings/config.pyfor required variables.
- Run the server:
uvicorn main:app --reloadAPI_NAME,API_DESCRIPTION,API_HOST,API_PORT,API_VERSION,API_MASTER_KEY,API_BASE_URLSTORAGE_*for remote storage config
MIT