7.6 KiB
MPS — Martinhal Presentation System
A self-hosted app that turns any presentation into a link and a QR code. Upload a video, PowerPoint, Word doc, Excel sheet, PDF, or image; the app converts it to a browser-friendly format and generates a QR code. Anyone who scans it watches along on their own phone — live and in sync with the presenter, or browsing at their own pace.
Everything runs in a single Docker container. No cloud, no third-party services except the mail account you choose to connect for email sharing.
Version 1.7 · © 2026 Martinhal IT — Joao Vaz
What's new in 1.7
- Present Local Only opens two browser tabs: the presenter console (to drive the talk, exactly like Present) and a second, chrome-free tab showing the presentation only — no header text, no control buttons. The second tab mirrors the console live, so it's ideal for a projector or second screen. Both open as normal tabs (not pop-up windows).
Since 1.4
- Simpler login screen — just the credentials, nothing else.
- End session button in the presenter bar (left of Exit): it invalidates the QR code and disconnects everyone watching, so the link can't be reused afterwards.
- Refresh regenerates codes — pressing Refresh on the dashboard issues a fresh QR code for every presentation, so links from a finished talk stop working.
- The presenter bar now shows a single QR Code button (opens the large, scannable page); the old "Share / QR" pop-over was removed.
Since 1.3
- First-run setup. The very first time you open the app it asks you to create the administrator account — no default password to remember or change.
- Rebranded as MPS — Martinhal Presentation System, with the MPS logo across the app.
- QR Code button on every ready presentation opens the code on its own page in large format — ideal for putting on a projector. It has copy-link, fullscreen and print actions.
- Bigger presentation cards on the host dashboard (twice as wide, 50% taller) so the QR preview and controls are easier to use.
From 1.2
Login page and user accounts, admin-only user management, mail settings and activity logs, email sharing, PurelyMail SMTP+IMAP with a copy saved to Sent, and First/Last slide controls.
Quick start (Docker Compose)
# Point QR codes at an address phones can actually reach (LAN IP or public URL).
export PUBLIC_BASE_URL=http://192.168.1.50:3000
docker compose up --build -d
Open http://<host>:3000. On first launch you'll be asked to create the admin
account. After that, sign in, upload a file, and hit Present. Show the QR code
(Share / QR, or the QR Code button for a full-screen page) and the room scans to
join.
Unattended installs: if you'd rather not use the setup screen, set
ADMIN_USERNAMEandADMIN_PASSWORDbefore the first start and an admin will be pre-created from those values instead. They're ignored once an account exists.
Plain Docker
docker build -t mps .
docker run -d --name mps \
-p 3000:3000 \
-e PUBLIC_BASE_URL=http://192.168.1.50:3000 \
-v qr_data:/data \
mps
Without Docker (Node 18+)
Requires libreoffice, ffmpeg, and fonts on the host.
npm install
npm run fetch-vendor # vendors PDF.js for offline use
npm start
Updating (no data loss)
All state — user accounts, mail settings, logs, uploaded decks — lives under the /data
volume, never inside the app code. To update:
-
Unzip the new version over your existing project folder (code files are replaced; your
data/and the Dockerqr_datavolume are left untouched). -
Rebuild and restart:
docker compose up --build -d
Your admin account, users, presentations, and mail configuration carry straight over, and the first-run setup screen does not reappear.
Users & roles
| Role | Can do |
|---|---|
| admin | Everything: manage users, configure mail, view logs, see and manage all presentations. |
| user | Sign in, upload and present their own decks, share them (link, QR, email). |
The first admin is created during setup. Manage further accounts at /users (admins only). The app prevents removing or demoting the last remaining admin, and you can't delete your own account.
Sharing a presentation
Each ready presentation offers several ways to get people in:
- QR Code — the presenter bar opens a dedicated full-screen page with a large, scannable code (plus copy-link, fullscreen, and print). Great for a projector.
- End session — invalidates the QR code and disconnects viewers when you're done.
- Copy link — the plain audience URL.
- Email — sends the link and QR straight to recipients (requires mail set up).
Email sharing (PurelyMail)
-
Sign in as an admin → Manage users → Mail.
-
Tick Enable email sending, enter the PurelyMail account email and password. Servers are prefilled and match PurelyMail's settings:
Host Port Security SMTP (send) smtp.purelymail.com465SSL/TLS IMAP (Sent copy) imap.purelymail.com993SSL/TLS -
Save, then Send test to confirm it works.
Every message is delivered over SMTP and a copy is appended to the Sent folder over
IMAP, so it appears in your normal mail client. The stored mail password lives only in
/data/config.json on your server and is never sent back to the browser.
Presenting
- Slides:
« First·‹ Prev·Next ›·Last », or the keys←→HomeEnd. - Present Local Only: opens the presenter console plus a separate controls-free tab that mirrors it for a projector/second screen. If the second tab doesn't open, allow pop-ups for the site.
- Synced ↔ Free browse: in Synced mode the audience follows you; in Free browse they move on their own. Audience members who wander off get a one-tap "Rejoin presenter".
- Video: play / pause / seek are broadcast to everyone. Viewers tap once to allow sound (browsers block autoplay audio until then).
Supported formats
| Input | Handled as | Tool |
|---|---|---|
.pdf |
paged deck | served directly |
.pptx .ppt .odp .pps .ppsx |
paged deck | LibreOffice → PDF |
.docx .doc .odt .rtf .txt .md |
paged deck | LibreOffice → PDF |
.xlsx .xls .ods .csv .tsv |
paged deck | LibreOffice → PDF |
.mp4 .webm .ogg |
synced video | served directly |
.mov .avi .mkv .wmv .flv .3gp .mpeg .ts |
synced video | ffmpeg → MP4 |
.png .jpg .jpeg .gif .webp .svg .bmp .avif |
image | served directly |
Configuration
| Variable | Default | Purpose |
|---|---|---|
PUBLIC_BASE_URL |
derived from request | Absolute URL encoded in QR codes / links. Set this. |
ADMIN_USERNAME |
admin |
Only for unattended pre-seed (see Quick start). |
ADMIN_PASSWORD |
(unset) | If set on first run, pre-creates an admin instead of showing setup. |
ADMIN_EMAIL |
(empty) | Email for a pre-seeded admin. |
MAX_UPLOAD_MB |
500 |
Maximum upload size. |
PORT |
3000 |
Listen port. |
DATA_DIR |
/data (Docker) |
Where all state is stored. Mount as a volume. |
CONVERT_TIMEOUT_MS |
240000 |
Per-file conversion timeout. |
Security notes
- Put a reverse proxy with HTTPS in front for anything beyond a trusted LAN, and set
PUBLIC_BASE_URLto the https address. - Passwords are stored as salted scrypt hashes. Login sessions are HttpOnly cookies.
- The container runs as a non-root user; all state stays on the mounted volume.
License
MIT.