linting
This commit is contained in:
parent
807e50388a
commit
832189a346
10 changed files with 518 additions and 299 deletions
52
.eslintrc.js
52
.eslintrc.js
|
@ -1,39 +1,21 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"env": {
|
env: {
|
||||||
"browser": true,
|
browser: true,
|
||||||
"es2021": true,
|
es2021: true,
|
||||||
"node": true
|
node: true,
|
||||||
},
|
},
|
||||||
"extends": [
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
"eslint:recommended",
|
overrides: [],
|
||||||
"plugin:@typescript-eslint/recommended"
|
parser: "@typescript-eslint/parser",
|
||||||
],
|
parserOptions: {
|
||||||
"overrides": [
|
ecmaVersion: "latest",
|
||||||
],
|
sourceType: "module",
|
||||||
"parser": "@typescript-eslint/parser",
|
},
|
||||||
"parserOptions": {
|
plugins: ["@typescript-eslint"],
|
||||||
"ecmaVersion": "latest",
|
rules: {
|
||||||
"sourceType": "module"
|
indent: ["error", 2, { SwitchCase: 1 }],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
quotes: ["error", "double"],
|
||||||
|
semi: ["error", "always"],
|
||||||
},
|
},
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { extension, videoExtensions, imageExtensions } from "./lib";
|
import { extension, videoExtensions, imageExtensions } from "./lib";
|
||||||
|
|
||||||
import ffmpeg, { FfprobeData, ffprobe } from 'fluent-ffmpeg';
|
import ffmpeg, { FfprobeData, ffprobe } from "fluent-ffmpeg";
|
||||||
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
|
import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
|
||||||
import ffprobeInstaller from '@ffprobe-installer/ffprobe';
|
import ffprobeInstaller from "@ffprobe-installer/ffprobe";
|
||||||
import which from 'which';
|
import which from "which";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum to represent different types of video encoding methods.
|
* Enum to represent different types of video encoding methods.
|
||||||
|
@ -16,11 +16,11 @@ import which from 'which';
|
||||||
* @property {string} APPLE - Uses the h264_videotoolbox codec for Apple GPU/MediaEngine-based encoding
|
* @property {string} APPLE - Uses the h264_videotoolbox codec for Apple GPU/MediaEngine-based encoding
|
||||||
*/
|
*/
|
||||||
export enum EncodingType {
|
export enum EncodingType {
|
||||||
CPU = 'libx264',
|
CPU = "libx264",
|
||||||
NVIDIA = 'h264_nvenc',
|
NVIDIA = "h264_nvenc",
|
||||||
AMD = 'h264_amf',
|
AMD = "h264_amf",
|
||||||
INTEL = 'h264_qsv',
|
INTEL = "h264_qsv",
|
||||||
APPLE = 'h264_videotoolbox'
|
APPLE = "h264_videotoolbox",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +49,11 @@ export const setEncodingType = (type: EncodingType) => {
|
||||||
* @returns {string} - The path to the executable.
|
* @returns {string} - The path to the executable.
|
||||||
* @throws Will throw an error if the executable is not found and the installer path is not available.
|
* @throws Will throw an error if the executable is not found and the installer path is not available.
|
||||||
*/
|
*/
|
||||||
const getExecutablePath = (envVar: string, executable: string, installer: { path: string }) => {
|
const getExecutablePath = (
|
||||||
|
envVar: string,
|
||||||
|
executable: string,
|
||||||
|
installer: { path: string },
|
||||||
|
) => {
|
||||||
if (process.env[envVar]) {
|
if (process.env[envVar]) {
|
||||||
return process.env[envVar];
|
return process.env[envVar];
|
||||||
}
|
}
|
||||||
|
@ -61,8 +65,16 @@ const getExecutablePath = (envVar: string, executable: string, installer: { path
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ffmpegPath = getExecutablePath('EB_FFMPEG_PATH', 'ffmpeg', ffmpegInstaller);
|
const ffmpegPath = getExecutablePath(
|
||||||
const ffprobePath = getExecutablePath('EB_FFPROBE_PATH', 'ffprobe', ffprobeInstaller);
|
"EB_FFMPEG_PATH",
|
||||||
|
"ffmpeg",
|
||||||
|
ffmpegInstaller,
|
||||||
|
);
|
||||||
|
const ffprobePath = getExecutablePath(
|
||||||
|
"EB_FFPROBE_PATH",
|
||||||
|
"ffprobe",
|
||||||
|
ffprobeInstaller,
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`Using ffmpeg from path: ${ffmpegPath}`);
|
console.log(`Using ffmpeg from path: ${ffmpegPath}`);
|
||||||
console.log(`Using ffprobe from path: ${ffprobePath}`);
|
console.log(`Using ffprobe from path: ${ffprobePath}`);
|
||||||
|
@ -74,10 +86,16 @@ const checkEnvForEncoder = () => {
|
||||||
const envEncoder = process.env.EB_ENCODER?.toUpperCase();
|
const envEncoder = process.env.EB_ENCODER?.toUpperCase();
|
||||||
|
|
||||||
if (envEncoder && Object.keys(EncodingType).includes(envEncoder)) {
|
if (envEncoder && Object.keys(EncodingType).includes(envEncoder)) {
|
||||||
setEncodingType(EncodingType[envEncoder as keyof typeof EncodingType] as EncodingType);
|
setEncodingType(
|
||||||
console.log(`Setting encoding type to ${envEncoder} based on environment variable.`);
|
EncodingType[envEncoder as keyof typeof EncodingType] as EncodingType,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Setting encoding type to ${envEncoder} based on environment variable.`,
|
||||||
|
);
|
||||||
} else if (envEncoder) {
|
} else if (envEncoder) {
|
||||||
console.warn(`Invalid encoder value "${envEncoder}" in environment variable, defaulting to CPU.`);
|
console.warn(
|
||||||
|
`Invalid encoder value "${envEncoder}" in environment variable, defaulting to CPU.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -98,13 +116,21 @@ checkEnvForEncoder();
|
||||||
* console.log(`Error: ${error}`);
|
* console.log(`Error: ${error}`);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export const ffmpegDownscale = (path: string, filename: string, extension: string) => {
|
export const ffmpegDownscale = (
|
||||||
|
path: string,
|
||||||
|
filename: string,
|
||||||
|
extension: string,
|
||||||
|
) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const outputOptions = [
|
const outputOptions = [
|
||||||
'-vf', 'scale=-2:720',
|
"-vf",
|
||||||
'-c:v', currentEncoding,
|
"scale=-2:720",
|
||||||
'-c:a', 'copy',
|
"-c:v",
|
||||||
"-pix_fmt", "yuv420p",
|
currentEncoding,
|
||||||
|
"-c:a",
|
||||||
|
"copy",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
];
|
];
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
@ -112,14 +138,18 @@ export const ffmpegDownscale = (path: string, filename: string, extension: strin
|
||||||
.input(path)
|
.input(path)
|
||||||
.outputOptions(outputOptions)
|
.outputOptions(outputOptions)
|
||||||
.output(`uploads/720p-${filename}${extension}`)
|
.output(`uploads/720p-${filename}${extension}`)
|
||||||
.on('end', () => {
|
.on("end", () => {
|
||||||
console.log(`720p copy complete using ${currentEncoding}, took ${Date.now() - startTime}ms to complete`);
|
console.log(
|
||||||
|
`720p copy complete using ${currentEncoding}, took ${
|
||||||
|
Date.now() - startTime
|
||||||
|
}ms to complete`,
|
||||||
|
);
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.on('error', (e) => reject(new Error(e)))
|
.on("error", (e) => reject(new Error(e)))
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a video to a gif or vice versa using ffmpeg with various encoding options.
|
* Convert a video to a gif or vice versa using ffmpeg with various encoding options.
|
||||||
|
@ -136,22 +166,31 @@ export const ffmpegDownscale = (path: string, filename: string, extension: strin
|
||||||
* console.log(`Error: ${error}`);
|
* console.log(`Error: ${error}`);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export const ffmpegConvert = (path: string, filename: string, extension: string) => {
|
export const ffmpegConvert = (
|
||||||
|
path: string,
|
||||||
|
filename: string,
|
||||||
|
extension: string,
|
||||||
|
) => {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const outputOptions = [
|
const outputOptions = [
|
||||||
'-vf', 'scale=-2:720',
|
"-vf",
|
||||||
'-c:v', currentEncoding,
|
"scale=-2:720",
|
||||||
'-c:a', 'copy',
|
"-c:v",
|
||||||
"-movflags", "+faststart",
|
currentEncoding,
|
||||||
"-pix_fmt", "yuv420p",
|
"-c:a",
|
||||||
]
|
"copy",
|
||||||
|
"-movflags",
|
||||||
|
"+faststart",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
|
];
|
||||||
|
|
||||||
let outputFormat: string;
|
let outputFormat: string;
|
||||||
|
|
||||||
if (videoExtensions.includes(extension)) {
|
if (videoExtensions.includes(extension)) {
|
||||||
outputFormat = '.gif';
|
outputFormat = ".gif";
|
||||||
} else if (extension == '.gif') {
|
} else if (extension == ".gif") {
|
||||||
outputFormat = '.mp4';
|
outputFormat = ".mp4";
|
||||||
} else {
|
} else {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
reject(`Submitted file is neither a video nor a gif: ${path}`);
|
reject(`Submitted file is neither a video nor a gif: ${path}`);
|
||||||
|
@ -162,24 +201,30 @@ export const ffmpegConvert = (path: string, filename: string, extension: string)
|
||||||
ffmpeg()
|
ffmpeg()
|
||||||
.input(path)
|
.input(path)
|
||||||
.outputOptions(outputOptions)
|
.outputOptions(outputOptions)
|
||||||
.output(`uploads/`)
|
.output("uploads/")
|
||||||
.outputFormat(outputFormat)
|
.outputFormat(outputFormat)
|
||||||
.output(`uploads/${filename}${outputFormat}`)
|
.output(`uploads/${filename}${outputFormat}`)
|
||||||
.on("end", function () {
|
.on("end", function () {
|
||||||
console.log(`Conversion complete, took ${Date.now() - startTime} to complete`);
|
console.log(
|
||||||
|
`Conversion complete, took ${Date.now() - startTime} to complete`,
|
||||||
|
);
|
||||||
console.log(`uploads/${filename}${outputFormat}`);
|
console.log(`uploads/${filename}${outputFormat}`);
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.on("error", (e) => reject(e))
|
.on("error", (e) => reject(e))
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ffProbe = (path: string, filename: string, extension: string) => {
|
export const ffProbe = async (
|
||||||
|
path: string,
|
||||||
|
filename: string,
|
||||||
|
extension: string,
|
||||||
|
) => {
|
||||||
return new Promise<FfprobeData>((resolve, reject) => {
|
return new Promise<FfprobeData>((resolve, reject) => {
|
||||||
ffprobe(path, (err, data) => {
|
ffprobe(path, (err, data) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
|
@ -2,25 +2,45 @@
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface User {
|
interface User {
|
||||||
id? : number | string,
|
id?: number | string;
|
||||||
username: string,
|
username: string;
|
||||||
hashed_password?: any,
|
hashed_password?: any;
|
||||||
salt?: any
|
salt?: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**Splits a file name into its name and then its extension */
|
/**Splits a file name into its name and then its extension */
|
||||||
export function extension(str: string) {
|
export function extension(str: string) {
|
||||||
const file = str.split("/").pop();
|
const file = str.split("/").pop();
|
||||||
return [file.substr(0,file.lastIndexOf(".")),file.substr(file.lastIndexOf("."),file.length).toLowerCase()];
|
return [
|
||||||
|
file.substr(0, file.lastIndexOf(".")),
|
||||||
|
file.substr(file.lastIndexOf("."), file.length).toLowerCase(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
/**Type for user data */
|
/**Type for user data */
|
||||||
export interface User {
|
export interface User {
|
||||||
id? : number | string,
|
id?: number | string;
|
||||||
username: string,
|
username: string;
|
||||||
hashed_password?: any,
|
hashed_password?: any;
|
||||||
salt?: any
|
salt?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const videoExtensions = [".mp4", ".mov", ".avi", ".flv", ".mkv", ".wmv", ".webm"];
|
export const videoExtensions = [
|
||||||
export const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".tiff", ".webp"];
|
".mp4",
|
||||||
|
".mov",
|
||||||
|
".avi",
|
||||||
|
".flv",
|
||||||
|
".mkv",
|
||||||
|
".wmv",
|
||||||
|
".webm",
|
||||||
|
];
|
||||||
|
export const imageExtensions = [
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".png",
|
||||||
|
".gif",
|
||||||
|
".bmp",
|
||||||
|
".svg",
|
||||||
|
".tiff",
|
||||||
|
".webp",
|
||||||
|
];
|
||||||
|
|
|
@ -5,7 +5,8 @@ import process from "process";
|
||||||
|
|
||||||
import { extension, videoExtensions, imageExtensions } from "./lib";
|
import { extension, videoExtensions, imageExtensions } from "./lib";
|
||||||
import { insertToDB } from "./db";
|
import { insertToDB } from "./db";
|
||||||
import {ffmpegDownscale} from "./ffmpeg";
|
import { ffmpegDownscale, ffProbe } from "./ffmpeg";
|
||||||
|
import { ffprobe } from "fluent-ffmpeg";
|
||||||
|
|
||||||
export const checkAuth: Middleware = (req, res, next) => {
|
export const checkAuth: Middleware = (req, res, next) => {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
|
@ -16,17 +17,26 @@ export const checkAuth: Middleware = (req, res, next) => {
|
||||||
|
|
||||||
/**Checks shareX auth key */
|
/**Checks shareX auth key */
|
||||||
export const checkSharexAuth: Middleware = (req, res, next) => {
|
export const checkSharexAuth: Middleware = (req, res, next) => {
|
||||||
const auth = process.env.EBAPI_KEY || process.env.EBPASS || "pleaseSetAPI_KEY";
|
const auth =
|
||||||
|
process.env.EBAPI_KEY || process.env.EBPASS || "pleaseSetAPI_KEY";
|
||||||
let key = null;
|
let key = null;
|
||||||
|
|
||||||
if (req.headers["key"]) {
|
if (req.headers["key"]) {
|
||||||
key = req.headers["key"];
|
key = req.headers["key"];
|
||||||
} else {
|
} else {
|
||||||
return res.status(400).send("{success: false, message: \"No key provided\", fix: \"Provide a key\"}");
|
return res
|
||||||
|
.status(400)
|
||||||
|
.send(
|
||||||
|
"{success: false, message: 'No key provided', fix: 'Provide a key'}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth != key) {
|
if (auth != key) {
|
||||||
return res.status(401).send("{success: false, message: '\"'Invalid key\", fix: \"Provide a valid key\"}");
|
return res
|
||||||
|
.status(401)
|
||||||
|
.send(
|
||||||
|
"{success: false, message: 'Invalid key', fix: 'Provide a valid key'}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortKey = key.substr(0, 3) + "...";
|
const shortKey = key.substr(0, 3) + "...";
|
||||||
|
@ -36,25 +46,41 @@ export const checkSharexAuth: Middleware = (req, res, next) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**Creates oembed json file for embed metadata */
|
/**Creates oembed json file for embed metadata */
|
||||||
export const createEmbedData: Middleware = (req, res, next) => {
|
export const createEmbedData: Middleware = async (req, res, next) => {
|
||||||
const files = req.files as Express.Multer.File[];
|
const files = req.files as Express.Multer.File[];
|
||||||
for (const file in files) {
|
for (const file in files) {
|
||||||
const nameAndExtension = extension(files[file].originalname);
|
const nameAndExtension = extension(files[file].originalname);
|
||||||
|
const ffProbeData = await ffProbe(
|
||||||
|
`uploads/${files[file].originalname}`,
|
||||||
|
nameAndExtension[0],
|
||||||
|
nameAndExtension[1],
|
||||||
|
);
|
||||||
|
const width = ffProbeData.streams[0].width;
|
||||||
|
const height = ffProbeData.streams[0].height;
|
||||||
|
|
||||||
const oembed = {
|
const oembed = {
|
||||||
type: "video",
|
type: "video",
|
||||||
version: "1.0",
|
version: "1.0",
|
||||||
provider_name: "embedder",
|
provider_name: "embedder",
|
||||||
provider_url: "https://github.com/WaveringAna/embedder",
|
provider_url: "https://github.com/WaveringAna/embedder",
|
||||||
cache_age: 86400,
|
cache_age: 86400,
|
||||||
html: `<iframe src='${req.protocol}://${req.get("host")}/gifv/${nameAndExtension[0]}${nameAndExtension[1]}'></iframe>`,
|
html: `<iframe src='${req.protocol}://${req.get("host")}/gifv/${
|
||||||
width: 640,
|
nameAndExtension[0]
|
||||||
height: 360
|
}${nameAndExtension[1]}'></iframe>`,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFile(`uploads/oembed-${nameAndExtension[0]}${nameAndExtension[1]}.json`, JSON.stringify(oembed), function (err) {
|
fs.writeFile(
|
||||||
|
`uploads/oembed-${nameAndExtension[0]}${nameAndExtension[1]}.json`,
|
||||||
|
JSON.stringify(oembed),
|
||||||
|
function (err) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
console.log(`oembed file created ${nameAndExtension[0]}${nameAndExtension[1]}.json`);
|
console.log(
|
||||||
});
|
`oembed file created ${nameAndExtension[0]}${nameAndExtension[1]}.json`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
@ -67,7 +93,10 @@ export const convertTo720p: Middleware = (req, res, next) => {
|
||||||
const nameAndExtension = extension(files[file].originalname);
|
const nameAndExtension = extension(files[file].originalname);
|
||||||
|
|
||||||
//Skip if not a video
|
//Skip if not a video
|
||||||
if (!videoExtensions.includes(nameAndExtension[1]) && nameAndExtension[1] !== ".gif") {
|
if (
|
||||||
|
!videoExtensions.includes(nameAndExtension[1]) &&
|
||||||
|
nameAndExtension[1] !== ".gif"
|
||||||
|
) {
|
||||||
console.log(`${files[file].originalname} is not a video file`);
|
console.log(`${files[file].originalname} is not a video file`);
|
||||||
console.log(nameAndExtension[1]);
|
console.log(nameAndExtension[1]);
|
||||||
continue;
|
continue;
|
||||||
|
@ -75,9 +104,15 @@ export const convertTo720p: Middleware = (req, res, next) => {
|
||||||
|
|
||||||
console.log(`Creating 720p for ${files[file].originalname}`);
|
console.log(`Creating 720p for ${files[file].originalname}`);
|
||||||
|
|
||||||
ffmpegDownscale(`uploads/${nameAndExtension[0]}${nameAndExtension[1]}`, nameAndExtension[0], nameAndExtension[1]).then(() => {
|
ffmpegDownscale(
|
||||||
|
`uploads/${nameAndExtension[0]}${nameAndExtension[1]}`,
|
||||||
|
nameAndExtension[0],
|
||||||
|
nameAndExtension[1],
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
//Nothing for now, can fire event flag that it is done to front end when react conversion is done
|
//Nothing for now, can fire event flag that it is done to front end when react conversion is done
|
||||||
}).catch((error) => {
|
})
|
||||||
|
.catch((error) => {
|
||||||
console.log(`Error: ${error}`);
|
console.log(`Error: ${error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -92,16 +127,17 @@ export const handleUpload: Middleware = (req, res, next) => {
|
||||||
return res.status(400).send("No files were uploaded.");
|
return res.status(400).send("No files were uploaded.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = (req.files) ? req.files as Express.Multer.File[] : req.file; //Check if a single file was uploaded or multiple
|
const files = req.files ? (req.files as Express.Multer.File[]) : req.file; //Check if a single file was uploaded or multiple
|
||||||
const username = (req.user) ? req.user.username : "sharex"; //if no username was provided, we can presume that it is sharex
|
const username = req.user ? req.user.username : "sharex"; //if no username was provided, we can presume that it is sharex
|
||||||
const expireDate: Date = (req.body.expire) ? new Date(Date.now() + (req.body.expire * 24 * 60 * 60 * 1000)) : null;
|
const expireDate: Date = req.body.expire
|
||||||
|
? new Date(Date.now() + req.body.expire * 24 * 60 * 60 * 1000)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (files instanceof Array) {
|
if (files instanceof Array) {
|
||||||
for (const file in files) {
|
for (const file in files) {
|
||||||
insertToDB(files[file].filename, expireDate, username);
|
insertToDB(files[file].filename, expireDate, username);
|
||||||
}
|
}
|
||||||
} else
|
} else insertToDB(files.filename, expireDate, username);
|
||||||
insertToDB(files.filename, expireDate, username);
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,25 +4,31 @@ import multer, {FileFilterCallback} from "multer";
|
||||||
import { db, MediaRow } from "./db";
|
import { db, MediaRow } from "./db";
|
||||||
import { extension } from "./lib";
|
import { extension } from "./lib";
|
||||||
|
|
||||||
export type DestinationCallback = (error: Error | null, destination: string) => void
|
export type DestinationCallback = (
|
||||||
export type FileNameCallback = (error: Error | null, filename: string) => void
|
error: Error | null,
|
||||||
|
destination: string,
|
||||||
|
) => void;
|
||||||
|
export type FileNameCallback = (error: Error | null, filename: string) => void;
|
||||||
|
|
||||||
export const fileStorage = multer.diskStorage({
|
export const fileStorage = multer.diskStorage({
|
||||||
destination: (
|
destination: (
|
||||||
request: Request,
|
request: Request,
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
callback: DestinationCallback
|
callback: DestinationCallback,
|
||||||
): void => {
|
): void => {
|
||||||
callback(null, __dirname + "/../../uploads");
|
callback(null, __dirname + "/../../uploads");
|
||||||
},
|
},
|
||||||
filename: (
|
filename: (
|
||||||
request: Request,
|
request: Request,
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
callback: FileNameCallback
|
callback: FileNameCallback,
|
||||||
): void => {
|
): void => {
|
||||||
const nameAndExtension = extension(file.originalname);
|
const nameAndExtension = extension(file.originalname);
|
||||||
console.log(`Uploading ${file}`);
|
console.log(`Uploading ${file}`);
|
||||||
db.all("SELECT * FROM media WHERE path = ?", [nameAndExtension[0] + nameAndExtension[1]], (err: Error, exists: []) => {
|
db.all(
|
||||||
|
"SELECT * FROM media WHERE path = ?",
|
||||||
|
[nameAndExtension[0] + nameAndExtension[1]],
|
||||||
|
(err: Error, exists: []) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
|
@ -30,20 +36,35 @@ export const fileStorage = multer.diskStorage({
|
||||||
if (exists.length != 0) {
|
if (exists.length != 0) {
|
||||||
const suffix = new Date().getTime() / 1000;
|
const suffix = new Date().getTime() / 1000;
|
||||||
|
|
||||||
if (request.body.title == "" || request.body.title == null || request.body.title == undefined) {
|
if (
|
||||||
callback(null, nameAndExtension[0] + "-" + suffix + nameAndExtension[1]);
|
request.body.title == "" ||
|
||||||
|
request.body.title == null ||
|
||||||
|
request.body.title == undefined
|
||||||
|
) {
|
||||||
|
callback(
|
||||||
|
null,
|
||||||
|
nameAndExtension[0] + "-" + suffix + nameAndExtension[1],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback(null, request.body.title + "-" + suffix + nameAndExtension[1]);
|
callback(
|
||||||
|
null,
|
||||||
|
request.body.title + "-" + suffix + nameAndExtension[1],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (request.body.title == "" || request.body.title == null || request.body.title == undefined) {
|
if (
|
||||||
|
request.body.title == "" ||
|
||||||
|
request.body.title == null ||
|
||||||
|
request.body.title == undefined
|
||||||
|
) {
|
||||||
callback(null, nameAndExtension[0] + nameAndExtension[1]);
|
callback(null, nameAndExtension[0] + nameAndExtension[1]);
|
||||||
} else {
|
} else {
|
||||||
callback(null, request.body.title + nameAndExtension[1]);
|
callback(null, request.body.title + nameAndExtension[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const allowedMimeTypes = [
|
export const allowedMimeTypes = [
|
||||||
|
@ -56,13 +77,13 @@ export const allowedMimeTypes = [
|
||||||
"video/mov",
|
"video/mov",
|
||||||
"video/webm",
|
"video/webm",
|
||||||
"audio/mpeg",
|
"audio/mpeg",
|
||||||
"audio/ogg"
|
"audio/ogg",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const fileFilter = (
|
export const fileFilter = (
|
||||||
request: Request,
|
request: Request,
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
callback: FileFilterCallback
|
callback: FileFilterCallback,
|
||||||
): void => {
|
): void => {
|
||||||
if (allowedMimeTypes.includes(file.mimetype)) {
|
if (allowedMimeTypes.includes(file.mimetype)) {
|
||||||
callback(null, true);
|
callback(null, true);
|
||||||
|
@ -70,3 +91,4 @@ export const fileFilter = (
|
||||||
callback(null, false);
|
callback(null, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,36 +8,49 @@ import {db, UserRow} from "../lib/db";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
passport.use(new LocalStrategy(function verify(username, password, cb) {
|
passport.use(
|
||||||
db.get("SELECT * FROM users WHERE username = ?", [username], function(err: Error, row: UserRow) {
|
new LocalStrategy(function verify(username, password, cb) {
|
||||||
|
db.get(
|
||||||
|
"SELECT * FROM users WHERE username = ?",
|
||||||
|
[username],
|
||||||
|
function (err: Error, row: UserRow) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return cb(null, false, {
|
return cb(null, false, {
|
||||||
message: "Incorrect username or password."
|
message: "Incorrect username or password.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
crypto.pbkdf2(password, row.salt, 310000, 32, "sha256", function(err, hashedPassword) {
|
crypto.pbkdf2(
|
||||||
|
password,
|
||||||
|
row.salt,
|
||||||
|
310000,
|
||||||
|
32,
|
||||||
|
"sha256",
|
||||||
|
function (err, hashedPassword) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
|
if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
|
||||||
return cb(null, false, {
|
return cb(null, false, {
|
||||||
message: "Incorrect username or password."
|
message: "Incorrect username or password.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return cb(null, row);
|
return cb(null, row);
|
||||||
});
|
},
|
||||||
});
|
);
|
||||||
}));
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
passport.serializeUser(function (user: User, cb) {
|
passport.serializeUser(function (user: User, cb) {
|
||||||
process.nextTick(function () {
|
process.nextTick(function () {
|
||||||
cb(null, {
|
cb(null, {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username
|
username: user.username,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -52,10 +65,13 @@ router.get("/login", function(req, res) {
|
||||||
res.render("login");
|
res.render("login");
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/login/password", passport.authenticate("local", {
|
router.post(
|
||||||
|
"/login/password",
|
||||||
|
passport.authenticate("local", {
|
||||||
successRedirect: "/",
|
successRedirect: "/",
|
||||||
failureRedirect: "/login"
|
failureRedirect: "/login",
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
router.post("/logout", function (req, res, next) {
|
router.post("/logout", function (req, res, next) {
|
||||||
req.logout(function (err) {
|
req.logout(function (err) {
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import type {RequestHandler as Middleware, Request, Response, NextFunction} from "express";
|
import type {
|
||||||
|
RequestHandler as Middleware,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
NextFunction,
|
||||||
|
} from "express";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import imageProbe from "probe-image-size";
|
import imageProbe from "probe-image-size";
|
||||||
|
@ -10,14 +15,23 @@ import fs from "fs";
|
||||||
import { extension, videoExtensions } from "../lib/lib";
|
import { extension, videoExtensions } from "../lib/lib";
|
||||||
import { db, MediaRow, getPath, deleteId } from "../lib/db";
|
import { db, MediaRow, getPath, deleteId } from "../lib/db";
|
||||||
import { fileStorage } from "../lib/multer";
|
import { fileStorage } from "../lib/multer";
|
||||||
import {checkAuth, checkSharexAuth, convertTo720p, createEmbedData, handleUpload} from "../lib/middleware";
|
import {
|
||||||
|
checkAuth,
|
||||||
|
checkSharexAuth,
|
||||||
|
convertTo720p,
|
||||||
|
createEmbedData,
|
||||||
|
handleUpload,
|
||||||
|
} from "../lib/middleware";
|
||||||
|
|
||||||
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 */
|
||||||
const fetchMedia: Middleware = (req, res, next) => {
|
const fetchMedia: Middleware = (req, res, next) => {
|
||||||
const admin: boolean = req.user.username == "admin" ? true : false;
|
const admin: boolean = req.user.username == "admin" ? true : false;
|
||||||
/**Check if the user is an admin, if so, show all posts from all users */
|
/**Check if the user is an admin, if so, show all posts from all users */
|
||||||
const query: string = admin == true ? "SELECT * FROM media" : `SELECT * FROM media WHERE username = '${req.user.username}'`;
|
const query: string =
|
||||||
|
admin == true
|
||||||
|
? "SELECT * FROM media"
|
||||||
|
: `SELECT * FROM media WHERE username = '${req.user.username}'`;
|
||||||
|
|
||||||
db.all(query, (err: Error, rows: []) => {
|
db.all(query, (err: Error, rows: []) => {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
@ -27,7 +41,7 @@ const fetchMedia: Middleware = (req, res, next) => {
|
||||||
path: row.path,
|
path: row.path,
|
||||||
expire: row.expire,
|
expire: row.expire,
|
||||||
username: row.username,
|
username: row.username,
|
||||||
url: "/" + row.id
|
url: "/" + row.id,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
res.locals.files = files.reverse(); //reverse so newest files appear first
|
res.locals.files = files.reverse(); //reverse so newest files appear first
|
||||||
|
@ -38,53 +52,106 @@ const fetchMedia: Middleware = (req, res, next) => {
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req: Request, res: Response, next: NextFunction) => {
|
router.get(
|
||||||
if (!req.user)
|
"/",
|
||||||
return res.render("home");
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (!req.user) return res.render("home");
|
||||||
next();
|
next();
|
||||||
}, fetchMedia, (req: Request, res: Response) => {
|
},
|
||||||
|
fetchMedia,
|
||||||
|
(req: Request, res: Response) => {
|
||||||
res.locals.filter = null;
|
res.locals.filter = null;
|
||||||
res.render("index", { user: req.user });
|
res.render("index", { user: req.user });
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.get("/gifv/:file", async (req: Request, res: Response, next: NextFunction) => {
|
router.get(
|
||||||
const url = `${req.protocol}://${req.get("host")}/uploads/${req.params.file}`;
|
"/gifv/:file",
|
||||||
let width; let height;
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const url = `${req.protocol}://${req.get("host")}/uploads/${
|
||||||
|
req.params.file
|
||||||
|
}`;
|
||||||
|
let width;
|
||||||
|
let height;
|
||||||
|
|
||||||
const nameAndExtension = extension(`uploads/${req.params.file}`);
|
const nameAndExtension = extension(`uploads/${req.params.file}`);
|
||||||
if (nameAndExtension[1] == ".mp4" || nameAndExtension[1] == ".mov" || nameAndExtension[1] == ".webm" || nameAndExtension[1] == ".gif") {
|
if (
|
||||||
let imageData = ffProbe(`uploads/${req.params.file}`, nameAndExtension[0], nameAndExtension[1]);
|
nameAndExtension[1] == ".mp4" ||
|
||||||
|
nameAndExtension[1] == ".mov" ||
|
||||||
|
nameAndExtension[1] == ".webm" ||
|
||||||
|
nameAndExtension[1] == ".gif"
|
||||||
|
) {
|
||||||
|
const imageData = ffProbe(
|
||||||
|
`uploads/${req.params.file}`,
|
||||||
|
nameAndExtension[0],
|
||||||
|
nameAndExtension[1],
|
||||||
|
);
|
||||||
|
|
||||||
width = (await imageData).streams[0].width;
|
width = (await imageData).streams[0].width;
|
||||||
height = (await imageData).streams[0].height;
|
height = (await imageData).streams[0].height;
|
||||||
|
|
||||||
return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: width, height: height });
|
return res.render("gifv", {
|
||||||
|
url: url,
|
||||||
|
host: `${req.protocol}://${req.get("host")}`,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const imageData = await imageProbe(fs.createReadStream(`uploads/${req.params.file}`));
|
const imageData = await imageProbe(
|
||||||
return res.render("gifv", { url: url, host: `${req.protocol}://${req.get("host")}`, width: imageData.width, height: imageData.height });
|
fs.createReadStream(`uploads/${req.params.file}`),
|
||||||
|
);
|
||||||
|
return res.render("gifv", {
|
||||||
|
url: url,
|
||||||
|
host: `${req.protocol}://${req.get("host")}`,
|
||||||
|
width: imageData.width,
|
||||||
|
height: imageData.height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post("/", [checkAuth, upload.array("fileupload"), convertTo720p, createEmbedData, handleUpload], (req: Request, res: Response) => {
|
router.post(
|
||||||
|
"/",
|
||||||
|
[
|
||||||
|
checkAuth,
|
||||||
|
upload.array("fileupload"),
|
||||||
|
convertTo720p,
|
||||||
|
createEmbedData,
|
||||||
|
handleUpload,
|
||||||
|
],
|
||||||
|
(req: Request, res: Response) => {
|
||||||
res.redirect("/");
|
res.redirect("/");
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post("/sharex", [checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload], (req: Request, res: Response) => {
|
router.post(
|
||||||
return res.send(`${req.protocol}://${req.get("host")}/uploads/${req.file.filename}`);
|
"/sharex",
|
||||||
});
|
[checkSharexAuth, upload.single("fileupload"), createEmbedData, handleUpload],
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
return res.send(
|
||||||
|
`${req.protocol}://${req.get("host")}/uploads/${req.file.filename}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
router.post("/:id(\\d+)/delete", [checkAuth], async (req: Request, res: Response) => {
|
router.post(
|
||||||
|
"/:id(\\d+)/delete",
|
||||||
|
[checkAuth],
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
const path: any = await getPath(req.params.id);
|
const path: any = await getPath(req.params.id);
|
||||||
|
|
||||||
const nameAndExtension = extension(path.path);
|
const nameAndExtension = extension(path.path);
|
||||||
|
|
||||||
const filesToDelete = [path.path, "oembed-" + path.path + ".json"];
|
const filesToDelete = [path.path, "oembed-" + path.path + ".json"];
|
||||||
|
|
||||||
if (videoExtensions.includes(nameAndExtension[1]) || nameAndExtension[1] == ".gif") {
|
if (
|
||||||
|
videoExtensions.includes(nameAndExtension[1]) ||
|
||||||
|
nameAndExtension[1] == ".gif"
|
||||||
|
) {
|
||||||
filesToDelete.push("720p-" + path.path);
|
filesToDelete.push("720p-" + path.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
filesToDelete.forEach(path => {
|
filesToDelete.forEach((path) => {
|
||||||
fs.unlink(path, async (err) => {
|
fs.unlink(path, async (err) => {
|
||||||
console.log(`Deleting ${path}`);
|
console.log(`Deleting ${path}`);
|
||||||
if (err && err.errno == -4058) {
|
if (err && err.errno == -4058) {
|
||||||
|
@ -95,6 +162,8 @@ router.post("/:id(\\d+)/delete", [checkAuth], async (req: Request, res: Response
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.redirect("/");
|
return res.redirect("/");
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ FROM node:16-alpine AS BUILD_IMAGE
|
||||||
|
|
||||||
RUN apk add curl
|
RUN apk add curl
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
COPY tsconfig.json ./
|
COPY tsconfig.json ./
|
||||||
COPY /app ./app
|
COPY /app ./app
|
||||||
|
@ -11,6 +12,7 @@ RUN npm prune --production
|
||||||
|
|
||||||
FROM node:16-alpine
|
FROM node:16-alpine
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
COPY --from=BUILD_IMAGE /node_modules ./node_modules
|
COPY --from=BUILD_IMAGE /node_modules ./node_modules
|
||||||
COPY --from=BUILD_IMAGE /dist ./dist
|
COPY --from=BUILD_IMAGE /dist ./dist
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
|
import ffmpegInstaller from "@ffmpeg-installer/ffmpeg";
|
||||||
import ffprobeInstaller from '@ffprobe-installer/ffprobe';
|
import ffprobeInstaller from "@ffprobe-installer/ffprobe";
|
||||||
import which from 'which';
|
import which from "which";
|
||||||
|
|
||||||
const getExecutablePath = (envVar: string, executable: string, installer: { path: string }) => {
|
const getExecutablePath = (
|
||||||
|
envVar: string,
|
||||||
|
executable: string,
|
||||||
|
installer: { path: string },
|
||||||
|
) => {
|
||||||
if (process.env[envVar]) {
|
if (process.env[envVar]) {
|
||||||
return process.env[envVar];
|
return process.env[envVar];
|
||||||
}
|
}
|
||||||
|
@ -15,8 +19,16 @@ const getExecutablePath = (envVar: string, executable: string, installer: { path
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ffmpegPath = getExecutablePath('EB_FFMPEG_PATH', 'ffmpeg', ffmpegInstaller);
|
const ffmpegPath = getExecutablePath(
|
||||||
const ffprobePath = getExecutablePath('EB_FFPROBE_PATH', 'ffprobe', ffprobeInstaller);
|
"EB_FFMPEG_PATH",
|
||||||
|
"ffmpeg",
|
||||||
|
ffmpegInstaller,
|
||||||
|
);
|
||||||
|
const ffprobePath = getExecutablePath(
|
||||||
|
"EB_FFPROBE_PATH",
|
||||||
|
"ffprobe",
|
||||||
|
ffprobeInstaller,
|
||||||
|
);
|
||||||
|
|
||||||
console.log(`Using ffmpeg from path: ${ffmpegPath}`);
|
console.log(`Using ffmpeg from path: ${ffmpegPath}`);
|
||||||
console.log(`Using ffprobe from path: ${ffprobePath}`);
|
console.log(`Using ffprobe from path: ${ffprobePath}`);
|
||||||
|
@ -25,25 +37,32 @@ ffmpeg.setFfmpegPath(ffmpegPath!);
|
||||||
ffmpeg.setFfprobePath(ffprobePath!);
|
ffmpeg.setFfprobePath(ffprobePath!);
|
||||||
|
|
||||||
export enum EncodingType {
|
export enum EncodingType {
|
||||||
CPU = 'libx264',
|
CPU = "libx264",
|
||||||
NVIDIA = 'h264_nvenc',
|
NVIDIA = "h264_nvenc",
|
||||||
AMD = 'h264_amf',
|
AMD = "h264_amf",
|
||||||
INTEL = 'h264_qsv',
|
INTEL = "h264_qsv",
|
||||||
APPLE = 'h264_videotoolbox',
|
APPLE = "h264_videotoolbox",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateTestVideo = async (encodingType: EncodingType): Promise<void> => {
|
export const generateTestVideo = async (
|
||||||
|
encodingType: EncodingType,
|
||||||
|
): Promise<void> => {
|
||||||
console.log(`Generating test video using ${encodingType}...`);
|
console.log(`Generating test video using ${encodingType}...`);
|
||||||
|
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
let totalFrames = 0;
|
let totalFrames = 0;
|
||||||
|
|
||||||
const outputOptions = [
|
const outputOptions = [
|
||||||
'-vf', 'scale=-2:720',
|
"-vf",
|
||||||
'-vcodec', encodingType,
|
"scale=-2:720",
|
||||||
'-c:a', 'copy',
|
"-vcodec",
|
||||||
'-b:v', '5000k',
|
encodingType,
|
||||||
'-pix_fmt', 'yuv420p',
|
"-c:a",
|
||||||
|
"copy",
|
||||||
|
"-b:v",
|
||||||
|
"5000k",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Adjust output options based on encoder for maximum quality
|
// Adjust output options based on encoder for maximum quality
|
||||||
|
@ -52,38 +71,42 @@ export const generateTestVideo = async (encodingType: EncodingType): Promise<voi
|
||||||
//outputOptions.push('-crf', '0');
|
//outputOptions.push('-crf', '0');
|
||||||
break;
|
break;
|
||||||
case EncodingType.NVIDIA:
|
case EncodingType.NVIDIA:
|
||||||
outputOptions.push('-rc', 'cqp', '-qp', '0');
|
outputOptions.push("-rc", "cqp", "-qp", "0");
|
||||||
break;
|
break;
|
||||||
case EncodingType.AMD:
|
case EncodingType.AMD:
|
||||||
outputOptions.push('-qp_i', '0', '-qp_p', '0', '-qp_b', '0');
|
outputOptions.push("-qp_i", "0", "-qp_p", "0", "-qp_b", "0");
|
||||||
break;
|
break;
|
||||||
case EncodingType.INTEL:
|
case EncodingType.INTEL:
|
||||||
outputOptions.push('-global_quality', '1'); // Intel QSV specific setting for high quality
|
outputOptions.push("-global_quality", "1"); // Intel QSV specific setting for high quality
|
||||||
break;
|
break;
|
||||||
case EncodingType.APPLE:
|
case EncodingType.APPLE:
|
||||||
outputOptions.push('-global_quality', '1');
|
outputOptions.push("-global_quality", "1");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
ffmpeg()
|
ffmpeg()
|
||||||
.input('unknown_replay_2023.10.29-22.57-00.00.38.103-00.01.00.016.mp4')
|
.input("unknown_replay_2023.10.29-22.57-00.00.38.103-00.01.00.016.mp4")
|
||||||
.inputFormat('mp4')
|
.inputFormat("mp4")
|
||||||
.outputOptions(outputOptions)
|
.outputOptions(outputOptions)
|
||||||
.output(`720p-test-${encodingType}.mp4`)
|
.output(`720p-test-${encodingType}.mp4`)
|
||||||
.on('progress', (progress) => {
|
.on("progress", (progress) => {
|
||||||
totalFrames = progress.frames;
|
totalFrames = progress.frames;
|
||||||
})
|
})
|
||||||
.on('end', () => {
|
.on("end", () => {
|
||||||
const elapsedTime = (Date.now() - startTime) / 1000; // Convert to seconds
|
const elapsedTime = (Date.now() - startTime) / 1000; // Convert to seconds
|
||||||
const avgFps = totalFrames / elapsedTime;
|
const avgFps = totalFrames / elapsedTime;
|
||||||
|
|
||||||
console.log(`720p copy complete using ${encodingType}, took ${Date.now() - startTime}ms to complete`);
|
console.log(
|
||||||
|
`720p copy complete using ${encodingType}, took ${
|
||||||
|
Date.now() - startTime
|
||||||
|
}ms to complete`,
|
||||||
|
);
|
||||||
console.log(`Average FPS for the entire process: ${avgFps.toFixed(2)}`);
|
console.log(`Average FPS for the entire process: ${avgFps.toFixed(2)}`);
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.on('error', (e) => reject(new Error(e)))
|
.on("error", (e) => reject(new Error(e)))
|
||||||
.run();
|
.run();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import readline from 'readline';
|
import readline from "readline";
|
||||||
import { generateTestVideo, EncodingType } from "./ffmpeg";
|
import { generateTestVideo, EncodingType } from "./ffmpeg";
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout,
|
||||||
});
|
});
|
||||||
|
|
||||||
const questionAsync = (query: string) => {
|
const questionAsync = (query: string) => {
|
||||||
return new Promise<string>(resolve => {
|
return new Promise<string>((resolve) => {
|
||||||
rl.question(query, resolve);
|
rl.question(query, resolve);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -16,27 +16,31 @@ const main = async () => {
|
||||||
console.log("Testing software encoder: ");
|
console.log("Testing software encoder: ");
|
||||||
await generateTestVideo(EncodingType.CPU).catch(console.error);
|
await generateTestVideo(EncodingType.CPU).catch(console.error);
|
||||||
|
|
||||||
const answer = await questionAsync('Would you like to test other hardware encoders? (yes/no): ');
|
const answer = await questionAsync(
|
||||||
|
"Would you like to test other hardware encoders? (yes/no): ",
|
||||||
|
);
|
||||||
|
|
||||||
if (answer.toLowerCase() === 'yes') {
|
if (answer.toLowerCase() === "yes") {
|
||||||
const encoder = await questionAsync('Which hardware encoder would you like to test? (INTEL/NVIDIA/AMD/APPLE): ');
|
const encoder = await questionAsync(
|
||||||
|
"Which hardware encoder would you like to test? (INTEL/NVIDIA/AMD/APPLE): ",
|
||||||
|
);
|
||||||
let selectedEncoder: EncodingType;
|
let selectedEncoder: EncodingType;
|
||||||
|
|
||||||
switch (encoder.toUpperCase()) {
|
switch (encoder.toUpperCase()) {
|
||||||
case 'INTEL':
|
case "INTEL":
|
||||||
selectedEncoder = EncodingType.INTEL;
|
selectedEncoder = EncodingType.INTEL;
|
||||||
break;
|
break;
|
||||||
case 'NVIDIA':
|
case "NVIDIA":
|
||||||
selectedEncoder = EncodingType.NVIDIA;
|
selectedEncoder = EncodingType.NVIDIA;
|
||||||
break;
|
break;
|
||||||
case 'AMD':
|
case "AMD":
|
||||||
selectedEncoder = EncodingType.AMD;
|
selectedEncoder = EncodingType.AMD;
|
||||||
break;
|
break;
|
||||||
case 'APPLE':
|
case "APPLE":
|
||||||
selectedEncoder = EncodingType.APPLE;
|
selectedEncoder = EncodingType.APPLE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('Invalid choice. Exiting.');
|
console.log("Invalid choice. Exiting.");
|
||||||
rl.close();
|
rl.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +54,7 @@ const main = async () => {
|
||||||
rl.close();
|
rl.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
main().catch(err => {
|
main().catch((err) => {
|
||||||
console.error("An error occurred:", err);
|
console.error("An error occurred:", err);
|
||||||
rl.close();
|
rl.close();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue