import { IMediaRepository, ResizeOptions, VideoInfo } from '@app/domain'; import { exiftool } from 'exiftool-vendored'; import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; import sharp from 'sharp'; import { promisify } from 'util'; const probe = promisify(ffmpeg.ffprobe); export class MediaRepository implements IMediaRepository { extractThumbnailFromExif(input: string, output: string): Promise { return exiftool.extractThumbnail(input, output); } async resize(input: string, output: string, options: ResizeOptions): Promise { switch (options.format) { case 'webp': await sharp(input, { failOnError: false }) .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) .webp() .rotate() .toFile(output); return; case 'jpeg': await sharp(input, { failOnError: false }) .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true }) .jpeg() .rotate() .toFile(output); return; } } extractVideoThumbnail(input: string, output: string, size: number) { return new Promise((resolve, reject) => { ffmpeg(input) .outputOptions([ '-ss 00:00:00.000', '-frames:v 1', `-vf scale='min(${size},iw)':'min(${size},ih)':force_original_aspect_ratio=increase`, ]) .output(output) .on('error', reject) .on('end', resolve) .run(); }); } async probe(input: string): Promise { const results = await probe(input); return { format: { formatName: results.format.format_name, formatLongName: results.format.format_long_name, duration: results.format.duration || 0, }, videoStreams: results.streams .filter((stream) => stream.codec_type === 'video') .map((stream) => ({ height: stream.height || 0, width: stream.width || 0, codecName: stream.codec_name, codecType: stream.codec_type, frameCount: Number.parseInt(stream.nb_frames ?? '0'), rotation: Number.parseInt(`${stream.rotation ?? 0}`), })), audioStreams: results.streams .filter((stream) => stream.codec_type === 'audio') .map((stream) => ({ codecType: stream.codec_type, codecName: stream.codec_name, })), }; } transcode(input: string, output: string, options: string[]): Promise { return new Promise((resolve, reject) => { ffmpeg(input, { niceness: 10 }) // .outputOptions(options) .output(output) .on('error', reject) .on('end', resolve) .run(); }); } }