Transform your PDFs and images into dark mode with a simple API! No more disconfort for your eyes when studying!
D2D (Docs to Dark) is a FastAPI-based web service that inverts the colors of PDFs and images, converting them to dark mode. Perfect for reading documents at night or reducing eye strain!
- π PDF Support: Convert entire PDF documents to dark mode
- πΌοΈ Image Support: PNG, JPG, JPEG, BMP, WEBP, TIFF, and GIF formats
- π¨ Themes: Pick how darkness looks β
hue(color-preserving),invert(classic),dim,sepia,midnight - π Fast Processing: Built on FastAPI; blocking conversion runs in a threadpool so the server stays responsive
- π€ Multiple Upload Options: Upload single files, batches, or via URL
- π₯οΈ CLI: Convert from the terminal with an interactive theme picker β no server needed

- π§Ή Auto Cleanup: Background scheduler removes old files automatically
- πΎ Easy Downloads: Get your converted files instantly
Every conversion takes an optional theme (default: hue). Pure inversion turns colored content into its complement (red β cyan); hue instead inverts only lightness, so colors stay recognizable in dark mode.
| Theme | What it does |
|---|---|
hue |
Inverts lightness, preserves hue/saturation (default) |
invert |
Classic negative, maximum contrast |
dim |
Softer blacks/whites, easier on the eyes |
sepia |
Warm-toned dark background |
midnight |
Cool blue-tinted dark |
GET /themes returns the live list and the default.
- Python 3.10 or higher (required by
numpy) - pip or uv (Python package manager)
- Poppler (for PDF processing)
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install poppler-utilsmacOS:
brew install popplerWindows: Download from poppler for Windows and add to PATH.
- Clone the repository:
git clone https://github.com/yourusername/D2D.git
cd D2D- Set up the environment and install dependencies:
Using uv (fast, recommended):
cd server
uv venv
uv pip install -r requirements.txtWith uv, prefix run commands with uv run and skip manual activation.
Or classic venv + pip:
cd server
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtDependencies (including
numpy, used for the theme transforms) are pinned inserver/requirements.txt. Poppler must still be installed system-wide (see above).
Start the server with:
cd server
uvicorn app:app --reload --host 0.0.0.0 --port 1313The API will be available at http://localhost:1313
cd server
docker compose up --buildConvert files straight from the terminal. Runs the same conversion engine as the API.
cd server
python cli.py document.pdf # interactive theme picker (β/β, enter)
python cli.py photo.jpg -t sepia # pick a theme, skip the picker
python cli.py photo.png -o out.png # choose the output path
python cli.py *.png --theme dim # batch convertWith uv: uv run python cli.py document.pdf.
When no -t/--theme is given and you're in a real terminal, an interactive picker appears (arrow keys or j/k, enter to select, q to quit). Piped/non-interactive runs fall back to the default theme. Output files get a _dark suffix unless -o is set.
A React + Vite UI lives in client/. It supports drag-and-drop, multi-file upload, URL upload, and a theme selector that applies to the next conversion.
cd client
npm install
npm run devThe client reads the API base URL from VITE_API_BASE_URL (client/.env, defaults to http://localhost:1313).
| Variable | Default | Purpose |
|---|---|---|
ALLOWED_ORIGINS |
* |
Comma-separated CORS origins |
CLEANUP_INTERVAL_SECONDS |
900 |
How often the cleanup scheduler runs |
FILE_MAX_AGE_SECONDS |
3600 |
Age after which uploaded/converted files are deleted |
Once the server is running, visit:
- Interactive API Docs:
http://localhost:1313/docs - Alternative Docs:
http://localhost:1313/redoc
Supported formats: PDF, and images PNG, JPG, JPEG, BMP, WEBP, TIFF, GIF.
GET /
Returns service information.
GET /themes
Returns the available themes and the default:
{ "themes": ["invert", "hue", "dim", "sepia", "midnight"], "default": "hue" }POST /upload/
Upload a single PDF or image file for conversion. Optional theme form field (default hue); an unknown theme returns 400.
Example using curl:
curl -X POST "http://localhost:1313/upload/" \
-H "accept: application/json" \
-H "Content-Type: multipart/form-data" \
-F "file=@/path/to/your/document.pdf" \
-F "theme=sepia"Response:
{
"download_url": "/download/filename_dark.pdf"
}POST /batch/
Convert multiple files in one request. Returns a per-file result list (each item is a success with download_url or an error). Optional theme form field applies to all files.
Example using curl:
curl -X POST "http://localhost:1313/batch/" \
-F "files=@/path/to/a.pdf" \
-F "files=@/path/to/b.png" \
-F "theme=midnight"Response:
{
"results": [
{ "filename": "a.pdf", "download_url": "/download/..._dark.pdf" },
{ "filename": "b.png", "download_url": "/download/..._dark.png" }
]
}POST /upload-url/?file=<url>&theme=<theme>
Provide a URL (as the file query parameter) to a PDF or image for conversion. Optional theme query parameter (default hue).
Example:
curl -X POST "http://localhost:1313/upload-url/?file=https://example.com/document.pdf&theme=dim"GET /download/{filename}
Download your converted dark mode file.
Example:
curl -O "http://localhost:1313/download/filename_dark.pdf"Python Example:
import requests
# Upload a file
with open('document.pdf', 'rb') as f:
files = {'file': f}
response = requests.post('http://localhost:1313/upload/', files=files)
download_url = response.json()['download_url']
# Download the converted file
converted = requests.get(f'http://localhost:1313{download_url}')
with open('document_dark.pdf', 'wb') as f:
f.write(converted.content)JavaScript/Node.js Example:
const FormData = require('form-data');
const fs = require('fs');
const axios = require('axios');
const form = new FormData();
form.append('file', fs.createReadStream('document.pdf'));
axios.post('http://localhost:1313/upload/', form, {
headers: form.getHeaders()
})
.then(response => {
console.log('Download URL:', response.data.download_url);
})
.catch(error => {
console.error('Error:', error);
});D2D/
βββ server/ # FastAPI backend
β βββ app.py # routes + orchestration
β βββ invertor.py # image/PDF processing + theme registry
β βββ helpers.py # extension/mime helpers
β βββ cli.py # terminal version (interactive theme picker)
β βββ requirements.txt
β βββ Dockerfile
β βββ uploads/ # ephemeral, auto-cleaned (gitignored)
βββ client/ # React + Vite + Tailwind frontend
βββ src/
βββ components/Uploader/ # drag-and-drop UI + theme selector
βββ hooks/useUpload.ts # upload state + conversion
βββ api/upload.ts # network layer
- Uploaded files are stored temporarily in the
uploads/directory - A background scheduler deletes files older than
FILE_MAX_AGE_SECONDS(default 1 hour); tune via env vars - Large PDFs may take longer to process
- Ensure adequate disk space for temporary file storage
Contributions are welcome! Please feel free to submit a Pull Request.
This project is open source and available under the MIT License.
Issue: "Poppler not found"
- Make sure Poppler is installed and accessible in your system PATH
Issue: "Module not found"
- Ensure all dependencies are installed:
pip install -r requirements.txt
Issue: "Permission denied on uploads/"
- Check folder permissions:
chmod 755 uploads/
- Batch processing support
- Support for more file formats
- File cleanup scheduler
- Theme presets (hue-preserving, sepia, dim, midnight)
- Command-line interface
- Adjustable brightness/contrast knobs
- Async jobs + progress for very large PDFs
- Preview before download
For questions or support, please open an issue on GitHub.
Made with β€οΈ for flow students and readers! Have fun!