Add subtitle UI badge and VTT positioning

Documents the disclaimer, shows subtitles in admin movie badges, and offsets VTT subtitles from the bottom.
This commit is contained in:
Mathieu Moran
2026-01-25 01:00:48 -05:00
parent a9592ece7a
commit 891eb67e2d
4 changed files with 16 additions and 1 deletions
+2
View File
@@ -3,6 +3,8 @@ Little nodejs + mongodb app that I hack together with ChatGPT and Cursor to keep
**Note:** The application interface is in French. **Note:** The application interface is in French.
**Disclaimer:** This was a fun, experimental project built with “AI slop” (Cursor), so it may have security issues. For example, `python_video_server` uses the default token‑in‑URL approach to serve video files, which can leave user tokens in logs or browser history. There are ways to improve this, but at this point I’m blinder than Ray Charles.
![Oscars Pool 2026](https://raw.githubusercontent.com/quoije/oscars-pool/refs/heads/prod/img/preview.png) ![Oscars Pool 2026](https://raw.githubusercontent.com/quoije/oscars-pool/refs/heads/prod/img/preview.png)
## Features ## Features
+2
View File
@@ -2442,12 +2442,14 @@ window.onload = async function () {
const hasLowFile = !!(m && m.video_file_low); const hasLowFile = !!(m && m.video_file_low);
const hasEmbed = !!(m && m.embed_src); const hasEmbed = !!(m && m.embed_src);
const hasLegacy = !!(m && m.vod_link); const hasLegacy = !!(m && m.vod_link);
const hasSubtitle = !!(m && m.subtitle_file);
if (hasVideo) badges.push({ text: 'Video', cls: 'bg-primary' }); if (hasVideo) badges.push({ text: 'Video', cls: 'bg-primary' });
if (hasFile) badges.push({ text: 'Server file', cls: 'bg-dark' }); if (hasFile) badges.push({ text: 'Server file', cls: 'bg-dark' });
if (hasLowFile) badges.push({ text: 'Low file', cls: 'bg-secondary' }); if (hasLowFile) badges.push({ text: 'Low file', cls: 'bg-secondary' });
if (hasEmbed) badges.push({ text: 'Embed', cls: 'bg-info text-dark' }); if (hasEmbed) badges.push({ text: 'Embed', cls: 'bg-info text-dark' });
if (hasLegacy) badges.push({ text: 'Legacy', cls: 'bg-secondary' }); if (hasLegacy) badges.push({ text: 'Legacy', cls: 'bg-secondary' });
if (hasSubtitle) badges.push({ text: 'Subtitles', cls: 'bg-warning text-dark' });
const modeRaw = String(m?.player_mode || 'auto').toLowerCase(); const modeRaw = String(m?.player_mode || 'auto').toLowerCase();
const mode = (modeRaw === 'video' || modeRaw === 'embed' || modeRaw === 'auto') ? modeRaw : 'auto'; const mode = (modeRaw === 'video' || modeRaw === 'embed' || modeRaw === 'auto') ? modeRaw : 'auto';
+3
View File
@@ -15,7 +15,10 @@ export MONGO_DB_NAME="..." # optional; required if MO
export JWT_SECRET="..." export JWT_SECRET="..."
export VIDEO_FILES_DIR="/absolute/path/to/public/video" # optional (default: ./public/video) export VIDEO_FILES_DIR="/absolute/path/to/public/video" # optional (default: ./public/video)
export VIDEO_SESSION_MAX_AGE_SECONDS="28800" # optional (default: 8h) export VIDEO_SESSION_MAX_AGE_SECONDS="28800" # optional (default: 8h)
uvicorn python_video_server.server:app --host 0.0.0.0 --port 8000 uvicorn python_video_server.server:app --host 0.0.0.0 --port 8000
or
python3 -m uvicorn python_video_server.server:app --host 0.0.0.0 --port 8000 --ssl-certfile python_video_server/certs/config/live/[site]/fullchain.pem --ssl-keyfile python_video_server/certs/config/live/[site]/privkey.pem
``` ```
## Extra environment variables (optional) ## Extra environment variables (optional)
+9 -1
View File
@@ -122,7 +122,15 @@ async function convertSrtToVtt(filePath) {
const content = await fs.promises.readFile(filePath, "utf8"); const content = await fs.promises.readFile(filePath, "utf8");
const normalized = content.replace(/\r\n/g, "\n"); const normalized = content.replace(/\r\n/g, "\n");
const body = normalized.replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, "$1.$2"); const body = normalized.replace(/(\d{2}:\d{2}:\d{2}),(\d{3})/g, "$1.$2");
const vtt = `WEBVTT\n\n${body.trim()}\n`; const positioned = body.replace(
/^(\d{2}:\d{2}:\d{2}\.\d{3}\s+-->\s+\d{2}:\d{2}:\d{2}\.\d{3})(.*)$/gm,
(_match, timing, settings) => {
const tail = String(settings || "");
if (/\bline:/i.test(tail)) return `${timing}${tail}`;
return `${timing}${tail} line:90%`;
},
);
const vtt = `WEBVTT\n\n${positioned.trim()}\n`;
const vttPath = filePath.replace(/\.srt$/i, ".vtt"); const vttPath = filePath.replace(/\.srt$/i, ".vtt");
await fs.promises.writeFile(vttPath, vtt, "utf8"); await fs.promises.writeFile(vttPath, vtt, "utf8");
try { await fs.promises.unlink(filePath); } catch (_) {} try { await fs.promises.unlink(filePath); } catch (_) {}