import { tmpdir } from 'node:os' import { writeFile, rm } from 'node:fs/promises' import { join } from 'node:path' import ffmpeg from 'fluent-ffmpeg' export type MediaMetadata = { durationInSec: number durationInMs: number hasAudio: boolean } /** * Get the media info of a base64 or file path * @param input * @returns */ export async function getMediaInfo(input: string): Promise { // If the input is a base64 string if (input.startsWith('data:')) { // Extract the base64 content // Extract the base64 content const [head, tail] = input.split(';base64,') if (!tail) { throw new Error('Invalid base64 data') } const extension = head.split('/').pop() || '' const base64Content = tail || '' // Decode the base64 content to a buffer const buffer = Buffer.from(base64Content, 'base64') // Generate a temporary file name const tempFileName = join(tmpdir(), `temp-media-${Date.now()}.${extension}`) // Write the buffer to a temporary file await writeFile(tempFileName, buffer) // Get metadata from the temporary file then delete the file try { return await getMetaDataFromPath(tempFileName) } finally { await rm(tempFileName) } } // If the input is a path to the file return await getMetaDataFromPath(input) } async function getMetaDataFromPath(filePath: string): Promise { return new Promise((resolve, reject) => { ffmpeg.ffprobe(filePath, (err, metadata) => { let results = { durationInSec: 0, durationInMs: 0, hasAudio: false, } if (err) { console.error( 'getMediaInfo(): failed to analyze the source (might happen with empty files)', err ) // reject(err); resolve(results) return } try { results.durationInSec = metadata?.format?.duration || 0 results.durationInMs = results.durationInSec * 1000 results.hasAudio = (metadata?.streams || []).some( (stream) => stream.codec_type === 'audio' ) } catch (err) { console.error( `getMediaInfo(): failed to analyze the source (might happen with empty files)`, err ) results.durationInSec = 0 results.durationInMs = 0 results.hasAudio = false } resolve(results) }) }) }