Create GIF clips from your Plex media library. Self-hosted, single-container. Optional integrations.
- Browse & Search - Navigate your Plex libraries or search by title
- Subtitle Navigation - Click subtitle lines to jump to that moment
- Clip Start & Duration - Choose where the clip begins and how long it runs, with frame preview
- GIF Generation - Create GIFs with optional subtitle burn-in or custom text
- Gallery - View, download, and manage your created GIFs
- GIPHY Upload - Upload GIFs directly to GIPHY (optional)
docker run -d -p 8000:8000 -v ./data:/data --name clipmark ghcr.io/reggio-digital/clipmark:latestOpen http://localhost:8000 and connect your Plex account.
Note: Plex Media Server must be reachable from the container.
Each user signs in with their own Plex account. Browsing, search, media previews, and GIF creation use that account's access to the configured server, including Plex library and content restrictions. Manage access in Plex; a Clipmark admin role does not grant additional Plex library permissions.
After upgrading from a version without per-user library access, existing users must sign out and sign in again. Library access is denied until they do. Upgrading from a build that stored account tokens also clears those user tokens and requires sign-in again. Each user's server connection and library list are reused for up to 30 seconds; media and preview access are checked against Plex on every request. Plex Media Server must be reachable for these checks; plex.tv is needed at login. Changing the configured server requires users to sign in again for that server. The owner-wide background library scan has been removed. Legacy thumbnail and subtitle/detail cache files are deleted automatically at startup and during cache cleanup; namespaced files are preserved, and expired frames/previews use their existing cleanup schedule. Queued GIFs check the creator's access again before processing. Jobs queued before this upgrade or for a different Plex server must be created again.
Saved favorites and completed GIFs remain in their owner's account. Public GIF links explicitly created with public sharing enabled remain public.
services:
clipmark:
image: ghcr.io/reggio-digital/clipmark:latest
container_name: clipmark
ports:
- "8000:8000"
volumes:
- ./data:/data
restart: unless-stoppeddocker run -d \
-p 8000:8000 \
-v ./data:/data \
--name clipmark \
ghcr.io/reggio-digital/clipmark:latest- Docker
- Plex Media Server (accessible from the container)
Environment variables (all optional):
| Variable | Default | Description |
|---|---|---|
DATA_DIR |
/data |
Persistent storage path |
MAX_CONCURRENT_JOBS |
1 |
Parallel FFmpeg jobs |
MAX_QUEUED_JOBS |
10 |
Max pending jobs |
MAX_QUEUED_JOBS_PER_USER |
3 |
Max pending jobs per user |
MAX_GIF_DURATION_SECONDS |
15 |
Max GIF length |
MAX_PREVIEW_DURATION_SECONDS |
10 |
Max preview length |
MAX_WIDTH |
480 |
Max output width (pixels) |
MAX_FPS |
10 |
Max frame rate |
FRAME_CACHE_TTL_MINUTES |
30 |
Frame cache lifetime |
PREVIEW_CACHE_TTL_MINUTES |
30 |
Preview cache lifetime |
FAILED_WORKSPACE_TTL_HOURS |
24 |
Failed job cleanup delay |
FFMPEG_TIMEOUT_SECONDS |
300 |
FFmpeg process timeout |
Output defaults are 480px width and 10 FPS. Environment variables define upper limits, not per-GIF defaults.
These are managed via the admin UI and stored in data/config.json:
| Setting | Default | Description |
|---|---|---|
gifsicle_enabled |
true |
Enable gifsicle GIF compression |
gifsicle_lossy |
100 |
Lossy compression level (0-200) |
public_sharing_enabled |
false |
Allow public GIF sharing via link |
giphy_global_enabled |
true |
Allow users to upload to GIPHY |
max_gif_duration_seconds |
15 |
Default max GIF duration |
max_width |
480 |
Default max output width |
max_fps |
10 |
Default max frame rate |
browse_page_size |
48 |
Items per page when browsing |
Mount /data as a volume. Contains:
/data/
├── config.json # Plex token (treat as secret)
├── clipmark.db # SQLite database
├── cache/
│ ├── frames/ # Extracted video frames
│ ├── previews/ # GIF previews
│ ├── thumbnails/ # Media thumbnails
│ └── subtitles/ # Parsed subtitle data
├── work/ # Job workspaces (auto-cleaned)
└── output/ # Generated GIFs
Clipmark can upload your GIFs directly to GIPHY. To enable this:
- Create a free account at developers.giphy.com
- Create an app to get an API key
- Go to Settings in Clipmark and enter your API key
Once configured, a GIPHY upload button appears on completed GIFs in the gallery. Uploaded GIFs follow GIPHY's standard review and visibility rules.
Clipmark uses gifsicle to reduce GIF file sizes by 20-40%. This is enabled by default and can be configured in Settings:
- Enable/disable - Toggle optimization on or off
- Lossy compression - Adjust quality vs size (0-200, default 100)
Higher lossy values = more compression = smaller files but more artifacts.
Clipmark detects subtitles associated with Plex items (sidecar or embedded). If no subtitles appear for your media:
- Check Plex first - Open the media in Plex and verify subtitles are available there
- Search in Plex - Plex can search for and download subtitles directly from the media detail page
- Add external subtitles - Place
.srtfiles alongside your media files with matching names (e.g.,Movie.mp4→Movie.srtorMovie.en.srt), then refresh the library in Plex - Embedded subtitles - These are automatically detected if present in the media file
Supported formats for burn-in:
- SRT, ASS/SSA, WebVTT - Full support (text-based)
- PGS, DVD subtitles - Detected but cannot be burned into GIFs (image-based bitmap subtitles)
If your media only has PGS/DVD bitmap subtitles, convert to SRT (via OCR tools) or use custom text overlay instead.
Tip: Bazarr can automatically find and download subtitles across your entire library. It's a great companion if you want subtitles available without manually searching in Plex for each title.
Prerequisites: Node 18+, Python 3.12, ffmpeg, and gifsicle on your PATH.
First-time setup (from the repo root):
cd frontend && npm install && cd ..
cd backend && python3 -m venv venv && ./venv/bin/pip install -r requirements.txt && cd ..Start the app:
npm run devThat runs the backend (Uvicorn, port 8000) and the frontend (Vite, port 5173) together with hot reload. Open http://localhost:5173. Vite proxies /api/* and /output/* to the backend.
The dev script also runs pip install -q -r requirements.txt against the backend venv on every start, so new Python dependencies are picked up automatically. Local data (config, SQLite DB, cache, generated GIFs) lives in ./data/ via DATA_DIR=../data.
Running npm run dev from frontend/ works too; the root script just delegates to it.
docker build -t clipmark .- Frontend: React, TypeScript, Vite, Tailwind CSS
- Backend: Python, FastAPI, SQLAlchemy, SQLite
- Plex: python-plexapi
- Media: FFmpeg, gifsicle
- The owner's Plex account token remains server-side in
config.json. At login, Clipmark obtains the selected server'sresource.accessTokenand stores it with the server ID inclipmark.db. Tokens are never included in browser responses. Treat both files and their backups as secrets; older backups may still contain account tokens. - If exposing to the internet, use a reverse proxy with HTTPS
MIT. See LICENSE for details.


