added backend to scan for and serve files

This commit is contained in:
Anton
2024-02-13 21:54:54 +03:00
parent 379496b333
commit 30da3896f1
8 changed files with 1656 additions and 0 deletions
+1184
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start:static": "ts-node src/index",
"start": "nodemon --watch 'src/**/*.ts' --ext ts --exec npm run start:static",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"fastify": "^4.26.1",
"ffprobe": "^1.1.2",
"mathjs": "^12.3.2"
},
"devDependencies": {
"@types/ffprobe": "^1.1.8",
"@types/mathjs": "^9.4.2"
}
}
+6
View File
@@ -0,0 +1,6 @@
export const config = {
directories: {
video: '/home/anton/Documents/TG/Панды/unsorted input',
audio: '/home/anton/Documents/TG/Панды/music',
}
}
+46
View File
@@ -0,0 +1,46 @@
import Fastify, { FastifyRequest } from 'fastify';
import { Video } from './model/video';
(async () => {
const fastify = Fastify({
logger: true
});
fastify.get('/video/all', async function handler(request, reply) {
return Video.list();
});
fastify.get('/video/:hash([0-9a-fA-F]+)', async function handler(request: FastifyRequest<{ Params: { hash: string; }; }>, reply) {
const hash = request.params.hash;
const file = await Video.getByHash(hash);
if (!file)
reply.callNotFound();
else
return file;
});
fastify.get('/video/:hash([0-9a-fA-F]+)/content', async function handler(request: FastifyRequest<{ Params: { hash: string; }; }>, reply) {
const hash = request.params.hash;
const file = await Video.getByHash(hash);
if (!file)
reply.callNotFound();
else {
reply.header('Content-Disposition', 'inline; filename=' + file.name);
reply.header('Content-Length', file.size);
reply.type(file.mimeType);
reply.send(await Video.getContentsByPath(file.path));
return reply;
}
});
try {
await fastify.listen({ port: 5174 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
})()
.catch((err) => console.error);
+96
View File
@@ -0,0 +1,96 @@
import * as fs from 'fs/promises';
import { readFileSync, createReadStream } from 'fs';
import { Readable } from 'stream';
import * as math from 'mathjs';
import { basename, join } from 'path';
import ffprobe, { FFProbeResult } from 'ffprobe';
import { config } from '../config';
import { hashString } from '../utils';
export type VideoFileMeta = {
codec: string;
frameRate: number;
width: number;
height: number;
};
export type VideoFile = {
hash: string;
path: string;
name: string;
size: number;
mimeType: string;
meta: VideoFileMeta;
rawMeta?: FFProbeResult;
};
export class Video {
public static async list(): Promise<VideoFile[]> {
const dir = config.directories.video;
const files = await fs.readdir(dir, { recursive: true, withFileTypes: true });
return (
await Promise.all(
files
.filter((f) => f.isFile())
.map((f) => Video.getFileDescription(join(dir, f.name)))
)
)
.filter((vf) => !!vf) as VideoFile[];
}
public static async getFileDescription(path: string): Promise<VideoFile | undefined> {
try {
const meta = await ffprobe(path, { path: '/usr/bin/ffprobe' });
const videoStreams = meta.streams.filter((st) => st.codec_type === 'video');
if (videoStreams.length > 1)
throw new Error('Files with multiple video streams are not supported');
const videoStream = videoStreams[0];
const name = basename(path, '');
return {
hash: hashString(name),
path,
name,
size: (await fs.stat(path)).size,
mimeType: 'video/mp4', // TODO
// rawMeta: meta,
meta: {
codec: videoStream.codec_name ?? '',
frameRate: math.evaluate(videoStream.r_frame_rate),
width: videoStream.width ?? 0,
height: videoStream.height ?? 0,
}
};
}
catch (err) {
console.error(`Error reading video file "${path}" meta: ${String(err)}`);
return undefined;
}
}
public static async getByHash(hash: string): Promise<VideoFile | undefined> {
const files = await Video.list();
const file = files.find((f) => f.hash == hash);
return file;
}
public static async getContentsByPath(path: string): Promise<Readable> {
const stream = createReadStream(path);
// const buffer = readFileSync(path);
// const stream = new Readable({
// read() {
// this.push(buffer);
// this.push(null);
// }
// });
return stream;
}
}
+5
View File
@@ -0,0 +1,5 @@
import crypto from 'crypto';
export function hashString(str: string): string {
return crypto.createHash('md5').update(str).digest('hex');
}