@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/ffmpeg-exporter-server.ts","../src/image-stream.ts","../src/settings.ts","../src/generate-audio.ts","../src/utils.ts","../src/video-frame-extractor.ts"],"sourcesContent":["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","fs","os","path","os","path","ffmpeg","EventName","sendEvent","fs","os","path","uuidv4","ffmpeg","uuidv4","err","process","image","sendEvent","EventName"]}
package/package.json CHANGED
@@ -1,30 +1,48 @@
1
1
  {
2
2
  "name": "@twick/ffmpeg",
3
- "version": "0.14.21",
3
+ "version": "0.15.0",
4
4
  "description": "Ffmpeg utilities for twick",
5
- "main": "dist/index.js",
6
5
  "author": "twick",
7
- "bugs": "https://github.com/ncounterspecialist/twick-base/issues",
8
6
  "license": "MIT",
9
- "scripts": {
10
- "build": "tsc"
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
11
17
  },
18
+ "sideEffects": false,
12
19
  "files": [
13
- "dist",
14
- "types"
20
+ "dist"
15
21
  ],
22
+ "scripts": {
23
+ "dev": "tsup --watch",
24
+ "build": "tsup",
25
+ "test": "vitest"
26
+ },
16
27
  "dependencies": {
17
28
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
18
29
  "@ffprobe-installer/ffprobe": "^2.0.0",
19
- "@twick/core": "0.14.21",
20
- "@twick/telemetry": "0.14.21",
30
+ "@twick/core": "0.15.0",
31
+ "@twick/telemetry": "0.15.0",
21
32
  "fluent-ffmpeg": "^2.1.2",
22
33
  "uuid": "^10.0.0"
23
34
  },
24
35
  "devDependencies": {
25
36
  "@types/ffprobe-static": "^2.0.3",
26
37
  "@types/fluent-ffmpeg": "^2.1.21",
27
- "@types/uuid": "^10.0.0"
38
+ "@types/uuid": "^10.0.0",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.5.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/ncounterspecialist/twick-base.git"
28
45
  },
29
- "gitHead": "597a0cb90db00991294a13ac41c2b86f4e11e13f"
46
+ "bugs": "https://github.com/ncounterspecialist/twick-base/issues",
47
+ "gitHead": "232564ca74e12d1cc192ff7f5896d54e48212753"
30
48
  }
