Subvert: subtitles, chapters and summaries from a video, in one Docker image
Generate subtitles, summaries, and chapters from videos in seconds
At a glance
- What is it?
- Subvert is a self-hosted PHP application that extracts audio with FFmpeg, transcribes it through OpenAI's Whisper model into VTT, and optionally asks a ChatGPT model for chapters and a description-length summary. It is a single-container tool for people who want captions without a SaaS subscription, and its own README calls it a work in progress.
- Who is it for?
- Adopt Subvert if you already hold an OpenAI API key, you are comfortable running a container that publishes plain HTTP on port 80, and you want VTT files and a draft summary on your own disk rather than inside a hosted editor. Do not adopt it if you need a documented upgrade path, a supported release cadence, or a service you can expose to the public internet without adding your own TLS terminator.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 124 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What Subvert does that a caption editor does not
The task is narrow and repetitive: take a video file, produce a WebVTT subtitle track, and optionally produce chapter markers and a summary short enough for a video description. Subvert wraps that pipeline in a web form. You pick a file, tick whether you want chapters and a summary, and the application does the rest. The README frames the output as "subtitles, chapters, and summaries of videos in seconds with the help of OpenAI."
The audience is the person who already edits video and does not want another monthly subscription for captions. Because the whole thing ships as one image, the transcript never has to pass through a hosted captioning product; only the audio reaches OpenAI. That is the trade Subvert makes: you keep the file handling local, you still send audio to a third party. Anyone who needs fully offline transcription should look elsewhere, because the README names OpenAI's Whisper model as the transcription step with no local alternative documented.
The pipeline: FFmpeg, Whisper, then ChatGPT
The README describes the data flow in a short paragraph. Your video is sent to an API, the audio is extracted from it with FFMpeg, and that audio goes to OpenAI's Whisper model for transcription into the common vtt format. If you asked for chapters or a summary, the transcript is then sent to a ChatGPT model, which returns chapters of the length you selected plus a brief summary sized for something like a YouTube description.
The repository layout backs this up. The Dockerfile installs ffmpeg through apk, copies src into /var/www, and creates two storage directories: storage/app/audio and storage/app/video. Both are declared as volumes, so the extracted audio and the uploaded video are the artefacts the container expects to persist. The application itself is PHP 8.2 on Alpine, with composer for PHP dependencies and npm for the front end.
One design consequence is worth naming. Transcription and chapter generation are two separate model calls, so a failure in the ChatGPT step does not lose the transcript. The README does not document what the interface does when the second call fails, so treat partial output as something you verify rather than assume.
Installing Subvert and running a first video through it
The README gives a single command as the supported entry point. It publishes container port 8080 on host port 80 and injects your OpenAI key as an environment variable.
docker run -it -p 80:8080 -e OPENAI_API_KEY=sk-123abc aschmelyun/subvertAfter that command the README states the server boots and the application is available at http://localhost. Open that address, choose a video file, and decide whether you also want chapters and a summary before submitting it.
Two limits are configurable at the same point, and both have defaults you may need to raise. UPLOAD_MAX_FILESIZE controls PHP's upload limit and defaults to 256M; MEMORY_LIMIT controls PHP's memory limit and defaults to 512M. Pass them as additional -e flags on the same run command.
If you prefer to run from source rather than the image, the README requires PHP 8.1+ and npm on your machine, and then a script inside src.
cd src
./startup.shThe README notes you can also run the commands inside startup.sh individually for the same result. For a longer-lived deployment, the repository ships a docker-compose.yml that pairs the Subvert image with an nginx TLS proxy. In that file the proxy sets UPSTREAM to subvert:8080, SERVERNAME to a placeholder domain, and NGINX_CLIENT_MAX_BODY_SIZE to 256M with a comment telling you to keep it matched to Subvert's UPLOAD_MAX_FILESIZE. That comment is the one piece of operational guidance in the repository, and it exists because a mismatch between the two produces an upload failure that looks like a Subvert bug.
The insecure port and the missing upgrade path
The README carries an explicit note: the image currently only exposes the insecure :80 http port. The compose file works around this by putting danieldent/nginx-ssl-proxy in front of it, which means TLS is your responsibility and not the project's. If you deploy the image alone on a cloud instance, you are serving plain HTTP.
The second limitation is lifecycle. The most recent release listed in the repository is v1.0.9 from 2023-04-16, while the last push to the default branch was on 2026-05-15. That gap matters for anyone planning to run this for years: the tagged releases are old, so you are effectively tracking the main branch, and the README does not document a rollback procedure or a supported upgrade path between versions. The Dockerfile copies .env.example into place at build time, which means configuration changes that live in that file arrive with the image rather than through a migration step.
The README is also upfront that the project is "very much a work-in-progress" and asks for issues when bugs appear. That is an honest framing, and it should shape how you deploy it. Treat Subvert as an internal tool on a trusted network, not as public infrastructure.
Where Subvert is the wrong tool
If your requirement is transcription that never leaves your hardware, Subvert fails the requirement at the first step. The audio is sent to OpenAI's Whisper model, and the README documents no local model path or offline mode. A team with a data-residency rule about media leaving the network cannot use this as written.
A second case is batch processing at volume. Subvert is a web form that processes a file you select, with a configurable upload ceiling and a PHP memory ceiling. The README describes no queue, no CLI batch mode, and no API for submitting jobs programmatically. If you need to transcribe a back catalogue of hundreds of files unattended, the interface described here is the wrong shape for the job, even though the underlying FFmpeg and Whisper steps are the same ones you would script yourself.
A third case is translation. The repository's topics list translation alongside transcription, but the README describes only transcription into vtt, then chapters and a summary. Nothing in the documented flow takes a target language. If you need subtitles in a language other than the source, the README does not describe how Subvert produces them.
How Subvert compares with running Whisper yourself
The obvious alternative is calling the OpenAI transcription endpoint directly, or running an open Whisper implementation locally and scripting FFmpeg around it. The difference is where the work sits. A direct script gives you control over chunking, retries, model choice and output formatting, and it can run headless over a directory of files. It also gives you nothing else: no form, no chapter generation, no summary, no storage layout.
Subvert's contribution is the assembled product around those two model calls. It extracts audio with FFmpeg, stores uploads and extracted audio in two named volumes, produces VTT, and then reuses the transcript for a second ChatGPT pass that returns chapters and a description-length summary. If you were scripting this, the second pass is the part you would have to design yourself, including the prompt that decides chapter granularity. The README says chapters come out at the length you wanted, which implies the interface exposes that choice, though it does not document the parameter names.
So the comparison is not capability against capability. It is whether you want a maintained-by-you script with full control, or a container that already wires the two stages together and gives you a browser form in exchange for accepting its defaults and its HTTP-only port.
Licence and the cost of keeping it running
Subvert is MIT licensed, with the licence text in LICENSE.md. That permits commercial use and modification, and it places no copyleft obligation on your own code. It says nothing about your OpenAI usage, which is billed separately and governed by OpenAI's terms, not this repository. The README does not discuss token costs, per-minute audio pricing, or rate limits, so budgeting for a large back catalogue is something you estimate from OpenAI's side rather than from anything here. This is a description of the licence text, not legal advice.
Upgrade cost is the more practical concern. With the newest tagged release at v1.0.9 (2023-04-16) and the last push on 2026-05-15, there is a long stretch of unreleased work on main. Pulling a newer image means pulling whatever has landed since, and the README documents no changelog, no migration step and no rollback. The Dockerfile's build-time copy of .env.example reinforces that: environment-driven configuration arrives with the image. If you deploy this, pin the image digest you validated and re-test the transcription flow after any change, because the project gives you no other signal that a version is safe to move to.
Editorial conclusion
Adopt Subvert if you already hold an OpenAI API key, you are comfortable running a container that publishes plain HTTP on port 80, and you want VTT files and a draft summary on your own disk rather than inside a hosted editor. Do not adopt it if you need a documented upgrade path, a supported release cadence, or a service you can expose to the public internet without adding your own TLS terminator. Before you commit, verify three things: that the default 256M upload and 512M memory limits accept your longest recording, that the container's storage volume is mounted so your uploads survive a restart, and that your OpenAI account allows the requests the transcription step will make.
Frequently asked questions
How do I use Subvert to generate subtitles from a video?
Run the Docker image with your OpenAI API key, open http://localhost, and select a video file. Subvert extracts the audio with FFMpeg, sends it to OpenAI's Whisper model, and returns the transcript in the vtt format.
What does Subvert need before it will start?
The README lists two requirements: Docker installed on your machine, and an OpenAI API key. The key is passed in as the OPENAI_API_KEY environment variable and has no default.
Can I change the maximum upload size in Subvert?
Yes. UPLOAD_MAX_FILESIZE sets PHP's upload limit and defaults to 256M, while MEMORY_LIMIT defaults to 512M. If you put a proxy in front, the compose file's NGINX_CLIENT_MAX_BODY_SIZE has to match UPLOAD_MAX_FILESIZE.
Does Subvert run without Docker?
The README gives a source option for machines with PHP 8.1+ and npm installed. You check out the repository, go into the src directory, and run ./startup.sh, or run the commands inside that script individually.
Is Subvert maintained?
The repository is not archived, and the last push to the default branch was on 2026-05-15. The most recent tagged release listed is v1.0.9 from 2023-04-16, so the releases lag well behind the branch.
Does Subvert serve HTTPS?
The README states the image currently only exposes the insecure :80 http port. The repository's docker-compose.yml adds a danieldent/nginx-ssl-proxy service in front of it, with UPSTREAM set to subvert:8080.
Community notes