Simple web application built with a python / flask back end for video compression using ffmpeg. It's not super flexible. I made it to take my giant 3.5 GB video file tv episodes, and compress them to 1/3 to 1/2 the size. It does this well, and saves a ton of storage over time.
  • Python 45%
  • JavaScript 27.4%
  • CSS 14.9%
  • HTML 9.8%
  • Dockerfile 2%
  • Other 0.9%
Find a file
2026-04-06 14:40:28 -05:00
__pycache__ Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00
app Added a scheduler and the otpion to compress now or later, as well as timezone settings. 2026-04-06 14:35:57 -05:00
node_modules Added better resume progress viewing options, and updated README.md 2026-04-03 06:38:45 -05:00
screens Updated README with new capabilities screenshots 2026-03-17 14:11:27 -05:00
static Added a scheduler and the otpion to compress now or later, as well as timezone settings. 2026-04-06 14:35:57 -05:00
templates Added a scheduler and the otpion to compress now or later, as well as timezone settings. 2026-04-06 14:35:57 -05:00
tests Updated tests, README, and added USAGE instructions 2026-04-06 14:40:28 -05:00
app.py.archive Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00
docker-compose.yml Added better resume progress viewing options, and updated README.md 2026-04-03 06:38:45 -05:00
Dockerfile Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00
gunicorn.conf.py Initial Commit 2026-03-09 17:42:26 -05:00
package-lock.json Added better resume progress viewing options, and updated README.md 2026-04-03 06:38:45 -05:00
package.json Add a tabbed view and testing to the suite. 2026-04-01 18:20:29 -05:00
pytest.ini Add a tabbed view and testing to the suite. 2026-04-01 18:20:29 -05:00
README.md Updated tests, README, and added USAGE instructions 2026-04-06 14:40:28 -05:00
requirements.txt Add a tabbed view and testing to the suite. 2026-04-01 18:20:29 -05:00
run.py Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00
start.sh Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00
USAGE.md Updated tests, README, and added USAGE instructions 2026-04-06 14:40:28 -05:00
vitest.config.js Add a tabbed view and testing to the suite. 2026-04-01 18:20:29 -05:00
wsgi.py Updates galore. Improved folder structure, componentized, and notifications upon completion. 2026-03-17 14:01:35 -05:00

VideoPress — FFmpeg Video Compressor

NOTE: This application was built with the help of AI.

  • The application code has been reviewed by this developer.
  • The application code has been tested by this developer.

If you find any issues, please feel free to post an issue, and / or a pull request for a fix.

What is VideoPress?

A web-based video compression tool served via Gunicorn + gevent (WSGI), containerised with Docker. FFmpeg compresses video files to approximately 1/3 their original size. All file-system access is restricted to a single configurable media root for security.

  1. Set your folder to scan, and minimum size to scan for.

Image 1

  1. Select from the files found.

Image 2

  1. Start the Compression and See the Progress

Image 3

  1. Get an email notification when a compresion run finishes.

Image 4

Image 5


Step. 1. You have two options

A. Build the image

docker build -t videopress .

B. Use the pre-built image

Already included in the docker-compose.yml file witht his project.

Step 2. Run — replace /your/video/path with the real path on your host

If you want to persist data between updates, changes, etc.; then you need to create a "data" directory in the same location you store your 'docker-compose.yml' file.

mkdir -p data

Ensure the 'data' directory is accessible with permissions = 775.

chmod -R 775 ./data

A. Using docker run:

  • with an image you build:
docker run -d \
  --name videopress \
  --restart unless-stopped \
  -p 8080:8080 \
  -v /your/video/path:/media \
  -v ./data:/data \
  videopress
  • with the pre-built image:
docker run -d \
--name videopress \
--restart unless-stopped \
-p 8080:8080 \
-v /your/video/path:/media \
-v ./data:/data \
bmcgonag/videopress:latest

B. Using Docker Compose

docker compose up -d

Step 3. Open http://localhost:8080


Bare-metal (without Docker)

Requirements

Tool Version
Python 3.8+
FFmpeg + FFprobe any recent
pip packages see requirements.txt
# Create a python virtual environment to run in
python3 -m venv videopress

#start the virtual environment
source ./videopress/bin/activate

# Install Python dependencies
pip install -r requirements.txt