@@ -1,26 +0,0 @@
1
- import type { FfmpegExporterOptions, RendererResult, RendererSettings } from '@twick/core';
2
- export interface FFmpegExporterSettings extends RendererSettings {
3
- fastStart: boolean;
4
- includeAudio: boolean;
5
- output: string;
6
- }
7
- export declare const extensions: Record<FfmpegExporterOptions['format'], string>;
8
- /**
9
- * The server-side implementation of the FFmpeg video exporter.
10
- */
11
- export declare class FFmpegExporterServer {
12
- private readonly stream;
13
- private readonly command;
14
- private readonly promise;
15
- private readonly settings;
16
- private readonly jobFolder;
17
- private readonly format;
18
- constructor(settings: FFmpegExporterSettings);
19
- start(): Promise<void>;
20
- handleFrame({ data }: {
21
- data: string;
22
- }): Promise<void>;
23
- end(result: RendererResult): Promise<void>;
24
- kill(): Promise<void>;
25
- }
26
- //# sourceMappingURL=ffmpeg-exporter-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ffmpeg-exporter-server.d.ts","sourceRoot":"","sources":["../src/ffmpeg-exporter-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAQrB,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAQD,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAItE,CAAC;AAEF;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;gBAEtC,QAAQ,EAAE,sBAAsB;IA4CtC,KAAK;IAIL,WAAW,CAAC,EAAC,IAAI,EAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;IAKlC,GAAG,CAAC,MAAM,EAAE,cAAc;IAc1B,IAAI;CAQlB"}
@@ -1,90 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FFmpegExporterServer = exports.extensions = void 0;
4
- const telemetry_1 = require("@twick/telemetry");
5
- const ffmpeg = require("fluent-ffmpeg");
6
- const os = require("os");
7
- const path = require("path");
8
- const image_stream_1 = require("./image-stream");
9
- const settings_1 = require("./settings");
10
- const pixelFormats = {
11
- mp4: 'yuv420p',
12
- webm: 'yuva420p',
13
- proRes: 'yuva444p10le',
14
- };
15
- exports.extensions = {
16
- mp4: 'mp4',
17
- webm: 'webm',
18
- proRes: 'mov',
19
- };
20
- /**
21
- * The server-side implementation of the FFmpeg video exporter.
22
- */
23
- class FFmpegExporterServer {
24
- constructor(settings) {
25
- if (settings.exporter.name !== '@twick/core/ffmpeg') {
26
- throw new Error('Invalid exporter');
27
- }
28
- this.settings = settings;
29
- this.format = settings.exporter.options.format;
30
- this.jobFolder = path.join(os.tmpdir(), `twick-${this.settings.name}-${settings.hiddenFolderId}`);
31
- this.stream = new image_stream_1.ImageStream();
32
- ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
33
- this.command = ffmpeg();
34
- // Input image sequence
35
- this.command
36
- .input(this.stream)
37
- .inputFormat('image2pipe')
38
- .inputFps(settings.fps);
39
- // Output settings
40
- const size = {
41
- x: Math.round(settings.size.x * settings.resolutionScale),
42
- y: Math.round(settings.size.y * settings.resolutionScale),
43
- };
44
- this.command
45
- .output(path.join(this.jobFolder, `visuals.${exports.extensions[this.format]}`))
46
- .outputOptions([`-pix_fmt ${pixelFormats[this.format]}`, '-shortest'])
47
- .outputFps(settings.fps)
48
- .size(`${size.x}x${size.y}`);
49
- if (this.format === 'proRes') {
50
- this.command.outputOptions(['-c:v prores_ks', '-profile:v 4444']);
51
- }
52
- this.command.outputOptions(['-movflags +faststart']);
53
- this.promise = new Promise((resolve, reject) => {
54
- this.command.on('end', resolve).on('error', reject);
55
- });
56
- }
57
- async start() {
58
- this.command.run();
59
- }
60
- async handleFrame({ data }) {
61
- const base64Data = data.slice(data.indexOf(',') + 1);
62
- this.stream.pushImage(Buffer.from(base64Data, 'base64'));
63
- }
64
- async end(result) {
65
- this.stream.pushImage(null);
66
- if (result === 1) {
67
- try {
68
- this.command.kill('SIGKILL');
69
- await this.promise;
70
- }
71
- catch (err) {
72
- (0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, { message: err.message });
73
- }
74
- }
75
- else {
76
- await this.promise;
77
- }
78
- }
79
- async kill() {
80
- try {
81
- this.command.kill('SIGKILL');
82
- await this.promise;
83
- }
84
- catch (_) {
85
- return;
86
- }
87
- }
88
- }
89
- exports.FFmpegExporterServer = FFmpegExporterServer;
90
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLWV4cG9ydGVyLXNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9mZm1wZWctZXhwb3J0ZXItc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUtBLGdEQUFzRDtBQUN0RCx3Q0FBd0M7QUFDeEMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixpREFBMkM7QUFDM0MseUNBQTBDO0FBUTFDLE1BQU0sWUFBWSxHQUFvRDtJQUNwRSxHQUFHLEVBQUUsU0FBUztJQUNkLElBQUksRUFBRSxVQUFVO0lBQ2hCLE1BQU0sRUFBRSxjQUFjO0NBQ3ZCLENBQUM7QUFFVyxRQUFBLFVBQVUsR0FBb0Q7SUFDekUsR0FBRyxFQUFFLEtBQUs7SUFDVixJQUFJLEVBQUUsTUFBTTtJQUNaLE1BQU0sRUFBRSxLQUFLO0NBQ2QsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBYSxvQkFBb0I7SUFRL0IsWUFBbUIsUUFBZ0M7UUFDakQsSUFBSSxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksS0FBSyxvQkFBb0IsRUFBRSxDQUFDO1lBQ3BELE1BQU0sSUFBSSxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUN0QyxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7UUFDekIsSUFBSSxDQUFDLE1BQU0sR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7UUFFL0MsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUN4QixFQUFFLENBQUMsTUFBTSxFQUFFLEVBQ1gsU0FBUyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksSUFBSSxRQUFRLENBQUMsY0FBYyxFQUFFLENBQ3pELENBQUM7UUFDRixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksMEJBQVcsRUFBRSxDQUFDO1FBRWhDLE1BQU0sQ0FBQyxhQUFhLENBQUMseUJBQWMsQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFD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
@@ -1,13 +0,0 @@
1
- import type { AssetInfo, FfmpegExporterOptions } from '@twick/core';
2
- import type { AudioCodec } from './utils';
3
- export declare const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec>;
4
- export declare function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }: {
5
- outputDir: string;
6
- tempDir: string;
7
- assets: AssetInfo[][];
8
- startFrame: number;
9
- endFrame: number;
10
- fps: number;
11
- }): Promise<string[]>;
12
- export declare function mergeMedia(outputFilename: string, outputDir: string, tempDir: string, format: FfmpegExporterOptions['format']): Promise<void>;
13
- //# sourceMappingURL=generate-audio.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-audio.d.ts","sourceRoot":"","sources":["../src/generate-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,qBAAqB,EAAC,MAAM,aAAa,CAAC;AAOlE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,SAAS,CAAC;AASxC,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,UAAU,CAKzE,CAAC;AAkMJ,wBAAsB,aAAa,CAAC,EAClC,SAAS,EACT,OAAO,EACP,MAAM,EACN,UAAU,EACV,QAAQ,EACR,GAAG,GACJ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,qBAkCA;AAED,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,iBA6BxC"}
@@ -1,195 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.audioCodecs = void 0;
4
- exports.generateAudio = generateAudio;
5
- exports.mergeMedia = mergeMedia;
6
- const ffmpeg = require("fluent-ffmpeg");
7
- const fs = require("fs");
8
- const os = require("os");
9
- const path = require("path");
10
- const ffmpeg_exporter_server_1 = require("./ffmpeg-exporter-server");
11
- const settings_1 = require("./settings");
12
- const utils_1 = require("./utils");
13
- exports.audioCodecs = {
14
- mp4: 'aac',
15
- webm: 'libopus',
16
- proRes: 'aac',
17
- };
18
- const SAMPLE_RATE = 48000;
19
- function getAssetPlacement(frames) {
20
- const assets = [];
21
- // A map to keep track of the first and last currentTime for each asset.
22
- const assetTimeMap = new Map();
23
- for (let frame = 0; frame < frames.length; frame++) {
24
- for (const asset of frames[frame]) {
25
- if (!assetTimeMap.has(asset.key)) {
26
- // If the asset is not in the map, add it with its current time as both start and end.
27
- assetTimeMap.set(asset.key, {
28
- start: asset.currentTime,
29
- end: asset.currentTime,
30
- });
31
- assets.push({
32
- key: asset.key,
33
- src: asset.src,
34
- type: asset.type,
35
- startInVideo: frame,
36
- endInVideo: frame,
37
- duration: 0, // Placeholder, will be recalculated later based on frames
38
- durationInSeconds: 0, // Placeholder, will be calculated based on currentTime
39
- playbackRate: asset.playbackRate,
40
- volume: asset.volume,
41
- trimLeftInSeconds: asset.currentTime,
42
- });
43
- }
44
- else {
45
- // If the asset is already in the map, update the end time.
46
- const timeInfo = assetTimeMap.get(asset.key);
47
- if (timeInfo) {
48
- timeInfo.end = asset.currentTime;
49
- assetTimeMap.set(asset.key, timeInfo);
50
- }
51
- const existingAsset = assets.find(a => a.key === asset.key);
52
- if (existingAsset) {
53
- existingAsset.endInVideo = frame;
54
- }
55
- }
56
- }
57
- }
58
- // Calculate the duration based on frame count and durationInSeconds based on currentTime.
59
- assets.forEach(asset => {
60
- const timeInfo = assetTimeMap.get(asset.key);
61
- if (timeInfo) {
62
- // Calculate durationInSeconds based on the start and end currentTime values.
63
- asset.durationInSeconds =
64
- (timeInfo.end - timeInfo.start) / asset.playbackRate;
65
- }
66
- // Recalculate the original duration based on frame count.
67
- asset.duration = asset.endInVideo - asset.startInVideo + 1;
68
- });
69
- return assets;
70
- }
71
- function calculateAtempoFilters(playbackRate) {
72
- const atempoFilters = [];
73
- // Calculate how many times we need to 100x the speed
74
- let rate = playbackRate;
75
- while (rate > 100.0) {
76
- atempoFilters.push('atempo=100.0');
77
- rate /= 100.0;
78
- }
79
- // Add the last atempo filter with the remaining rate
80
- if (rate > 1.0) {
81
- atempoFilters.push(`atempo=${rate}`);
82
- }
83
- // Calculate how many times we need to halve the speed
84
- rate = playbackRate;
85
- while (rate < 0.5) {
86
- atempoFilters.push('atempo=0.5');
87
- rate *= 2.0;
88
- }
89
- // Add the last atempo filter with the remaining rate
90
- if (rate < 1.0) {
91
- atempoFilters.push(`atempo=${rate}`);
92
- }
93
- return atempoFilters;
94
- }
95
- async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps) {
96
- // Construct the output path
97
- const sanitizedKey = asset.key.replace(/[/[\]]/g, '-');
98
- const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);
99
- const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;
100
- const trimRight = 1 / fps +
101
- Math.min(trimLeft + asset.durationInSeconds, trimLeft + (endFrame - startFrame) / fps);
102
- const padStart = (asset.startInVideo / fps) * 1000;
103
- const assetSampleRate = await (0, utils_1.getSampleRate)((0, utils_1.resolvePath)(outputDir, asset.src));
104
- const padEnd = Math.max(0, (assetSampleRate * (endFrame - startFrame + 1)) / fps -
105
- (assetSampleRate * asset.duration) / fps -
106
- (assetSampleRate * padStart) / 1000);
107
- 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
108
- const resolvedPath = (0, utils_1.resolvePath)(outputDir, asset.src);
109
- await new Promise((resolve, reject) => {
110
- const audioFilters = [
111
- ...atempoFilters,
112
- `atrim=start=${trimLeft}:end=${trimRight}`,
113
- `apad=pad_len=${padEnd}`,
114
- `adelay=${padStart}|${padStart}|${padStart}`,
115
- `volume=${asset.volume}`,
116
- ].join(',');
117
- ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
118
- ffmpeg(resolvedPath)
119
- .audioChannels(2)
120
- .audioCodec('pcm_s16le')
121
- .audioFrequency(SAMPLE_RATE)
122
- .outputOptions([`-af`, audioFilters])
123
- .on('end', () => {
124
- resolve();
125
- })
126
- .on('error', err => {
127
- console.error(`Error processing audio for asset key: ${asset.key}`, err);
128
- reject(err);
129
- })
130
- .save(outputPath);
131
- });
132
- return outputPath;
133
- }
134
- async function mergeAudioTracks(tempDir, audioFilenames) {
135
- return new Promise((resolve, reject) => {
136
- ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
137
- const command = ffmpeg();
138
- audioFilenames.forEach(filename => {
139
- command.input(filename);
140
- });
141
- command
142
- .complexFilter([
143
- `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`,
144
- ])
145
- .outputOptions(['-c:a', 'pcm_s16le'])
146
- .on('end', () => {
147
- resolve();
148
- })
149
- .on('error', err => {
150
- console.error(`Error merging audio tracks: ${err.message}`);
151
- reject(err);
152
- })
153
- .save(path.join(tempDir, `audio.wav`));
154
- });
155
- }
156
- async function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }) {
157
- const fullTempDir = path.join(os.tmpdir(), tempDir);
158
- await (0, utils_1.makeSureFolderExists)(outputDir);
159
- await (0, utils_1.makeSureFolderExists)(fullTempDir);
160
- const assetPositions = getAssetPlacement(assets);
161
- const audioFilenames = [];
162
- for (const asset of assetPositions) {
163
- let hasAudioStream = true;
164
- if (asset.type !== 'audio') {
165
- hasAudioStream = await (0, utils_1.checkForAudioStream)((0, utils_1.resolvePath)(outputDir, asset.src));
166
- }
167
- if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {
168
- const filename = await prepareAudio(outputDir, fullTempDir, asset, startFrame, endFrame, fps);
169
- audioFilenames.push(filename);
170
- }
171
- }
172
- if (audioFilenames.length > 0) {
173
- await mergeAudioTracks(fullTempDir, audioFilenames);
174
- }
175
- return audioFilenames;
176
- }
177
- async function mergeMedia(outputFilename, outputDir, tempDir, format) {
178
- const fullTempDir = path.join(os.tmpdir(), tempDir);
179
- await (0, utils_1.makeSureFolderExists)(outputDir);
180
- await (0, utils_1.makeSureFolderExists)(fullTempDir);
181
- const audioWavExists = fs.existsSync(path.join(fullTempDir, `audio.wav`));
182
- if (audioWavExists) {
183
- await (0, utils_1.mergeAudioWithVideo)(path.join(fullTempDir, `audio.wav`), path.join(fullTempDir, `visuals.${ffmpeg_exporter_server_1.extensions[format]}`), path.join(outputDir, `${outputFilename}.${ffmpeg_exporter_server_1.extensions[format]}`), exports.audioCodecs[format]);
184
- }
185
- else {
186
- const destination = path.join(outputDir, `${outputFilename}.${ffmpeg_exporter_server_1.extensions[format]}`);
187
- await fs.promises.copyFile(path.join(fullTempDir, `visuals.${ffmpeg_exporter_server_1.extensions[format]}`), destination);
188
- }
189
- if (fullTempDir.endsWith('-undefined')) {
190
- await fs.promises
191
- .rm(fullTempDir, { recursive: true, force: true })
192
- .catch(() => { });
193
- }
194
- }
195
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGUtYXVkaW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZ2VuZXJhdGUtYXVkaW8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBdU5BLHNDQWdEQztBQUVELGdDQWlDQztBQXpTRCx3Q0FBd0M7QUFDeEMseUJBQXlCO0FBQ3pCLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IscUVBQW9EO0FBQ3BELHlDQUEwQztBQUUxQyxtQ0FNaUI7QUFFSixRQUFBLFdBQVcsR0FDdEI7SUFDRSxHQUFHLEVBQUUsS0FBSztJQUNWLElBQUksRUFBRSxTQUFTO0lBQ2YsTUFBTSxFQUFFLEtBQUs7Q0FDZCxDQUFDO0FBZUosTUFBTSxXQUFXLEdBQUcsS0FBSyxDQUFDO0FBRTFCLFNBQVMsaUJBQWlCLENBQUMsTUFBcUI7SUFDOUMsTUFBTSxNQUFNLEdBQWlCLEVBQUUsQ0FBQztJQUVoQyx3RUFBd0U7SUFDeEUsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLEVBQXdDLENBQUM7SUFFckUsS0FBSyxJQUFJLEtBQUssR0FBRyxDQUFDLEVBQUUsS0FBSyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQztRQUNuRCxLQUFLLE1BQU0sS0FBSyxJQUFJLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2xDLElBQUksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNqQyxzRkFBc0Y7Z0JBQ3RGLFlBQVksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtvQkFDMUIsS0FBSyxFQUFFLEtBQUssQ0FBQyxXQUFXO29CQUN4QixHQUFHLEVBQUUsS0FBSyxDQUFDLFdBQVc7aUJBQ3ZCLENBQUMsQ0FBQztnQkFDSCxNQUFNLENBQUMsSUFBSSxDQUFDO29CQUNWLEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRztvQkFDZCxHQUFHLEVBQUUsS0FB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
@@ -1,8 +0,0 @@
1
- import { Readable } from 'stream';
2
- export declare class ImageStream extends Readable {
3
- private image;
4
- private hasData;
5
- pushImage(image: Buffer | null): void;
6
- _read(): void;
7
- }
8
- //# sourceMappingURL=image-stream.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"image-stream.d.ts","sourceRoot":"","sources":["../src/image-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,QAAQ,CAAC;AAEhC,qBAAa,WAAY,SAAQ,QAAQ;IACvC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;IAEjB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOrB,KAAK;CAMtB"}
@@ -1,25 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ImageStream = void 0;
4
- const stream_1 = require("stream");
5
- class ImageStream extends stream_1.Readable {
6
- constructor() {
7
- super(...arguments);
8
- this.image = null;
9
- this.hasData = false;
10
- }
11
- pushImage(image) {
12
- this.image = image;
13
- this.hasData = true;
14
- this._read();
15
- }
16
- // eslint-disable-next-line @typescript-eslint/naming-convention
17
- _read() {
18
- if (this.hasData) {
19
- this.hasData = false;
20
- this.push(this.image);
21
- }
22
- }
23
- }
24
- exports.ImageStream = ImageStream;
25
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2Utc3RyZWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ltYWdlLXN0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtQ0FBZ0M7QUFFaEMsTUFBYSxXQUFZLFNBQVEsaUJBQVE7SUFBekM7O1FBQ1UsVUFBSyxHQUFrQixJQUFJLENBQUM7UUFDNUIsWUFBTyxHQUFHLEtBQUssQ0FBQztJQWUxQixDQUFDO0lBYlEsU0FBUyxDQUFDLEtBQW9CO1FBQ25DLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNmLENBQUM7SUFFRCxnRUFBZ0U7SUFDaEQsS0FBSztRQUNuQixJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUNyQixJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN4QixDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBakJELGtDQWlCQyJ9
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC"}
@@ -1,22 +0,0 @@
1
- declare const ffmpegLogLevels: readonly ["quiet", "panic", "fatal", "error", "warning", "info", "verbose", "debug", "trace"];
2
- export type LogLevel = (typeof ffmpegLogLevels)[number];
3
- export type FfmpegSettings = {
4
- ffmpegPath?: string;
5
- ffprobePath?: string;
6
- ffmpegLogLevel?: LogLevel;
7
- };
8
- declare class FfmpegSettingState {
9
- private ffmpegPath;
10
- private ffprobePath;
11
- private logLevel;
12
- constructor();
13
- getFfmpegPath(): string;
14
- setFfmpegPath(ffmpegPath: string): void;
15
- getFfprobePath(): string;
16
- setFfprobePath(ffprobePath: string): void;
17
- getLogLevel(): "error" | "info" | "verbose" | "debug" | "warning" | "quiet" | "panic" | "fatal" | "trace";
18
- setLogLevel(logLevel: LogLevel): void;
19
- }
20
- export declare const ffmpegSettings: FfmpegSettingState;
21
- export {};
22
- //# sourceMappingURL=settings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,eAAe,+FAUX,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;CAC3B,CAAC;AAEF,cAAM,kBAAkB;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAW;;IA2BpB,aAAa;IAIb,aAAa,CAAC,UAAU,EAAE,MAAM;IAIhC,cAAc;IAId,cAAc,CAAC,WAAW,EAAE,MAAM;IAIlC,WAAW;IAIX,WAAW,CAAC,QAAQ,EAAE,QAAQ;CAGtC;AAED,eAAO,MAAM,cAAc,oBAA2B,CAAC"}