build video support
This commit is contained in:
@@ -3,12 +3,16 @@ import type { MultipartFile } from '@fastify/multipart';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import db, { UNSORTED_ID } from '../db.js';
|
||||
import { buildFilePath, deleteFile, getExtension } from '../services/storage.js';
|
||||
import { extractMeta, resizeImage, saveBuffer } from '../services/image.js';
|
||||
import { extractMeta, extractVideoMeta, resizeImage, saveBuffer } from '../services/image.js';
|
||||
import { extractText } from '../services/ocr.js';
|
||||
import { requireAuth } from '../auth.js';
|
||||
import type { ListQuery, UpdateBody, RescaleBody, MoveBody, Meme } from '../types.js';
|
||||
|
||||
const ALLOWED_MIMES = new Set(['image/jpeg', 'image/png', 'image/gif', 'image/webp']);
|
||||
const ALLOWED_MIMES = new Set([
|
||||
'image/jpeg', 'image/png', 'image/gif', 'image/webp',
|
||||
'video/mp4', 'video/webm', 'video/quicktime',
|
||||
]);
|
||||
const VIDEO_MIMES = new Set(['video/mp4', 'video/webm', 'video/quicktime']);
|
||||
|
||||
function getMemeTags(memeId: string): string[] {
|
||||
return (
|
||||
@@ -143,7 +147,10 @@ export async function memesRoutes(app: FastifyInstance) {
|
||||
const filePath = buildFilePath(id, ext);
|
||||
|
||||
await saveBuffer(buffer, filePath);
|
||||
const meta = await extractMeta(filePath);
|
||||
const isVideo = VIDEO_MIMES.has(mimeType);
|
||||
const meta = isVideo
|
||||
? await extractVideoMeta(filePath, mimeType)
|
||||
: await extractMeta(filePath);
|
||||
|
||||
const fields = file.fields as Record<string, { value: string }>;
|
||||
const title = fields.title?.value ?? file.filename ?? 'Untitled';
|
||||
@@ -160,10 +167,12 @@ export async function memesRoutes(app: FastifyInstance) {
|
||||
|
||||
if (tagsRaw) setMemeTags(id, tagsRaw.split(','));
|
||||
|
||||
// Fire OCR in the background — doesn't block the upload response
|
||||
extractText(filePath, mimeType).then((text) => {
|
||||
if (text) db.prepare('UPDATE memes SET ocr_text = ? WHERE id = ?').run(text, id);
|
||||
});
|
||||
// OCR only makes sense for images
|
||||
if (!isVideo) {
|
||||
extractText(filePath, mimeType).then((text) => {
|
||||
if (text) db.prepare('UPDATE memes SET ocr_text = ? WHERE id = ?').run(text, id);
|
||||
});
|
||||
}
|
||||
|
||||
return reply.status(201).send(getMemeById(id));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import sharp from 'sharp';
|
||||
import fs from 'fs';
|
||||
import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { absolutePath, ensureDir } from './storage.js';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export interface ImageMeta {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -29,6 +33,30 @@ export async function extractMeta(filePath: string): Promise<ImageMeta> {
|
||||
};
|
||||
}
|
||||
|
||||
export async function extractVideoMeta(filePath: string, mimeType: string): Promise<ImageMeta> {
|
||||
const abs = absolutePath(filePath);
|
||||
const stat = fs.statSync(abs);
|
||||
try {
|
||||
const { stdout } = await execFileAsync('ffprobe', [
|
||||
'-v', 'quiet',
|
||||
'-print_format', 'json',
|
||||
'-show_streams',
|
||||
abs,
|
||||
]);
|
||||
const data = JSON.parse(stdout);
|
||||
const video = (data.streams as any[])?.find((s) => s.codec_type === 'video');
|
||||
return {
|
||||
width: video?.width ?? 1280,
|
||||
height: video?.height ?? 720,
|
||||
mimeType,
|
||||
size: stat.size,
|
||||
};
|
||||
} catch {
|
||||
// ffprobe unavailable or failed — use safe defaults
|
||||
return { width: 1280, height: 720, mimeType, size: stat.size };
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveBuffer(buffer: Buffer, destRelPath: string): Promise<void> {
|
||||
ensureDir(destRelPath);
|
||||
const abs = absolutePath(destRelPath);
|
||||
|
||||
@@ -44,6 +44,9 @@ export function getExtension(mimeType: string): string {
|
||||
'image/png': 'png',
|
||||
'image/gif': 'gif',
|
||||
'image/webp': 'webp',
|
||||
'video/mp4': 'mp4',
|
||||
'video/webm': 'webm',
|
||||
'video/quicktime': 'mov',
|
||||
};
|
||||
return map[mimeType] ?? 'jpg';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user