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 EBPORT=3000
$ export EBAPI_KEY=changeme # For ShareX support
$ export EB_PROCESS_VIDEO=true # Enable video processing
$ npm install
$ npm start
@ -57,22 +58,38 @@ Enabled at `/upload`. Requires authentication with key. `expire` key specifies d
### 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
```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
@ -86,6 +103,7 @@ services:
- EBPORT=3000
- EBPASS=changeme
- EBAPI_KEY=changeme
- EB_PROCESS_VIDEO=true
volumes:
- ./db:/var/db
- ./uploads:/uploads

View file

@ -92,6 +92,8 @@ function onListening() {
const bind = typeof addr === "string"
? "pipe " + addr
: "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("Listening on " + bind);
}

View file

@ -24,7 +24,10 @@ class FileUploader {
this.dropArea = document.getElementById('dropArea');
this.gallery = document.getElementById('gallery');
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() {

View file

@ -26,6 +26,8 @@ import {
processUploadedMedia,
} 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?
/**Middleware to grab media from media database */
@ -65,6 +67,11 @@ const fetchMedia: Middleware = (req, res, next) => {
const router = express.Router();
router.get('/progress-updates', (req, res) => {
if (!processVideo) {
res.status(404).send('Video processing is disabled');
return;
}
console.log("SSE connection requested"); // Debug log
res.setHeader('Content-Type', 'text/event-stream');
@ -96,12 +103,12 @@ router.get(
fetchMedia,
(req: Request, res: Response) => {
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) => {
res.render("partials/_fileList", { user: req.user });
res.render("partials/_fileList", { user: req.user, processVideo });
});
router.get(
@ -189,21 +196,36 @@ router.get("/oembed/:file",
}
);
router.post(
"/",
[
checkAuth,
upload.array("fileupload"),
handleUpload,
fetchMedia,
processUploadedMedia,
createEmbedData,
],
(req: Request, res: Response) => {
return res.render("partials/_fileList", { user: req.user }); // Render only the file list partial
}
);
if (processVideo) {
router.post(
"/",
[
checkAuth,
upload.array("fileupload"),
handleUpload,
fetchMedia,
processUploadedMedia,
createEmbedData,
],
(req: Request, res: Response) => {
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(
"/sharex",
[checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload],

View file

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

View file

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

View file

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

BIN
bun.lockb

Binary file not shown.