Version: 3.8.0
A Stash plugin that integrates with Subgen to automatically generate subtitles for your media library scenes.
- One-click subtitle generation — adds a "Generate Subtitles" option to the scene three-dot menu
- In-browser subtitle editor — view and edit generated
.srtfiles without leaving Stash (appears as "Edit Subtitles" in the menu when a subtitle already exists) - Automatic Stash scan trigger — after generation, Stash is automatically rescanned so the subtitle appears immediately
- Local audio extraction — optionally uploads a 16 kHz mono WAV of the audio track instead of the whole video: far smaller uploads, less work for the Subgen container, and no quality loss
- MP4 pipe compatibility fix — optionally remuxes files whose moov atom is not at the start, which would otherwise cause Subgen to fail
- Multilingual support — pin the source language or let Whisper auto-detect it; translates to English or transcribes in the native language depending on your settings
- Configurable settings — Subgen URL, ffmpeg path, source language, translate to English, debug logging, audio extraction, auto-fix, and backup creation are all configurable from the Stash plugin settings UI
- SPA-aware — survives Stash's single-page app navigation without page reloads
The plugin has two components:
JavaScript frontend (subgen-integration.js) runs inside the Stash web UI. It injects menu items into the scene detail dropdown, handles user interaction, and calls the Python backend via Stash's GraphQL runPluginTask / runPluginOperation mutations.
Python backend (subgen-integration.py) runs server-side inside the Stash container. It queries the Stash GraphQL API for the scene's file path, uploads the video file to Subgen's /asr endpoint, writes the returned .srt file next to the video, and triggers a Stash metadata scan.
| Dependency | Purpose | Notes |
|---|---|---|
| Stash | Media server and plugin host | v0.23.0+ recommended; older versions may pass booleans as strings (handled) |
| Subgen | AI subtitle generation service | Must be running and network-reachable from the Stash container |
| Python 3 | Runs the backend script | Must be available inside the Stash container |
ffmpeg |
Local audio extraction, pipe compatibility check and remux | Must be available inside the Stash container (standard in most Stash Docker images) |
The following packages must be available in the Python environment inside the Stash container:
requests
urllib3
Install with:
pip install requests urllib3These are typically pre-installed in Stash Docker images. If not, install them inside the container or add them to your Docker setup.
This plugin is designed for a Docker Compose deployment where Stash and Subgen run as sibling containers on the same Docker network.
By default the plugin connects to Subgen at http://subgen:9000, using the Docker container name as the hostname. If your Subgen container has a different name or runs on a different host/port, configure the URL in the plugin settings.
Example docker-compose.yml snippet:
services:
stash:
image: stashapp/stash:latest
# ... your stash config ...
subgen:
image: mccloud/subgen:latest
environment:
- WHISPER_MODEL=medium
- CONCURRENT_TRANSCRIPTIONS=2
# Add other Subgen config as needed
ports:
- "9000:9000"Both services must be on the same Docker network so stash can reach http://subgen:9000.
-
Copy the three plugin files into your Stash plugins directory:
~/.stash/plugins/subgen-integration/ ├── subgen-integration.js ├── subgen-integration.py └── subgen-integration.yml -
In Stash, go to Settings → Plugins and click Reload Plugins.
-
The plugin will appear as Subgen in the plugin list.
Go to Settings → Plugins → Subgen to configure:
| Setting | Default | Description |
|---|---|---|
| Subgen Webhook URL | http://subgen:9000 |
URL to your Subgen instance. Leave blank to use the default Docker network address. |
| FFmpeg Binary Path | ffmpeg |
Path to the ffmpeg binary used for local audio extraction, the pipe-compatibility check and remux. Leave blank to use ffmpeg from PATH. Set to a newer binary (e.g. /opt/bin/ffmpeg) if your system's ffmpeg is too old for -movflags +faststart — common on native/QNAP Stash installs. |
| Source Language | (blank — auto-detect) | ISO-639-1 code of the language spoken in your media, e.g. en. Set this to en if your library is English. Left blank, Whisper auto-detects from the opening seconds of audio, which is unreliable on accented or quiet-intro material — a British English scene has been observed detected as Welsh and transcribed into phonetic Welsh, producing subtitles that read as gibberish. Only leave it blank if you genuinely have mixed-language content. |
| Translate to English | Off | When enabled, Whisper translates any audio language to English subtitles (.eng.srt). Safe for English audio — translating English to English is a no-op. Disable if you want native-language subtitles (.en.srt). |
| Skip Existing Subtitles | Off | Skip generation for scenes that already have a subtitle file. Applies to both single-scene generation and the batch task. |
| Batch Generation Tag | subgen_me |
The tag the Batch Generate Subtitles (By Tag) task looks for. Scenes with this tag are processed and the tag is removed on success. |
| Debug Logging | Off | Enables verbose logging to the browser console (F12). Useful for troubleshooting. |
| Extract Audio Locally | Off | Extracts the audio track to a temporary 16 kHz mono WAV with ffmpeg and uploads that instead of the video file. The WAV is uncompressed PCM at a fixed ~110 MB/hour regardless of source bitrate, so a 90-minute film uploads as ~165 MB instead of several GB — and the Subgen container no longer has to demux the video. No quality loss: Whisper resamples to exactly this format anyway. Because the container is rebuilt from a full decode, this also makes moov-atom/pipe problems impossible, so it supersedes Auto-fix Pipe Compatibility Issues when both are on. Note the fixed rate cuts both ways — for unusually low-bitrate or short clips the WAV can be larger than the source. |
| Auto-fix Pipe Compatibility Issues | Off | Automatically remuxes MP4 files that fail the ffmpeg pipe compatibility check (moov atom not at start). Recommended if you encounter silent failures. Ignored when Extract Audio Locally is enabled. |
| Create Backup Files | Off | Creates a .bak copy of the original file before remuxing. Useful for safety during testing. Only applies to the remux path — local audio extraction never touches the original file. |
- Navigate to any scene detail page in Stash.
- Click the three-dot menu (⋮) in the scene header.
- Select Generate Subtitles.
- A confirmation dialog will appear. Subtitle generation runs server-side and may take several minutes depending on video length and your hardware.
- Progress is visible in the Stash server logs.
- Once complete, the subtitle file (
.eng.srt) is saved next to the video and Stash is automatically rescanned. - If a subtitle already exists, an Edit Subtitles option will also appear in the menu, opening an in-browser editor with line numbers.
- Tag every scene you want processed with your Batch Generation Tag (default:
subgen_me). - Go to Settings → Tasks, find the Plugin Tasks section, and click Batch Generate Subtitles (By Tag).
- The plugin processes the tagged scenes sequentially, with progress shown in the Stash Tasks log.
- On success (or when skipped because a subtitle already exists), the trigger tag is automatically removed from the scene so it isn't reprocessed. Scenes that error keep the tag and will be retried on the next run.
Companion: host-side automation (stash-tools)
The built-in batch task runs inside Stash's job queue, which is serial — a long batch ties up that queue for the duration. For unattended, large-scale, or scheduled use, the companion project stash-tools moves the work out of Stash entirely:
- Scheduled metadata scans from cron/systemd (Stash has no built-in scheduler), which skip when a scan is already running/queued so they never pile up.
- One-at-a-time Subgen subtitle generation driven by the same
subgen_metag, run from the host — so it never blocks Stash's job queue. - Crash-safe recovery: the tag is the work queue (a scene's tag is cleared only after its subtitle is confirmed written), so an interrupted run resumes cleanly with no state file.
The in-plugin Generate Subtitles / Edit Subtitles menu items remain the quick interactive path; stash-tools is the batch/automation counterpart. See its README for setup.
Generated subtitles are saved in the same directory as the source video. The filename depends on the Translate to English setting:
| Setting | Output file | Notes |
|---|---|---|
| Translate to English = On | video.eng.srt |
Any audio language → English subtitles |
| Translate to English = Off | video.en.srt |
Audio transcribed in its detected language (named .en.srt so Stash tags the language rather than showing "Unknown") |
The plugin checks for existing subtitles in priority order: .eng.srt, .en.srt, .srt.
Audio language is always auto-detected by Whisper — no configuration required. The source language does not need to be specified. Whisper's translate task is safe for English audio; translating English to English produces identical output to transcription.
"File not found in Stash container" — The video path queried from Stash does not exist inside the container. Verify your volume mounts match between your compose file and Stash's library paths.
Subgen returns empty response — The video file may have pipe compatibility issues. Enable Extract Audio Locally (preferred) or Auto-fix Pipe Compatibility Issues in plugin settings.
"Remux failed: ffmpeg version too old" — Your system's ffmpeg is too old for -movflags +faststart (seen on native/QNAP Stash installs with ancient ffmpeg). Install a newer ffmpeg and set its path in FFmpeg Binary Path in plugin settings. Alternatively, switch to Extract Audio Locally, which does not use +faststart at all.
Uploads are slow or Subgen runs out of memory — Enable Extract Audio Locally. A 16 kHz mono WAV of a 90-minute film is ~165 MB versus several GB for the video, and Subgen no longer has to demux it. The trade-off is that extraction is a full decode, so it costs some local CPU time up front.
Subtitles are gibberish, or in the wrong language — Whisper misdetected the source language. It picks the language from the opening seconds of audio, and accented speech, a quiet intro, or background music can throw it off; the result is real words in the wrong language rather than nonsense, so it is easy to mistake for an encoding problem. Set Source Language to your library's language (en for English) and regenerate. Note that Subgen's FORCE_LANG / FORCE_DETECTED_LANGUAGE_TO environment variables do not help here — they do not apply to the /asr endpoint this plugin uses, so the language must be pinned in the plugin settings.
Subtitle generated but not appearing in Stash — The automatic rescan may have been skipped. Manually trigger a library scan in Stash (Tasks → Scan).
"Could not trigger automatic scan" — Non-fatal. The subtitle is saved successfully; trigger a manual scan.
Debug logging — Enable Debug Logging in plugin settings and open your browser's developer console (F12) for detailed trace output.
MIT