mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-13 22:23:47 +01:00
Video attachment support!
This commit is contained in:
parent
7db5c02189
commit
73923e269a
78
package-lock.json
generated
78
package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"exif-be-gone": "^1.3.1",
|
"exif-be-gone": "^1.3.1",
|
||||||
"fast-zlib": "^2.0.1",
|
"fast-zlib": "^2.0.1",
|
||||||
"file-type": "16.5",
|
"file-type": "16.5",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"i18next": "^21.9.2",
|
"i18next": "^21.9.2",
|
||||||
"i18next-http-middleware": "^3.2.1",
|
"i18next-http-middleware": "^3.2.1",
|
||||||
@ -50,6 +51,7 @@
|
|||||||
"@types/amqplib": "^0.8.2",
|
"@types/amqplib": "^0.8.2",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
|
"@types/fluent-ffmpeg": "^2.1.20",
|
||||||
"@types/i18next-node-fs-backend": "^2.1.1",
|
"@types/i18next-node-fs-backend": "^2.1.1",
|
||||||
"@types/json-bigint": "^1.0.1",
|
"@types/json-bigint": "^1.0.1",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
@ -1659,6 +1661,15 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/fluent-ffmpeg": {
|
||||||
|
"version": "2.1.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz",
|
||||||
|
"integrity": "sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/i18next-node-fs-backend": {
|
"node_modules/@types/i18next-node-fs-backend": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.1.tgz",
|
||||||
@ -2115,6 +2126,11 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/async": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -3204,6 +3220,29 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fluent-ffmpeg": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"async": ">=0.2.9",
|
||||||
|
"which": "^1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fluent-ffmpeg/node_modules/which": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"which": "bin/which"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
@ -3740,8 +3779,7 @@
|
|||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
@ -7561,6 +7599,15 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/fluent-ffmpeg": {
|
||||||
|
"version": "2.1.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz",
|
||||||
|
"integrity": "sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/i18next-node-fs-backend": {
|
"@types/i18next-node-fs-backend": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.1.tgz",
|
||||||
@ -7947,6 +7994,11 @@
|
|||||||
"tslib": "^2.0.1"
|
"tslib": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"async": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
|
||||||
|
},
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
@ -8763,6 +8815,25 @@
|
|||||||
"unpipe": "~1.0.0"
|
"unpipe": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fluent-ffmpeg": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==",
|
||||||
|
"requires": {
|
||||||
|
"async": ">=0.2.9",
|
||||||
|
"which": "^1.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"which": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||||
|
"requires": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
@ -9160,8 +9231,7 @@
|
|||||||
"isexe": {
|
"isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"@types/amqplib": "^0.8.2",
|
"@types/amqplib": "^0.8.2",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
|
"@types/fluent-ffmpeg": "^2.1.20",
|
||||||
"@types/i18next-node-fs-backend": "^2.1.1",
|
"@types/i18next-node-fs-backend": "^2.1.1",
|
||||||
"@types/json-bigint": "^1.0.1",
|
"@types/json-bigint": "^1.0.1",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
@ -58,6 +59,7 @@
|
|||||||
"exif-be-gone": "^1.3.1",
|
"exif-be-gone": "^1.3.1",
|
||||||
"fast-zlib": "^2.0.1",
|
"fast-zlib": "^2.0.1",
|
||||||
"file-type": "16.5",
|
"file-type": "16.5",
|
||||||
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"i18next": "^21.9.2",
|
"i18next": "^21.9.2",
|
||||||
"i18next-http-middleware": "^3.2.1",
|
"i18next-http-middleware": "^3.2.1",
|
||||||
|
@ -5,6 +5,9 @@ import FileType from "file-type";
|
|||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
import { multer } from "../util/multer";
|
import { multer } from "../util/multer";
|
||||||
import imageSize from "image-size";
|
import imageSize from "image-size";
|
||||||
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
|
import Path from "path";
|
||||||
|
import { Duplex, Readable, Transform, Writable } from "stream";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -15,6 +18,14 @@ const SANITIZED_CONTENT_TYPE = [
|
|||||||
"application/xhtml+xml",
|
"application/xhtml+xml",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const probe = (file: string): Promise<ffmpeg.FfprobeData> => new Promise((resolve, reject) => {
|
||||||
|
ffmpeg.setFfprobePath(process.env.FFPROBE_PATH as string);
|
||||||
|
ffmpeg.ffprobe(file, (err, data) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:channel_id",
|
"/:channel_id",
|
||||||
multer.single("file"),
|
multer.single("file"),
|
||||||
@ -44,6 +55,13 @@ router.post(
|
|||||||
height = dimensions.height;
|
height = dimensions.height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (mimetype.includes("video") && process.env.FFPROBE_PATH) {
|
||||||
|
const root = process.env.STORAGE_LOCATION || "../"; // hmm, stolen from FileStorage
|
||||||
|
const out = await probe(Path.join(root, path));
|
||||||
|
const stream = out.streams[0]; // hmm
|
||||||
|
width = stream.width;
|
||||||
|
height = stream.height;
|
||||||
|
}
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
id,
|
id,
|
||||||
@ -63,10 +81,10 @@ router.get(
|
|||||||
"/:channel_id/:id/:filename",
|
"/:channel_id/:id/:filename",
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const { channel_id, id, filename } = req.params;
|
const { channel_id, id, filename } = req.params;
|
||||||
|
const { format } = req.query;
|
||||||
|
|
||||||
const file = await storage.get(
|
const path = `attachments/${channel_id}/${id}/${filename}`;
|
||||||
`attachments/${channel_id}/${id}/${filename}`,
|
let file = await storage.get(path);
|
||||||
);
|
|
||||||
if (!file) throw new HTTPError("File not found");
|
if (!file) throw new HTTPError("File not found");
|
||||||
const type = await FileType.fromBuffer(file);
|
const type = await FileType.fromBuffer(file);
|
||||||
let content_type = type?.mime || "application/octet-stream";
|
let content_type = type?.mime || "application/octet-stream";
|
||||||
@ -75,6 +93,26 @@ router.get(
|
|||||||
content_type = "application/octet-stream";
|
content_type = "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lol, super gross
|
||||||
|
if (content_type.includes("video") && format == "jpeg" && process.env.FFMPEG_PATH) {
|
||||||
|
const promise = (): Promise<Buffer> => new Promise((resolve, reject) => {
|
||||||
|
ffmpeg.setFfmpegPath(process.env.FFMPEG_PATH as string);
|
||||||
|
const out: any[] = [];
|
||||||
|
const cmd = ffmpeg(Readable.from(file as Buffer))
|
||||||
|
.format("mjpeg")
|
||||||
|
.frames(1)
|
||||||
|
.on("end", () => resolve(Buffer.concat(out)))
|
||||||
|
.on("error", (err) => reject(err))
|
||||||
|
const stream = cmd.pipe();
|
||||||
|
stream.on("data", (data) => {
|
||||||
|
out.push(data)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const res = await promise();
|
||||||
|
file = res;
|
||||||
|
content_type = "jpeg";
|
||||||
|
}
|
||||||
|
|
||||||
res.set("Content-Type", content_type);
|
res.set("Content-Type", content_type);
|
||||||
res.set("Cache-Control", "public, max-age=31536000");
|
res.set("Cache-Control", "public, max-age=31536000");
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export class FileStorage implements Storage {
|
|||||||
|
|
||||||
async set(path: string, value: any) {
|
async set(path: string, value: any) {
|
||||||
path = getPath(path);
|
path = getPath(path);
|
||||||
if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path));
|
if (!fs.existsSync(dirname(path))) fs.mkdirSync(dirname(path), { recursive: true });
|
||||||
|
|
||||||
value = Readable.from(value);
|
value = Readable.from(value);
|
||||||
const cleaned_file = fs.createWriteStream(path);
|
const cleaned_file = fs.createWriteStream(path);
|
||||||
|
Loading…
Reference in New Issue
Block a user