@twick/ffmpeg 0.14.21 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/ffmpeg-exporter-server.ts","../src/image-stream.ts","../src/settings.ts","../src/generate-audio.ts","../src/utils.ts","../src/video-frame-extractor.ts"],"sourcesContent":["export * from './ffmpeg-exporter-server';\nexport * from './generate-audio';\nexport * from './settings';\nexport * from './utils';\nexport * from './video-frame-extractor';\n","import type {\n FfmpegExporterOptions,\n RendererResult,\n RendererSettings,\n} from '@twick/core';\nimport {EventName, sendEvent} from '@twick/telemetry';\nimport ffmpeg from 'fluent-ffmpeg';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {ImageStream} from './image-stream';\nimport {ffmpegSettings} from './settings';\n\nexport interface FFmpegExporterSettings extends RendererSettings {\n fastStart: boolean;\n includeAudio: boolean;\n output: string;\n}\n\nconst pixelFormats: Record<FfmpegExporterOptions['format'], string> = {\n mp4: 'yuv420p',\n webm: 'yuva420p',\n proRes: 'yuva444p10le',\n};\n\nexport const extensions: Record<FfmpegExporterOptions['format'], string> = {\n mp4: 'mp4',\n webm: 'webm',\n proRes: 'mov',\n};\n\n/**\n * The server-side implementation of the FFmpeg video exporter.\n */\nexport class FFmpegExporterServer {\n private readonly stream: ImageStream;\n private readonly command: ffmpeg.FfmpegCommand;\n private readonly promise: Promise<void>;\n private readonly settings: FFmpegExporterSettings;\n private readonly jobFolder: string;\n private readonly format: FfmpegExporterOptions['format'];\n\n public constructor(settings: FFmpegExporterSettings) {\n if (settings.exporter.name !== '@twick/core/ffmpeg') {\n throw new Error('Invalid exporter');\n }\n\n this.settings = settings;\n this.format = settings.exporter.options.format;\n\n this.jobFolder = path.join(\n os.tmpdir(),\n `twick-${this.settings.name}-${settings.hiddenFolderId}`,\n );\n this.stream = new ImageStream();\n\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n this.command = ffmpeg();\n\n // Input image sequence\n this.command\n .input(this.stream)\n .inputFormat('image2pipe')\n .inputFps(settings.fps);\n\n // Output settings\n const size = {\n x: Math.round(settings.size.x * settings.resolutionScale),\n y: Math.round(settings.size.y * settings.resolutionScale),\n };\n this.command\n .output(path.join(this.jobFolder, `visuals.${extensions[this.format]}`))\n .outputOptions([`-pix_fmt ${pixelFormats[this.format]}`, '-shortest'])\n .outputFps(settings.fps)\n .size(`${size.x}x${size.y}`);\n\n if (this.format === 'proRes') {\n this.command.outputOptions(['-c:v prores_ks', '-profile:v 4444']);\n }\n\n this.command.outputOptions(['-movflags +faststart']);\n this.promise = new Promise<void>((resolve, reject) => {\n this.command.on('end', resolve).on('error', reject);\n });\n }\n\n public async start() {\n this.command.run();\n }\n\n public async handleFrame({data}: {data: string}) {\n const base64Data = data.slice(data.indexOf(',') + 1);\n this.stream.pushImage(Buffer.from(base64Data, 'base64'));\n }\n\n public async end(result: RendererResult) {\n this.stream.pushImage(null);\n if (result === 1) {\n try {\n this.command.kill('SIGKILL');\n await this.promise;\n } catch (err) {\n sendEvent(EventName.Error, {message: (err as Error).message});\n }\n } else {\n await this.promise;\n }\n }\n\n public async kill() {\n try {\n this.command.kill('SIGKILL');\n await this.promise;\n } catch (_) {\n return;\n }\n }\n}\n","import {Readable} from 'stream';\n\nexport class ImageStream extends Readable {\n private image: Buffer | null = null;\n private hasData = false;\n\n public pushImage(image: Buffer | null) {\n this.image = image;\n this.hasData = true;\n this._read();\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n public override _read() {\n if (this.hasData) {\n this.hasData = false;\n this.push(this.image);\n }\n }\n}\n","import * as ffmpegInstaller from '@ffmpeg-installer/ffmpeg';\nimport * as ffprobeInstaller from '@ffprobe-installer/ffprobe';\n\nconst ffmpegLogLevels = [\n 'quiet',\n 'panic',\n 'fatal',\n 'error',\n 'warning',\n 'info',\n 'verbose',\n 'debug',\n 'trace',\n] as const;\n\nexport type LogLevel = (typeof ffmpegLogLevels)[number];\n\nexport type FfmpegSettings = {\n ffmpegPath?: string;\n ffprobePath?: string;\n ffmpegLogLevel?: LogLevel;\n};\n\nclass FfmpegSettingState {\n private ffmpegPath: string;\n private ffprobePath: string;\n private logLevel: LogLevel;\n\n public constructor() {\n this.ffmpegPath = ffmpegInstaller.path as unknown as string;\n this.ffprobePath = ffprobeInstaller.path as unknown as string;\n\n // Use the FFMPEG_PATH environment variable if it is set\n if (process.env.FFMPEG_PATH) {\n this.ffmpegPath = process.env.FFMPEG_PATH;\n }\n\n // Use the FFPROBE_PATH environment variable if it is set\n if (process.env.FFPROBE_PATH) {\n this.ffprobePath = process.env.FFPROBE_PATH;\n }\n\n this.logLevel = 'error';\n\n // Use the FFMPEG_LOG_LEVEL environment variable if it is set\n if (\n process.env.FFMPEG_LOG_LEVEL &&\n ffmpegLogLevels.includes(process.env.FFMPEG_LOG_LEVEL as LogLevel)\n ) {\n this.logLevel = process.env.FFMPEG_LOG_LEVEL as LogLevel;\n }\n }\n\n public getFfmpegPath() {\n return this.ffmpegPath;\n }\n\n public setFfmpegPath(ffmpegPath: string) {\n this.ffmpegPath = ffmpegPath;\n }\n\n public getFfprobePath() {\n return this.ffprobePath;\n }\n\n public setFfprobePath(ffprobePath: string) {\n this.ffprobePath = ffprobePath;\n }\n\n public getLogLevel() {\n return this.logLevel;\n }\n\n public setLogLevel(logLevel: LogLevel) {\n this.logLevel = logLevel;\n }\n}\n\nexport const ffmpegSettings = new FfmpegSettingState();\n","import type {AssetInfo, FfmpegExporterOptions} from '@twick/core';\nimport ffmpeg from 'fluent-ffmpeg';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {extensions} from './ffmpeg-exporter-server';\nimport {ffmpegSettings} from './settings';\nimport type {AudioCodec} from './utils';\nimport {\n checkForAudioStream,\n getSampleRate,\n makeSureFolderExists,\n mergeAudioWithVideo,\n resolvePath,\n} from './utils';\n\nexport const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec> =\n {\n mp4: 'aac',\n webm: 'libopus',\n proRes: 'aac',\n };\n\ninterface MediaAsset {\n key: string;\n src: string;\n type: 'video' | 'audio';\n startInVideo: number;\n endInVideo: number;\n duration: number;\n playbackRate: number;\n volume: number;\n trimLeftInSeconds: number;\n durationInSeconds: number;\n}\n\nconst SAMPLE_RATE = 48000;\n\nfunction getAssetPlacement(frames: AssetInfo[][]): MediaAsset[] {\n const assets: MediaAsset[] = [];\n\n // A map to keep track of the first and last currentTime for each asset.\n const assetTimeMap = new Map<string, {start: number; end: number}>();\n\n for (let frame = 0; frame < frames.length; frame++) {\n for (const asset of frames[frame]) {\n if (!assetTimeMap.has(asset.key)) {\n // If the asset is not in the map, add it with its current time as both start and end.\n assetTimeMap.set(asset.key, {\n start: asset.currentTime,\n end: asset.currentTime,\n });\n assets.push({\n key: asset.key,\n src: asset.src,\n type: asset.type,\n startInVideo: frame,\n endInVideo: frame,\n duration: 0, // Placeholder, will be recalculated later based on frames\n durationInSeconds: 0, // Placeholder, will be calculated based on currentTime\n playbackRate: asset.playbackRate,\n volume: asset.volume,\n trimLeftInSeconds: asset.currentTime,\n });\n } else {\n // If the asset is already in the map, update the end time.\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n timeInfo.end = asset.currentTime;\n assetTimeMap.set(asset.key, timeInfo);\n }\n\n const existingAsset = assets.find(a => a.key === asset.key);\n if (existingAsset) {\n existingAsset.endInVideo = frame;\n }\n }\n }\n }\n\n // Calculate the duration based on frame count and durationInSeconds based on currentTime.\n assets.forEach(asset => {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n // Calculate durationInSeconds based on the start and end currentTime values.\n asset.durationInSeconds =\n (timeInfo.end - timeInfo.start) / asset.playbackRate;\n }\n // Recalculate the original duration based on frame count.\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n });\n\n return assets;\n}\n\nfunction calculateAtempoFilters(playbackRate: number) {\n const atempoFilters = [];\n\n // Calculate how many times we need to 100x the speed\n let rate = playbackRate;\n while (rate > 100.0) {\n atempoFilters.push('atempo=100.0');\n rate /= 100.0;\n }\n // Add the last atempo filter with the remaining rate\n if (rate > 1.0) {\n atempoFilters.push(`atempo=${rate}`);\n }\n\n // Calculate how many times we need to halve the speed\n rate = playbackRate;\n while (rate < 0.5) {\n atempoFilters.push('atempo=0.5');\n rate *= 2.0;\n }\n // Add the last atempo filter with the remaining rate\n if (rate < 1.0) {\n atempoFilters.push(`atempo=${rate}`);\n }\n\n return atempoFilters;\n}\nasync function prepareAudio(\n outputDir: string,\n tempDir: string,\n asset: MediaAsset,\n startFrame: number,\n endFrame: number,\n fps: number,\n): Promise<string> {\n // Construct the output path\n const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + asset.durationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n const assetSampleRate = await getSampleRate(\n resolvePath(outputDir, asset.src),\n );\n\n const padEnd = Math.max(\n 0,\n (assetSampleRate * (endFrame - startFrame + 1)) / fps -\n (assetSampleRate * asset.duration) / fps -\n (assetSampleRate * padStart) / 1000,\n );\n\n const atempoFilters = calculateAtempoFilters(asset.playbackRate); // atempo filter value must be >=0.5 and <=100. If the value is higher or lower, this function sets multiple atempo filters\n const resolvedPath = resolvePath(outputDir, asset.src);\n\n await new Promise<void>((resolve, reject) => {\n const audioFilters = [\n ...atempoFilters,\n `atrim=start=${trimLeft}:end=${trimRight}`,\n `apad=pad_len=${padEnd}`,\n `adelay=${padStart}|${padStart}|${padStart}`,\n `volume=${asset.volume}`,\n ].join(',');\n\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n ffmpeg(resolvedPath)\n .audioChannels(2)\n .audioCodec('pcm_s16le')\n .audioFrequency(SAMPLE_RATE)\n .outputOptions([`-af`, audioFilters])\n .on('end', () => {\n resolve();\n })\n .on('error', err => {\n console.error(\n `Error processing audio for asset key: ${asset.key}`,\n err,\n );\n reject(err);\n })\n .save(outputPath);\n });\n\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n return new Promise((resolve, reject) => {\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const command = ffmpeg();\n\n audioFilenames.forEach(filename => {\n command.input(filename);\n });\n\n command\n .complexFilter([\n `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`,\n ])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => {\n resolve();\n })\n .on('error', err => {\n console.error(`Error merging audio tracks: ${err.message}`);\n reject(err);\n })\n .save(path.join(tempDir, `audio.wav`));\n });\n}\n\nexport async function generateAudio({\n outputDir,\n tempDir,\n assets,\n startFrame,\n endFrame,\n fps,\n}: {\n outputDir: string;\n tempDir: string;\n assets: AssetInfo[][];\n startFrame: number;\n endFrame: number;\n fps: number;\n}) {\n const fullTempDir = path.join(os.tmpdir(), tempDir);\n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const assetPositions = getAssetPlacement(assets);\n const audioFilenames: string[] = [];\n\n for (const asset of assetPositions) {\n let hasAudioStream = true;\n if (asset.type !== 'audio') {\n hasAudioStream = await checkForAudioStream(\n resolvePath(outputDir, asset.src),\n );\n }\n\n if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {\n const filename = await prepareAudio(\n outputDir,\n fullTempDir,\n asset,\n startFrame,\n endFrame,\n fps,\n );\n audioFilenames.push(filename);\n }\n }\n\n if (audioFilenames.length > 0) {\n await mergeAudioTracks(fullTempDir, audioFilenames);\n }\n\n return audioFilenames;\n}\n\nexport async function mergeMedia(\n outputFilename: string,\n outputDir: string,\n tempDir: string,\n format: FfmpegExporterOptions['format'],\n) {\n const fullTempDir = path.join(os.tmpdir(), tempDir);\n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const audioWavExists = fs.existsSync(path.join(fullTempDir, `audio.wav`));\n if (audioWavExists) {\n await mergeAudioWithVideo(\n path.join(fullTempDir, `audio.wav`),\n path.join(fullTempDir, `visuals.${extensions[format]}`),\n path.join(outputDir, `${outputFilename}.${extensions[format]}`),\n audioCodecs[format],\n );\n } else {\n const destination = path.join(\n outputDir,\n `${outputFilename}.${extensions[format]}`,\n );\n await fs.promises.copyFile(\n path.join(fullTempDir, `visuals.${extensions[format]}`),\n destination,\n );\n }\n if (fullTempDir.endsWith('-undefined')) {\n await fs.promises\n .rm(fullTempDir, {recursive: true, force: true})\n .catch(() => {});\n }\n}\n","import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {v4 as uuidv4} from 'uuid';\nimport {ffmpegSettings} from './settings';\n\n// Import fluent-ffmpeg - handle both ESM and CJS\n// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires\nconst fluentFfmpeg = require('fluent-ffmpeg');\n\nexport type AudioCodec = 'aac' | 'libopus';\n\nexport function resolvePath(output: string, assetPath: string) {\n let resolvedPath: string;\n if (\n assetPath.startsWith('http://') ||\n assetPath.startsWith('https://') ||\n assetPath.startsWith('data:')\n ) {\n resolvedPath = assetPath;\n } else {\n resolvedPath = path.join(output, '../public', assetPath);\n }\n return resolvedPath;\n}\n\nexport async function makeSureFolderExists(folderPath: string) {\n if (\n await fs.promises\n .access(folderPath)\n .then(() => false)\n .catch(() => true)\n ) {\n await fs.promises.mkdir(folderPath, {recursive: true});\n }\n}\n\nexport async function concatenateMedia(\n files: string[],\n outputFile: string,\n): Promise<void> {\n const tempFile = path.join(os.tmpdir(), `${uuidv4()}.txt`);\n const fileContent = files\n .map(file => `file '${file.replace(/'/g, \"\\\\'\")}'`)\n .join('\\n');\n await fs.promises.writeFile(tempFile, fileContent);\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const ffmpegCommand = fluentFfmpeg();\n\n ffmpegCommand\n .input(tempFile)\n .inputOptions([\n '-f concat',\n '-safe 0',\n '-protocol_whitelist file,http,https,tcp,tls',\n ])\n .outputOptions(['-c copy'])\n .on('error', (err: Error) => {\n console.error('Error:', err);\n fs.promises.unlink(tempFile).catch(console.error);\n reject(err); // Reject the promise on error\n })\n .on('end', () => {\n fs.promises.unlink(tempFile).catch(console.error);\n resolve(); // Resolve the promise on successful completion\n })\n .save(outputFile);\n });\n}\n\nexport async function createSilentAudioFile(\n filePath: string,\n duration: number,\n) {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg()\n .addInput(`anullsrc=channel_layout=stereo:sample_rate=${48000}`)\n .inputFormat('lavfi')\n .duration(duration)\n .on('end', () => {\n resolve(filePath);\n })\n .on('error', (err: Error) => {\n console.error('Error creating silent audio file:', err);\n reject(err);\n })\n .save(filePath);\n });\n}\n\nexport async function getVideoDuration(filePath: string): Promise<number> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const duration = metadata.format.duration;\n if (duration) {\n resolve(duration);\n } else {\n reject(new Error('Could not determine video duration.'));\n }\n });\n });\n}\n\nexport async function getVideoDimensions(\n filePath: string,\n): Promise<{width: number; height: number}> {\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n console.error('Error getting video dimensions:', err);\n reject(new Error('Failed to get video dimensions'));\n return;\n }\n\n const videoStream = metadata.streams.find(\n (stream: any) => stream.codec_type === 'video',\n );\n if (videoStream && videoStream.width && videoStream.height) {\n resolve({\n width: videoStream.width,\n height: videoStream.height,\n });\n }\n reject(new Error('Could not find video dimensions in metadata'));\n });\n });\n}\n\nexport async function doesFileExist(filePath: string): Promise<boolean> {\n try {\n await fs.promises.access(filePath, fs.constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function mergeAudioWithVideo(\n audioPath: string,\n videoPath: string,\n outputPath: string,\n audioCodec: AudioCodec = 'aac',\n): Promise<void> {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg()\n .input(videoPath)\n .input(audioPath)\n .outputOptions([\n '-c:v',\n 'copy',\n '-c:a',\n audioCodec,\n '-strict',\n 'experimental',\n ])\n .on('end', () => {\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`Error merging video and audio: ${err.message}`);\n reject(err);\n })\n .save(outputPath);\n });\n}\n\nexport async function checkForAudioStream(file: string): Promise<boolean> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(file, (err: Error | null, metadata: any) => {\n if (err) {\n console.error(`error checking for audioStream for file ${file}`, err);\n reject(err);\n return;\n }\n\n const audioStreams = metadata.streams.filter(\n (s: any) => s.codec_type === 'audio',\n );\n resolve(audioStreams.length > 0);\n });\n });\n}\n\nexport async function getSampleRate(filePath: string): Promise<number> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const audioStream = metadata.streams.find((s: any) => s.codec_type === 'audio');\n if (audioStream && audioStream.sample_rate) {\n resolve(audioStream.sample_rate);\n } else {\n reject(new Error('No audio stream found'));\n }\n });\n });\n}\n\nexport async function getVideoCodec(filePath: string) {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise<string>((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const videoStream = metadata.streams.find((s: any) => s.codec_type === 'video');\n if (videoStream && videoStream.codec_name) {\n resolve(videoStream.codec_name);\n } else {\n reject(new Error('No video stream found'));\n }\n });\n });\n}\n\nexport async function getVideoMetadata(\n filePath: string,\n): Promise<{codec: string; width: number; height: number}> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const videoStream = metadata.streams.find((s: any) => s.codec_type === 'video');\n if (\n videoStream &&\n videoStream.codec_name &&\n videoStream.width &&\n videoStream.height\n ) {\n resolve({\n codec: videoStream.codec_name,\n width: videoStream.width,\n height: videoStream.height,\n });\n } else {\n reject(new Error('Unable to retrieve complete video information'));\n }\n });\n });\n}\n","import {EventName, sendEvent} from '@twick/telemetry';\n// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires\nconst ffmpeg = require('fluent-ffmpeg');\nimport type * as FfmpegTypes from 'fluent-ffmpeg';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {v4 as uuidv4} from 'uuid';\nimport {ffmpegSettings} from './settings';\nimport {getVideoMetadata} from './utils';\n\ntype VideoFrameExtractorState = 'processing' | 'done' | 'error';\n\n/**\n * Walks through a video file and extracts frames.\n */\nexport class VideoFrameExtractor {\n private static readonly chunkLengthInSeconds = 5;\n\n private readonly ffmpegPath = ffmpegSettings.getFfmpegPath();\n\n public state: VideoFrameExtractorState;\n public filePath: string;\n private downloadedFilePath: string;\n\n private buffer: Buffer = Buffer.alloc(0);\n private bufferOffset: number = 0;\n\n // Images are buffered in memory until they are requested.\n private imageBuffers: Buffer[] = [];\n private lastImage: Buffer | null = null;\n\n private startTime: number;\n private startTimeOffset: number;\n private duration: number;\n private toTime: number;\n private fps: number;\n private framesProcessed: number = 0;\n\n private width: number = 0;\n private height: number = 0;\n private frameSize: number = 0;\n private codec: string | null = null;\n private process: FfmpegTypes.FfmpegCommand | null = null;\n private terminated: boolean = false;\n\n public static downloadedVideoMap: Map<\n string,\n {localPath: string; startTimeOffset: number}\n > = new Map();\n\n public constructor(\n filePath: string,\n startTime: number,\n fps: number,\n duration: number,\n ) {\n this.state = 'processing';\n this.filePath = filePath;\n this.downloadedFilePath = VideoFrameExtractor.downloadedVideoMap.get(\n filePath,\n )?.localPath as string;\n this.startTimeOffset = VideoFrameExtractor.downloadedVideoMap.get(filePath)\n ?.startTimeOffset as number;\n\n this.startTime = startTime;\n this.duration = duration;\n this.toTime = this.getEndTime(this.startTime);\n this.fps = fps;\n\n getVideoMetadata(this.downloadedFilePath).then(metadata => {\n this.width = metadata.width;\n this.height = metadata.height;\n this.frameSize = this.width * this.height * 4;\n this.buffer = Buffer.alloc(this.frameSize);\n this.codec = metadata.codec;\n\n if (this.startTime >= this.duration) {\n this.process = this.createFfmpegProcessToExtractFirstFrame(\n this.downloadedFilePath,\n this.codec,\n );\n return;\n }\n\n this.process = this.createFfmpegProcess(\n this.startTime - this.startTimeOffset,\n this.toTime,\n this.downloadedFilePath,\n this.fps,\n this.codec,\n );\n });\n }\n\n public static downloadVideoChunk(\n url: string,\n startTime: number,\n endTime: number,\n ) {\n const outputDir = path.join(os.tmpdir(), `twick-decoder-chunks`);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {recursive: true});\n }\n\n return new Promise((resolve, reject) => {\n ffmpeg.ffprobe(url, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n\n const format = metadata.format.format_name?.split(',')[-1] || 'mp4';\n const outputFileName = `chunk_${uuidv4()}.${format}`;\n const outputPath = path.join(outputDir, outputFileName);\n const toleranceInSeconds = 0.5;\n\n const adjustedStartTime = Math.max(startTime - toleranceInSeconds, 0);\n\n ffmpeg(url)\n .setFfmpegPath(ffmpegSettings.getFfmpegPath())\n .inputOptions([\n `-ss ${adjustedStartTime}`,\n `-to ${endTime + toleranceInSeconds}`,\n ])\n .outputOptions(['-c copy'])\n .output(outputPath)\n .on('end', () => {\n this.downloadedVideoMap.set(url, {\n localPath: outputPath,\n startTimeOffset: adjustedStartTime,\n });\n resolve(outputPath);\n })\n .on('error', (err: Error) => reject(err))\n .run();\n });\n });\n }\n\n public getTime() {\n return this.startTime + this.framesProcessed / this.fps;\n }\n\n public getLastTime() {\n return this.startTime + (this.framesProcessed - 1) / this.fps;\n }\n\n public getLastFrame() {\n return this.lastImage;\n }\n\n public getWidth() {\n return this.width;\n }\n\n public getHeight() {\n return this.height;\n }\n\n private getEndTime(startTime: number) {\n return Math.min(\n startTime + VideoFrameExtractor.chunkLengthInSeconds,\n this.duration,\n );\n }\n\n private getArgs(\n codec: string,\n range?: [number, number],\n fps?: number,\n ): {inputOptions: string[]; outputOptions: string[]} {\n const inputOptions = [];\n const outputOptions = [];\n\n inputOptions.push('-loglevel', ffmpegSettings.getLogLevel());\n\n if (range) {\n inputOptions.push(\n ...['-ss', range[0].toFixed(2), '-to', range[1].toFixed(2)],\n );\n }\n\n if (codec === 'vp9') {\n inputOptions.push('-vcodec', 'libvpx-vp9');\n }\n\n if (fps) {\n outputOptions.push('-vf', `fps=fps=${fps}`);\n }\n\n if (!range) {\n outputOptions.push('-vframes', '1');\n }\n\n outputOptions.push('-f', 'rawvideo');\n outputOptions.push('-pix_fmt', 'rgba');\n\n return {inputOptions, outputOptions};\n }\n\n private createFfmpegProcess(\n startTime: number,\n toTime: number,\n filePath: string,\n fps: number,\n codec: string,\n ): FfmpegTypes.FfmpegCommand {\n const {inputOptions, outputOptions} = this.getArgs(\n codec,\n [startTime, toTime],\n fps,\n );\n\n const process = ffmpeg(filePath)\n .setFfmpegPath(this.ffmpegPath)\n .inputOptions(inputOptions)\n .outputOptions(outputOptions)\n .on('end', () => {\n this.handleClose(0);\n })\n .on('error', (err: Error) => {\n this.handleError(err);\n })\n .on('stderr', (stderrLine: string) => {\n console.log(stderrLine);\n })\n .on('stdout', (stderrLine: string) => {\n console.log(stderrLine);\n });\n\n const ffstream = process.pipe();\n ffstream.on('data', (data: Buffer) => {\n this.processData(data);\n });\n\n return process;\n }\n\n /**\n * We call this in the case that the time requested is greater than the\n * duration of the video. In this case, we want to display the first frame\n * of the video.\n *\n * Note: This does NOT match the behavior of the old implementation\n * inside of 2d/src/lib/components/Video.ts. In the old implementation, the\n * last frame is shown instead of the first frame.\n */\n private createFfmpegProcessToExtractFirstFrame(\n filePath: string,\n codec: string,\n ): FfmpegTypes.FfmpegCommand {\n const {inputOptions, outputOptions} = this.getArgs(\n codec,\n undefined,\n undefined,\n );\n\n const process = ffmpeg(filePath)\n .setFfmpegPath(this.ffmpegPath)\n .inputOptions(inputOptions)\n .outputOptions(outputOptions)\n .on('end', () => {\n this.handleClose(0);\n })\n .on('error', (err: Error) => {\n this.handleError(err);\n })\n .on('stderr', (stderrLine: string) => {\n console.log(stderrLine);\n })\n .on('stdout', (stderrLine: string) => {\n console.log(stderrLine);\n });\n\n const ffstream = process.pipe();\n ffstream.on('data', (data: Buffer) => {\n this.processData(data);\n });\n\n return process;\n }\n\n private processData(data: Buffer) {\n let dataOffset = 0;\n\n while (dataOffset < data.length) {\n const remainingSpace = this.frameSize - this.bufferOffset;\n const chunkSize = Math.min(remainingSpace, data.length - dataOffset);\n\n data.copy(\n this.buffer as any,\n this.bufferOffset,\n dataOffset,\n dataOffset + chunkSize,\n );\n this.bufferOffset += chunkSize;\n dataOffset += chunkSize;\n\n // We have a complete frame\n if (this.bufferOffset === this.frameSize) {\n this.imageBuffers.push(Buffer.from(this.buffer as Uint8Array)); // Create a copy\n this.bufferOffset = 0; // Reset buffer for next frame\n }\n }\n }\n\n public async popImage() {\n if (this.imageBuffers.length) {\n const image = this.imageBuffers.shift()!;\n this.framesProcessed++;\n this.lastImage = image;\n return image;\n }\n\n if (this.state === 'error') {\n throw new Error('An error occurred while extracting the video frames.');\n }\n\n // If the video is done and there are no more frames to extract, return the last frame.\n if (this.state === 'done' && this.toTime >= this.duration) {\n return this.lastImage;\n }\n\n // If there are more frames to extract, request the next chunk.\n if (this.state === 'done') {\n this.startTime = this.toTime;\n this.toTime = Math.min(\n this.startTime + VideoFrameExtractor.chunkLengthInSeconds,\n this.duration,\n );\n\n if (!this.codec) {\n throw new Error(\n \"Can't extract frames without a codec. This error should never happen.\",\n );\n }\n\n this.process = this.createFfmpegProcess(\n this.startTime,\n this.toTime,\n this.downloadedFilePath,\n this.fps,\n this.codec,\n );\n\n this.state = 'processing';\n }\n\n while (this.imageBuffers.length < 1) {\n await new Promise(resolve => setTimeout(resolve, 50));\n }\n\n const image = this.imageBuffers.shift()!;\n this.framesProcessed++;\n this.lastImage = image;\n return image;\n }\n\n private handleClose(code: number) {\n this.state = code === 0 ? 'done' : 'error';\n }\n\n private async handleError(err: any) {\n const code = err.code;\n\n if (this.terminated) {\n return;\n }\n\n if (code === 'ENOENT') {\n sendEvent(EventName.Error, {error: 'ffmpeg-not-found'});\n throw new Error(\n 'Error: ffmpeg not found. Make sure ffmpeg is installed on your system.',\n );\n } else if (err.message.includes('SIGSEGV')) {\n sendEvent(EventName.Error, {\n error: 'ffmpeg-sigsegv',\n message: err.message,\n });\n throw new Error(\n `Error: Segmentation fault when running ffmpeg. This is a common issue on Linux, you might be able to fix it by installing nscd ('sudo apt-get install nscd'). For more information, see https://docs.re.video/common-issues/ffmpeg/`,\n );\n } else {\n await sendEvent(EventName.Error, {\n error: 'ffmpeg-error',\n message: err.message,\n });\n throw new Error(\n `An ffmpeg error occurred while fetching frames from source video ${this.filePath}: ${err}`,\n );\n }\n }\n\n public destroy() {\n this.terminated = true;\n this.process?.kill('SIGTERM');\n }\n}\n"],"mappings":"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","names":["path","ffmpeg","import_fluent_ffmpeg","fs","os","path","os","path","uuidv4","ffmpeg","import_telemetry","fs","os","path","import_uuid","ffmpeg","uuidv4","err","process","image"]}
@@ -0,0 +1,138 @@
1
+ import { RendererSettings, FfmpegExporterOptions, RendererResult, AssetInfo } from '@twick/core';
2
+
3
+ interface FFmpegExporterSettings extends RendererSettings {
4
+ fastStart: boolean;
5
+ includeAudio: boolean;
6
+ output: string;
7
+ }
8
+ declare const extensions: Record<FfmpegExporterOptions['format'], string>;
9
+ /**
10
+ * The server-side implementation of the FFmpeg video exporter.
11
+ */
12
+ declare class FFmpegExporterServer {
13
+ private readonly stream;
14
+ private readonly command;
15
+ private readonly promise;
16
+ private readonly settings;
17
+ private readonly jobFolder;
18
+ private readonly format;
19
+ constructor(settings: FFmpegExporterSettings);
20
+ start(): Promise<void>;
21
+ handleFrame({ data }: {
22
+ data: string;
23
+ }): Promise<void>;
24
+ end(result: RendererResult): Promise<void>;
25
+ kill(): Promise<void>;
26
+ }
27
+
28
+ type AudioCodec = 'aac' | 'libopus';
29
+ declare function resolvePath(output: string, assetPath: string): string;
30
+ declare function makeSureFolderExists(folderPath: string): Promise<void>;
31
+ declare function concatenateMedia(files: string[], outputFile: string): Promise<void>;
32
+ declare function createSilentAudioFile(filePath: string, duration: number): Promise<unknown>;
33
+ declare function getVideoDuration(filePath: string): Promise<number>;
34
+ declare function getVideoDimensions(filePath: string): Promise<{
35
+ width: number;
36
+ height: number;
37
+ }>;
38
+ declare function doesFileExist(filePath: string): Promise<boolean>;
39
+ declare function mergeAudioWithVideo(audioPath: string, videoPath: string, outputPath: string, audioCodec?: AudioCodec): Promise<void>;
40
+ declare function checkForAudioStream(file: string): Promise<boolean>;
41
+ declare function getSampleRate(filePath: string): Promise<number>;
42
+ declare function getVideoCodec(filePath: string): Promise<string>;
43
+ declare function getVideoMetadata(filePath: string): Promise<{
44
+ codec: string;
45
+ width: number;
46
+ height: number;
47
+ }>;
48
+
49
+ declare const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec>;
50
+ declare function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }: {
51
+ outputDir: string;
52
+ tempDir: string;
53
+ assets: AssetInfo[][];
54
+ startFrame: number;
55
+ endFrame: number;
56
+ fps: number;
57
+ }): Promise<string[]>;
58
+ declare function mergeMedia(outputFilename: string, outputDir: string, tempDir: string, format: FfmpegExporterOptions['format']): Promise<void>;
59
+
60
+ declare const ffmpegLogLevels: readonly ["quiet", "panic", "fatal", "error", "warning", "info", "verbose", "debug", "trace"];
61
+ type LogLevel = (typeof ffmpegLogLevels)[number];
62
+ type FfmpegSettings = {
63
+ ffmpegPath?: string;
64
+ ffprobePath?: string;
65
+ ffmpegLogLevel?: LogLevel;
66
+ };
67
+ declare class FfmpegSettingState {
68
+ private ffmpegPath;
69
+ private ffprobePath;
70
+ private logLevel;
71
+ constructor();
72
+ getFfmpegPath(): string;
73
+ setFfmpegPath(ffmpegPath: string): void;
74
+ getFfprobePath(): string;
75
+ setFfprobePath(ffprobePath: string): void;
76
+ getLogLevel(): "quiet" | "panic" | "fatal" | "error" | "warning" | "info" | "verbose" | "debug" | "trace";
77
+ setLogLevel(logLevel: LogLevel): void;
78
+ }
79
+ declare const ffmpegSettings: FfmpegSettingState;
80
+
81
+ type VideoFrameExtractorState = 'processing' | 'done' | 'error';
82
+ /**
83
+ * Walks through a video file and extracts frames.
84
+ */
85
+ declare class VideoFrameExtractor {
86
+ private static readonly chunkLengthInSeconds;
87
+ private readonly ffmpegPath;
88
+ state: VideoFrameExtractorState;
89
+ filePath: string;
90
+ private downloadedFilePath;
91
+ private buffer;
92
+ private bufferOffset;
93
+ private imageBuffers;
94
+ private lastImage;
95
+ private startTime;
96
+ private startTimeOffset;
97
+ private duration;
98
+ private toTime;
99
+ private fps;
100
+ private framesProcessed;
101
+ private width;
102
+ private height;
103
+ private frameSize;
104
+ private codec;
105
+ private process;
106
+ private terminated;
107
+ static downloadedVideoMap: Map<string, {
108
+ localPath: string;
109
+ startTimeOffset: number;
110
+ }>;
111
+ constructor(filePath: string, startTime: number, fps: number, duration: number);
112
+ static downloadVideoChunk(url: string, startTime: number, endTime: number): Promise<unknown>;
113
+ getTime(): number;
114
+ getLastTime(): number;
115
+ getLastFrame(): Buffer | null;
116
+ getWidth(): number;
117
+ getHeight(): number;
118
+ private getEndTime;
119
+ private getArgs;
120
+ private createFfmpegProcess;
121
+ /**
122
+ * We call this in the case that the time requested is greater than the
123
+ * duration of the video. In this case, we want to display the first frame
124
+ * of the video.
125
+ *
126
+ * Note: This does NOT match the behavior of the old implementation
127
+ * inside of 2d/src/lib/components/Video.ts. In the old implementation, the
128
+ * last frame is shown instead of the first frame.
129
+ */
130
+ private createFfmpegProcessToExtractFirstFrame;
131
+ private processData;
132
+ popImage(): Promise<Buffer | null>;
133
+ private handleClose;
134
+ private handleError;
135
+ destroy(): void;
136
+ }
137
+
138
+ export { type AudioCodec, FFmpegExporterServer, type FFmpegExporterSettings, type FfmpegSettings, type LogLevel, VideoFrameExtractor, audioCodecs, checkForAudioStream, concatenateMedia, createSilentAudioFile, doesFileExist, extensions, ffmpegSettings, generateAudio, getSampleRate, getVideoCodec, getVideoDimensions, getVideoDuration, getVideoMetadata, makeSureFolderExists, mergeAudioWithVideo, mergeMedia, resolvePath };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,138 @@
1
- export * from './ffmpeg-exporter-server';
2
- export * from './generate-audio';
3
- export * from './settings';
4
- export * from './utils';
5
- export * from './video-frame-extractor';
6
- //# sourceMappingURL=index.d.ts.map
1
+ import { RendererSettings, FfmpegExporterOptions, RendererResult, AssetInfo } from '@twick/core';
2
+
3
+ interface FFmpegExporterSettings extends RendererSettings {
4
+ fastStart: boolean;
5
+ includeAudio: boolean;
6
+ output: string;
7
+ }
8
+ declare const extensions: Record<FfmpegExporterOptions['format'], string>;
9
+ /**
10
+ * The server-side implementation of the FFmpeg video exporter.
11
+ */
12
+ declare class FFmpegExporterServer {
13
+ private readonly stream;
14
+ private readonly command;
15
+ private readonly promise;
16
+ private readonly settings;
17
+ private readonly jobFolder;
18
+ private readonly format;
19
+ constructor(settings: FFmpegExporterSettings);
20
+ start(): Promise<void>;
21
+ handleFrame({ data }: {
22
+ data: string;
23
+ }): Promise<void>;
24
+ end(result: RendererResult): Promise<void>;
25
+ kill(): Promise<void>;
26
+ }
27
+
28
+ type AudioCodec = 'aac' | 'libopus';
29
+ declare function resolvePath(output: string, assetPath: string): string;
30
+ declare function makeSureFolderExists(folderPath: string): Promise<void>;
31
+ declare function concatenateMedia(files: string[], outputFile: string): Promise<void>;
32
+ declare function createSilentAudioFile(filePath: string, duration: number): Promise<unknown>;
33
+ declare function getVideoDuration(filePath: string): Promise<number>;
34
+ declare function getVideoDimensions(filePath: string): Promise<{
35
+ width: number;
36
+ height: number;
37
+ }>;
38
+ declare function doesFileExist(filePath: string): Promise<boolean>;
39
+ declare function mergeAudioWithVideo(audioPath: string, videoPath: string, outputPath: string, audioCodec?: AudioCodec): Promise<void>;
40
+ declare function checkForAudioStream(file: string): Promise<boolean>;
41
+ declare function getSampleRate(filePath: string): Promise<number>;
42
+ declare function getVideoCodec(filePath: string): Promise<string>;
43
+ declare function getVideoMetadata(filePath: string): Promise<{
44
+ codec: string;
45
+ width: number;
46
+ height: number;
47
+ }>;
48
+
49
+ declare const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec>;
50
+ declare function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }: {
51
+ outputDir: string;
52
+ tempDir: string;
53
+ assets: AssetInfo[][];
54
+ startFrame: number;
55
+ endFrame: number;
56
+ fps: number;
57
+ }): Promise<string[]>;
58
+ declare function mergeMedia(outputFilename: string, outputDir: string, tempDir: string, format: FfmpegExporterOptions['format']): Promise<void>;
59
+
60
+ declare const ffmpegLogLevels: readonly ["quiet", "panic", "fatal", "error", "warning", "info", "verbose", "debug", "trace"];
61
+ type LogLevel = (typeof ffmpegLogLevels)[number];
62
+ type FfmpegSettings = {
63
+ ffmpegPath?: string;
64
+ ffprobePath?: string;
65
+ ffmpegLogLevel?: LogLevel;
66
+ };
67
+ declare class FfmpegSettingState {
68
+ private ffmpegPath;
69
+ private ffprobePath;
70
+ private logLevel;
71
+ constructor();
72
+ getFfmpegPath(): string;
73
+ setFfmpegPath(ffmpegPath: string): void;
74
+ getFfprobePath(): string;
75
+ setFfprobePath(ffprobePath: string): void;
76
+ getLogLevel(): "quiet" | "panic" | "fatal" | "error" | "warning" | "info" | "verbose" | "debug" | "trace";
77
+ setLogLevel(logLevel: LogLevel): void;
78
+ }
79
+ declare const ffmpegSettings: FfmpegSettingState;
80
+
81
+ type VideoFrameExtractorState = 'processing' | 'done' | 'error';
82
+ /**
83
+ * Walks through a video file and extracts frames.
84
+ */
85
+ declare class VideoFrameExtractor {
86
+ private static readonly chunkLengthInSeconds;
87
+ private readonly ffmpegPath;
88
+ state: VideoFrameExtractorState;
89
+ filePath: string;
90
+ private downloadedFilePath;
91
+ private buffer;
92
+ private bufferOffset;
93
+ private imageBuffers;
94
+ private lastImage;
95
+ private startTime;
96
+ private startTimeOffset;
97
+ private duration;
98
+ private toTime;
99
+ private fps;
100
+ private framesProcessed;
101
+ private width;
102
+ private height;
103
+ private frameSize;
104
+ private codec;
105
+ private process;
106
+ private terminated;
107
+ static downloadedVideoMap: Map<string, {
108
+ localPath: string;
109
+ startTimeOffset: number;
110
+ }>;
111
+ constructor(filePath: string, startTime: number, fps: number, duration: number);
112
+ static downloadVideoChunk(url: string, startTime: number, endTime: number): Promise<unknown>;
113
+ getTime(): number;
114
+ getLastTime(): number;
115
+ getLastFrame(): Buffer | null;
116
+ getWidth(): number;
117
+ getHeight(): number;
118
+ private getEndTime;
119
+ private getArgs;
120
+ private createFfmpegProcess;
121
+ /**
122
+ * We call this in the case that the time requested is greater than the
123
+ * duration of the video. In this case, we want to display the first frame
124
+ * of the video.
125
+ *
126
+ * Note: This does NOT match the behavior of the old implementation
127
+ * inside of 2d/src/lib/components/Video.ts. In the old implementation, the
128
+ * last frame is shown instead of the first frame.
129
+ */
130
+ private createFfmpegProcessToExtractFirstFrame;
131
+ private processData;
132
+ popImage(): Promise<Buffer | null>;
133
+ private handleClose;
134
+ private handleError;
135
+ destroy(): void;
136
+ }
137
+
138
+ export { type AudioCodec, FFmpegExporterServer, type FFmpegExporterSettings, type FfmpegSettings, type LogLevel, VideoFrameExtractor, audioCodecs, checkForAudioStream, concatenateMedia, createSilentAudioFile, doesFileExist, extensions, ffmpegSettings, generateAudio, getSampleRate, getVideoCodec, getVideoDimensions, getVideoDuration, getVideoMetadata, makeSureFolderExists, mergeAudioWithVideo, mergeMedia, resolvePath };