add processVideo env

This commit is contained in:
waveringana 2025-05-11 19:16:02 -04:00
parent 3605a3f3a5
commit 170479b585
8 changed files with 89 additions and 27 deletions

View file

@ -21,6 +21,7 @@ A media host specialized in producing visually appealing embeds for services lik
$ export EBPASS=changeme $ export EBPASS=changeme
$ export EBPORT=3000 $ export EBPORT=3000
$ export EBAPI_KEY=changeme # For ShareX support $ export EBAPI_KEY=changeme # For ShareX support
$ export EB_PROCESS_VIDEO=true # Enable video processing
$ npm install $ npm install
$ npm start $ npm start
@ -57,22 +58,38 @@ Enabled at `/upload`. Requires authentication with key. `expire` key specifies d
### Configuration ### Configuration
This project uses environmental variables to configure functions. This project uses environmental variables for configuration:
`EBPASS` configures the password for the admin account. | Variable | Description | Default |
|----------|-------------|---------|
| `EBPASS` | Password for the admin account | required |
| `EBAPI_KEY` | Key for API uploading (ShareX) | required |
| `EBPORT` | Port the server runs on | required |
| `EB_PROCESS_VIDEO` | Enable video processing/optimization | `true` |
| `EB_ENCODER` | Video encoder to use (CPU, NVENC, QSV, etc.) | `CPU` |
| `EB_FFMPEG_PATH` | Path to ffmpeg binary | auto-detected or bundled |
| `EB_FFPROBE_PATH` | Path to ffprobe binary | auto-detected or bundled |
| `EB_RANDOMIZE_NAMES` | Randomize uploaded file names | `false` |
`EBAPI_KEY` configures the key for API uploading support typically used for ShareX. #### Video Processing
`EBPORT` configures the port the server runs on. When `EB_PROCESS_VIDEO` is enabled (which is the default), Embedder will:
`EB_FFMPEG_PATH` and `EB_FFPROBE_PATH` configures the path to the ffmpeg and ffprobe binaries respectively. If not set, it uses installed binaries set in the PATH. If none are detected, it will default to preinstalled binaries from the [node-ffmpeg-installer](https://www.npmjs.com/package/@ffmpeg-installer/ffmpeg) package. 1. Process uploaded videos to create optimized 720p versions
2. Show "Copy as GIFv" links for video files
3. Display a processing spinner while videos are being optimized
`EB_RANDOMIZE_NAMES` configures whether or not to randomize file names. If set to `true`, file names will be randomized. If not set or set to false, it will be `false`. If disabled by setting `EB_PROCESS_VIDEO=false`, videos will be served directly without processing, which can be useful on systems with limited resources.
### Using Docker ### Using Docker
```bash ```bash
docker run -d -p "3000:3000" -e EBPORT=3000 -e EBPASS=changeme -e EBAPI_KEY=changeme ghcr.io/waveringana/embedder:1.13 docker run -d -p "3000:3000" \
-e EBPORT=3000 \
-e EBPASS=changeme \
-e EBAPI_KEY=changeme \
-e EB_PROCESS_VIDEO=true \
ghcr.io/waveringana/embedder:1.13
``` ```
### Docker Compose ### Docker Compose
@ -86,6 +103,7 @@ services:
- EBPORT=3000 - EBPORT=3000
- EBPASS=changeme - EBPASS=changeme
- EBAPI_KEY=changeme - EBAPI_KEY=changeme
- EB_PROCESS_VIDEO=true
volumes: volumes:
- ./db:/var/db - ./db:/var/db
- ./uploads:/uploads - ./uploads:/uploads

View file

@ -92,6 +92,8 @@ function onListening() {
const bind = typeof addr === "string" const bind = typeof addr === "string"
? "pipe " + addr ? "pipe " + addr
: "port " + addr.port; : "port " + addr.port;
const processVideo = process.env["EB_PROCESS_VIDEO"] === "true" ? true : false;
console.log("Process video: " + processVideo);
console.log("Embedder version: " + version); console.log("Embedder version: " + version);
console.log("Listening on " + bind); console.log("Listening on " + bind);
} }

View file