# Development server (Flask built-in — not for production)
./start.sh

# Production server (Gunicorn + gevent)
MEDIA_ROOT=/your/video/path ./start.sh --prod
# or on a custom port:
MEDIA_ROOT=/your/video/path ./start.sh --prod 9000

Security model

Every API call that accepts a path validates it with safe_path() before any OS operation. safe_path() resolves symlinks and asserts the result is inside MEDIA_ROOT. Requests that attempt directory traversal are rejected with HTTP 403. The container runs as a non-root user (e.g. your id. You can find this by entering the command id in the terminal, make note of the number for uid - usually 1000).


Architecture

Browser  ──HTTP──▶  Gunicorn (gevent worker)
                        │
                        ├─ GET  /                          → index.html
                        ├─ GET  /api/config                 → {"media_root": ..., schedule, server_time}
                        ├─ GET  /api/browse                 → directory listing
                        ├─ POST /api/scan                   → ffprobe metadata
                        ├─ GET  /api/compress/active         → list running/queued jobs
                        ├─ GET  /api/compress/status/<id>   → job snapshot
                        ├─ POST /api/compress/start         → launch ffmpeg thread
                        ├─ GET  /api/compress/progress/<id> ← SSE stream
                        ├─ POST /api/compress/cancel/<id>   → cancel job
                        ├─ GET  /api/settings/smtp          → SMTP config
                        ├─ POST /api/settings/smtp          → save SMTP config
                        ├─ POST /api/settings/smtp/test     → send test email
                        ├─ GET  /api/settings/schedule      → schedule config
                        └─ POST /api/settings/schedule      → save schedule config

Why gevent? SSE (/api/compress/progress) is a long-lived streaming response. Standard Gunicorn sync workers block for its entire duration. Gevent workers use cooperative greenlets so a single worker process can handle many concurrent SSE streams and normal requests simultaneously.

Why workers=1? Job state lives in an in-process Python dict. Multiple worker processes would not share it. One gevent worker with many greenlets is sufficient for this workload. To scale to multiple workers, replace the in-process job store with Redis.


Environment variables

Variable Default Description
MEDIA_ROOT /media Root directory the app may access
DB_PATH /data/videopress.db SQLite settings database path
PORT 8080 Port Gunicorn listens on
LOG_LEVEL info Gunicorn log level

Scheduled Compression

VideoPress supports scheduling compression jobs to run during specific hours:

Features

  • Set a time window (start hour and end hour)
  • Configure your timezone for the schedule
  • Jobs queue and wait until the window opens
  • Supports overnight schedules (e.g., 9 PM to 6 AM)
  • Real-time display of server time in your chosen timezone

Usage

  1. Open Settings (⚙ button in header)
  2. Enable "Schedule window" checkbox
  3. Set your timezone (important for accurate scheduling)
  4. Set the start and end hours
  5. When compressing files, choose "Schedule for Later"

The server time displayed in the header shows the time in your configured timezone.


File layout

videopress/
├── app/
│   ├── __init__.py
│   ├── config.py
│   ├── db.py
│   ├── jobs.py
│   ├── media.py
│   ├── notify.py
│   └── routes.py
├── tests/
│   ├── api/
│   ├── frontend/
│   └── unit/
├── static/
│   ├── css/
│   │   └── main.css
│   └── js/
│       ├── app.js
│       └── modules/
│           ├── browser.js
│           ├── compress.js
│           ├── progress.js
│           ├── scan.js
│           ├── session.js
│           ├── settings.js
│           ├── state.js
│           ├── stream.js
│           ├── tabs.js
│           ├── theme.js
│           └── utils.js
├── templates/
│   └── index.html
├── data/               ← created at runtime (SQLite DB)
├── Dockerfile
├── docker-compose.yml
├── gunicorn.conf.py
├── package.json
├── pytest.ini
├── requirements.txt
├── run.py
├── start.sh
├── vitest.config.js
└── wsgi.py

Contribute

Feel free to clone the repository, make updates, and submit a pull request to make this more feature rich.

Keep in mind, I like to keep things fairly simple to use.

If you need to know too much about ffmpeg and how to configure the perfect compression, then that may be too much for this app, but feel free to fork this repository and make your own as well.