@ -24,7 +24,10 @@ class FileUploader {
this.dropArea = document.getElementById('dropArea'); this.dropArea = document.getElementById('dropArea');
this.gallery = document.getElementById('gallery'); this.gallery = document.getElementById('gallery');
this.setupEventListeners(); this.setupEventListeners();
this.setupProgressUpdates(); // SSE logic for transcoding // Only set up SSE if we're processing videos
if (document.body.dataset.processVideo === 'true') {
this.setupProgressUpdates();
}
} }
setupEventListeners() { setupEventListeners() {

View file

@ -26,6 +26,8 @@ import {
processUploadedMedia, processUploadedMedia,
} from "../lib/middleware"; } from "../lib/middleware";
const processVideo: boolean = process.env["EB_PROCESS_VIDEO"] !== "false";
const upload = multer({ storage: fileStorage /**, fileFilter: fileFilter**/ }); //maybe make this a env variable? const upload = multer({ storage: fileStorage /**, fileFilter: fileFilter**/ }); //maybe make this a env variable?
/**Middleware to grab media from media database */ /**Middleware to grab media from media database */
@ -65,6 +67,11 @@ const fetchMedia: Middleware = (req, res, next) => {
const router = express.Router(); const router = express.Router();
router.get('/progress-updates', (req, res) => { router.get('/progress-updates', (req, res) => {
if (!processVideo) {
res.status(404).send('Video processing is disabled');
return;
}
console.log("SSE connection requested"); // Debug log console.log("SSE connection requested"); // Debug log
res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Content-Type', 'text/event-stream');
@ -96,12 +103,12 @@ router.get(
fetchMedia, fetchMedia,
(req: Request, res: Response) => { (req: Request, res: Response) => {
res.locals.filter = null; res.locals.filter = null;
res.render("index", { user: req.user }); res.render("index", { user: req.user, processVideo });
} }
); );
router.get("/media-list", fetchMedia, (req: Request, res: Response) => { router.get("/media-list", fetchMedia, (req: Request, res: Response) => {
res.render("partials/_fileList", { user: req.user }); res.render("partials/_fileList", { user: req.user, processVideo });
}); });
router.get( router.get(
@ -189,7 +196,8 @@ router.get("/oembed/:file",
} }
); );
router.post( if (processVideo) {
router.post(
"/", "/",
[ [
checkAuth, checkAuth,
@ -200,10 +208,24 @@ router.post(
createEmbedData, createEmbedData,
], ],
(req: Request, res: Response) => { (req: Request, res: Response) => {
return res.render("partials/_fileList", { user: req.user }); // Render only the file list partial return res.render("partials/_fileList", { user: req.user, processVideo }); // Render only the file list partial
} }
); );
} else {
router.post(
"/",
[
checkAuth,
upload.array("fileupload"),
handleUpload,
fetchMedia,
createEmbedData
],
(req: Request, res: Response) => {
return res.render("partials/_fileList", { user: req.user, processVideo }); // Render only the file list partial
}
);
}
router.post( router.post(
"/sharex", "/sharex",
[checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload], [checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload],

View file

@ -11,10 +11,12 @@ class ProgressManager {
private static instance: ProgressManager; private static instance: ProgressManager;
private emitter: EventEmitter; private emitter: EventEmitter;
private activeJobs: Map<string, ProgressUpdate>; private activeJobs: Map<string, ProgressUpdate>;
private processVideo: boolean;
private constructor() { private constructor() {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.activeJobs = new Map(); this.activeJobs = new Map();
this.processVideo = process.env["EB_PROCESS_VIDEO"] === "true";
} }
static getInstance(): ProgressManager { static getInstance(): ProgressManager {
@ -25,19 +27,27 @@ class ProgressManager {
} }
updateProgress(update: ProgressUpdate) { updateProgress(update: ProgressUpdate) {
if (!this.processVideo) return;
this.activeJobs.set(update.filename, update); this.activeJobs.set(update.filename, update);
this.emitter.emit('progress', update); this.emitter.emit('progress', update);
} }
subscribeToUpdates(callback: (update: ProgressUpdate) => void) { subscribeToUpdates(callback: (update: ProgressUpdate) => void) {
if (!this.processVideo) return;
this.emitter.on('progress', callback); this.emitter.on('progress', callback);
} }
unsubscribeFromUpdates(callback: (update: ProgressUpdate) => void) { unsubscribeFromUpdates(callback: (update: ProgressUpdate) => void) {
if (!this.processVideo) return;
this.emitter.off('progress', callback); this.emitter.off('progress', callback);
} }
getJobStatus(filename: string): ProgressUpdate | undefined { getJobStatus(filename: string): ProgressUpdate | undefined {
if (!this.processVideo) return undefined;
return this.activeJobs.get(filename); return this.activeJobs.get(filename);
} }
} }

View file

@ -15,7 +15,7 @@
<script src="https://unpkg.com/htmx.org@1.9.8"></script> <script src="https://unpkg.com/htmx.org@1.9.8"></script>
</head> </head>
<body> <body data-process-video="<%= processVideo %>">
<section class="embedderapp"> <section class="embedderapp">
<header class="header"> <header class="header">
<h1>Embedder</h1> <h1>Embedder</h1>

View file

@ -23,6 +23,7 @@ function sanitizeId(filename) {
<div class="video"> <div class="video">
<% const sanitizedId = file.path.replace(/[^a-z0-9]/gi, '_'); %> <% const sanitizedId = file.path.replace(/[^a-z0-9]/gi, '_'); %>
<% if (processVideo) { %>
<div id="spinner-<%= sanitizedId %>" class="spinner" style="display: <%= file.isProcessed ? 'none' : 'block' %>;"> <div id="spinner-<%= sanitizedId %>" class="spinner" style="display: <%= file.isProcessed ? 'none' : 'block' %>;">
<div class="spinner-content"> <div class="spinner-content">
Optimizing Video for Sharing... Optimizing Video for Sharing...
@ -30,8 +31,12 @@ function sanitizeId(filename) {
</div> </div>
<div id="media-container-<%= sanitizedId %>" class="video-container" style="display: <%= file.isProcessed ? 'block' : 'none' %>;"> <div id="media-container-<%= sanitizedId %>" class="video-container" style="display: <%= file.isProcessed ? 'block' : 'none' %>;">
<% } else { %>
<div class="video-container">
<% } %>
<video class="image" autoplay loop muted playsinline> <video class="image" autoplay loop muted playsinline>
<source src="/uploads/720p-<%= file.path %>"> <source src="/uploads/720p-<%= file.path %>">
<source src="/uploads/<%= file.path %>">
</video> </video>
</div> </div>
@ -40,7 +45,9 @@ function sanitizeId(filename) {
<small class="username"><%= file.username %></small> <small class="username"><%= file.username %></small>
<br> <br>
<% } %> <% } %>
<% if (processVideo) { %>
<a href="/gifv/<%= file.path %>" onclick="copyA(event)">Copy as GIFv</a> <a href="/gifv/<%= file.path %>" onclick="copyA(event)">Copy as GIFv</a>
<% } %>
</div> </div>
</div> </div>
<% } else if (mediaType === 'image') { %> <% } else if (mediaType === 'image') { %>

BIN
bun.lockb

Binary file not shown.