@twick/ffmpeg 0.15.6 → 0.15.8
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.
- package/dist/index.cjs +4 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -86
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -441,13 +441,8 @@ function getAssetPlacement(frames) {
|
|
|
441
441
|
const timeInfo = assetTimeMap.get(asset.key);
|
|
442
442
|
if (timeInfo) {
|
|
443
443
|
asset.durationInSeconds = (timeInfo.end - timeInfo.start) / asset.playbackRate;
|
|
444
|
-
console.log(`[getAssetPlacement] Asset ${asset.key}:`);
|
|
445
|
-
console.log(` - currentTime range: ${timeInfo.start} to ${timeInfo.end}`);
|
|
446
|
-
console.log(` - durationInSeconds (from currentTime): ${asset.durationInSeconds}`);
|
|
447
444
|
}
|
|
448
445
|
asset.duration = asset.endInVideo - asset.startInVideo + 1;
|
|
449
|
-
console.log(` - frame range: ${asset.startInVideo} to ${asset.endInVideo}`);
|
|
450
|
-
console.log(` - duration (frames): ${asset.duration}`);
|
|
451
446
|
});
|
|
452
447
|
return assets;
|
|
453
448
|
}
|
|
@@ -472,40 +467,25 @@ function calculateAtempoFilters(playbackRate) {
|
|
|
472
467
|
return atempoFilters;
|
|
473
468
|
}
|
|
474
469
|
async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps) {
|
|
475
|
-
console.log(`[prepareAudio] Processing asset: ${asset.key}`);
|
|
476
|
-
console.log(`[prepareAudio] Asset src: ${asset.src}`);
|
|
477
|
-
console.log(`[prepareAudio] Asset type: ${asset.type}`);
|
|
478
|
-
console.log(`[prepareAudio] Playback rate: ${asset.playbackRate}`);
|
|
479
|
-
console.log(`[prepareAudio] Volume: ${asset.volume}`);
|
|
480
470
|
const sanitizedKey = asset.key.replace(/[/[\]]/g, "-");
|
|
481
471
|
const outputPath = path5.join(tempDir, `${sanitizedKey}.wav`);
|
|
482
|
-
console.log(`[prepareAudio] Output path: ${outputPath}`);
|
|
483
472
|
const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;
|
|
484
473
|
let effectiveDurationInSeconds = asset.durationInSeconds;
|
|
485
474
|
if (effectiveDurationInSeconds < 0.1) {
|
|
486
475
|
effectiveDurationInSeconds = asset.duration / fps;
|
|
487
|
-
console.log(`[prepareAudio] WARNING: durationInSeconds was ${asset.durationInSeconds}, using frame-based duration: ${effectiveDurationInSeconds}s`);
|
|
488
476
|
}
|
|
489
477
|
const trimRight = 1 / fps + Math.min(
|
|
490
478
|
trimLeft + effectiveDurationInSeconds,
|
|
491
479
|
trimLeft + (endFrame - startFrame) / fps
|
|
492
480
|
);
|
|
493
481
|
const padStart = asset.startInVideo / fps * 1e3;
|
|
494
|
-
console.log(`[prepareAudio] Trim calculation:`);
|
|
495
|
-
console.log(` - trimLeft: ${trimLeft}s`);
|
|
496
|
-
console.log(` - effectiveDurationInSeconds: ${effectiveDurationInSeconds}s`);
|
|
497
|
-
console.log(` - trimRight: ${trimRight}s`);
|
|
498
|
-
console.log(` - padStart: ${padStart}ms`);
|
|
499
482
|
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
500
|
-
console.log(`[prepareAudio] Resolved path: ${resolvedPath}`);
|
|
501
483
|
const assetSampleRate = await getSampleRate(resolvedPath);
|
|
502
|
-
console.log(`[prepareAudio] Sample rate: ${assetSampleRate}`);
|
|
503
484
|
const padEnd = Math.max(
|
|
504
485
|
0,
|
|
505
486
|
assetSampleRate * (endFrame - startFrame + 1) / fps - assetSampleRate * asset.duration / fps - assetSampleRate * padStart / 1e3
|
|
506
487
|
);
|
|
507
488
|
const atempoFilters = calculateAtempoFilters(asset.playbackRate);
|
|
508
|
-
console.log(`[prepareAudio] Atempo filters: ${atempoFilters.join(", ")}`);
|
|
509
489
|
await new Promise((resolve, reject) => {
|
|
510
490
|
const audioFilters = [
|
|
511
491
|
...atempoFilters,
|
|
@@ -514,42 +494,19 @@ async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps
|
|
|
514
494
|
`adelay=${padStart}|${padStart}|${padStart}`,
|
|
515
495
|
`volume=${asset.volume}`
|
|
516
496
|
].join(",");
|
|
517
|
-
console.log(`[prepareAudio] Audio filters: ${audioFilters}`);
|
|
518
|
-
console.log(`[prepareAudio] Starting ffmpeg processing...`);
|
|
519
497
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
520
|
-
ffmpeg2(resolvedPath).audioChannels(2).audioCodec("pcm_s16le").audioFrequency(SAMPLE_RATE).outputOptions([`-af`, audioFilters]).on("end", () =>
|
|
521
|
-
console.log(`[prepareAudio] Successfully processed audio for ${asset.key}`);
|
|
522
|
-
resolve();
|
|
523
|
-
}).on("error", (err) => {
|
|
524
|
-
console.error(`[prepareAudio] Error processing audio for asset key: ${asset.key}`, err);
|
|
525
|
-
reject(err);
|
|
526
|
-
}).save(outputPath);
|
|
498
|
+
ffmpeg2(resolvedPath).audioChannels(2).audioCodec("pcm_s16le").audioFrequency(SAMPLE_RATE).outputOptions([`-af`, audioFilters]).on("end", () => resolve()).on("error", (err) => reject(err)).save(outputPath);
|
|
527
499
|
});
|
|
528
|
-
console.log(`[prepareAudio] Audio file saved: ${outputPath}`);
|
|
529
500
|
return outputPath;
|
|
530
501
|
}
|
|
531
502
|
async function mergeAudioTracks(tempDir, audioFilenames) {
|
|
532
|
-
console.log(`[mergeAudioTracks] Starting merge of ${audioFilenames.length} tracks`);
|
|
533
|
-
audioFilenames.forEach((filename, idx) => {
|
|
534
|
-
console.log(`[mergeAudioTracks] Track ${idx + 1}: ${filename}`);
|
|
535
|
-
});
|
|
536
503
|
const outputPath = path5.join(tempDir, `audio.wav`);
|
|
537
|
-
console.log(`[mergeAudioTracks] Output path: ${outputPath}`);
|
|
538
504
|
return new Promise((resolve, reject) => {
|
|
539
505
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
540
506
|
const command = ffmpeg2();
|
|
541
|
-
audioFilenames.forEach((filename) =>
|
|
542
|
-
command.input(filename);
|
|
543
|
-
});
|
|
507
|
+
audioFilenames.forEach((filename) => command.input(filename));
|
|
544
508
|
const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;
|
|
545
|
-
|
|
546
|
-
command.complexFilter([complexFilter]).outputOptions(["-c:a", "pcm_s16le"]).on("end", () => {
|
|
547
|
-
console.log(`[mergeAudioTracks] Successfully merged audio tracks to: ${outputPath}`);
|
|
548
|
-
resolve();
|
|
549
|
-
}).on("error", (err) => {
|
|
550
|
-
console.error(`[mergeAudioTracks] Error merging audio tracks:`, err);
|
|
551
|
-
reject(err);
|
|
552
|
-
}).save(outputPath);
|
|
509
|
+
command.complexFilter([complexFilter]).outputOptions(["-c:a", "pcm_s16le"]).on("end", () => resolve()).on("error", (err) => reject(err)).save(outputPath);
|
|
553
510
|
});
|
|
554
511
|
}
|
|
555
512
|
async function generateAudio({
|
|
@@ -560,33 +517,18 @@ async function generateAudio({
|
|
|
560
517
|
endFrame,
|
|
561
518
|
fps
|
|
562
519
|
}) {
|
|
563
|
-
console.log(`[generateAudio] Starting audio generation`);
|
|
564
|
-
console.log(`[generateAudio] Output dir: ${outputDir}`);
|
|
565
|
-
console.log(`[generateAudio] Temp dir: ${tempDir}`);
|
|
566
|
-
console.log(`[generateAudio] Start frame: ${startFrame}, End frame: ${endFrame}`);
|
|
567
|
-
console.log(`[generateAudio] FPS: ${fps}`);
|
|
568
|
-
console.log(`[generateAudio] Total frames: ${assets.length}`);
|
|
569
520
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
570
|
-
console.log(`[generateAudio] Full temp dir: ${fullTempDir}`);
|
|
571
521
|
await makeSureFolderExists(outputDir);
|
|
572
522
|
await makeSureFolderExists(fullTempDir);
|
|
573
523
|
const assetPositions = getAssetPlacement(assets);
|
|
574
|
-
console.log(`[generateAudio] Found ${assetPositions.length} unique assets`);
|
|
575
|
-
assetPositions.forEach((asset, idx) => {
|
|
576
|
-
console.log(`[generateAudio] Asset ${idx + 1}: key=${asset.key}, src=${asset.src}, type=${asset.type}, playbackRate=${asset.playbackRate}, volume=${asset.volume}`);
|
|
577
|
-
});
|
|
578
524
|
const audioFilenames = [];
|
|
579
525
|
for (const asset of assetPositions) {
|
|
580
|
-
console.log(`[generateAudio] Processing asset: ${asset.key}`);
|
|
581
526
|
let hasAudioStream = true;
|
|
582
527
|
if (asset.type !== "audio") {
|
|
583
528
|
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
584
|
-
console.log(`[generateAudio] Checking for audio stream in: ${resolvedPath}`);
|
|
585
529
|
hasAudioStream = await checkForAudioStream(resolvedPath);
|
|
586
|
-
console.log(`[generateAudio] Has audio stream: ${hasAudioStream}`);
|
|
587
530
|
}
|
|
588
531
|
if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {
|
|
589
|
-
console.log(`[generateAudio] Asset ${asset.key} will be processed (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);
|
|
590
532
|
const filename = await prepareAudio(
|
|
591
533
|
outputDir,
|
|
592
534
|
fullTempDir,
|
|
@@ -596,59 +538,35 @@ async function generateAudio({
|
|
|
596
538
|
fps
|
|
597
539
|
);
|
|
598
540
|
audioFilenames.push(filename);
|
|
599
|
-
console.log(`[generateAudio] Added audio file to list: ${filename}`);
|
|
600
|
-
} else {
|
|
601
|
-
console.log(`[generateAudio] Skipping asset ${asset.key} (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);
|
|
602
541
|
}
|
|
603
542
|
}
|
|
604
|
-
console.log(`[generateAudio] Total audio files to merge: ${audioFilenames.length}`);
|
|
605
543
|
if (audioFilenames.length > 0) {
|
|
606
|
-
console.log(`[generateAudio] Merging ${audioFilenames.length} audio tracks...`);
|
|
607
544
|
await mergeAudioTracks(fullTempDir, audioFilenames);
|
|
608
|
-
console.log(`[generateAudio] Audio tracks merged successfully`);
|
|
609
|
-
} else {
|
|
610
|
-
console.warn(`[generateAudio] No audio files to merge!`);
|
|
611
545
|
}
|
|
612
546
|
return audioFilenames;
|
|
613
547
|
}
|
|
614
548
|
async function mergeMedia(outputFilename, outputDir, tempDir, format) {
|
|
615
|
-
console.log(`[mergeMedia] Starting media merge`);
|
|
616
|
-
console.log(`[mergeMedia] Output filename: ${outputFilename}`);
|
|
617
|
-
console.log(`[mergeMedia] Output dir: ${outputDir}`);
|
|
618
|
-
console.log(`[mergeMedia] Temp dir: ${tempDir}`);
|
|
619
|
-
console.log(`[mergeMedia] Format: ${format}`);
|
|
620
549
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
621
|
-
console.log(`[mergeMedia] Full temp dir: ${fullTempDir}`);
|
|
622
550
|
await makeSureFolderExists(outputDir);
|
|
623
551
|
await makeSureFolderExists(fullTempDir);
|
|
624
552
|
const audioWavPath = path5.join(fullTempDir, `audio.wav`);
|
|
625
553
|
const audioWavExists = fs2.existsSync(audioWavPath);
|
|
626
|
-
console.log(`[mergeMedia] Audio WAV exists: ${audioWavExists} (${audioWavPath})`);
|
|
627
554
|
const visualsPath = path5.join(fullTempDir, `visuals.${extensions[format]}`);
|
|
628
|
-
const visualsExists = fs2.existsSync(visualsPath);
|
|
629
|
-
console.log(`[mergeMedia] Visuals exist: ${visualsExists} (${visualsPath})`);
|
|
630
555
|
const outputPath = path5.join(outputDir, `${outputFilename}.${extensions[format]}`);
|
|
631
556
|
if (audioWavExists) {
|
|
632
|
-
console.log(`[mergeMedia] Merging audio and video...`);
|
|
633
557
|
await mergeAudioWithVideo(
|
|
634
558
|
audioWavPath,
|
|
635
559
|
visualsPath,
|
|
636
560
|
outputPath,
|
|
637
561
|
audioCodecs[format]
|
|
638
562
|
);
|
|
639
|
-
console.log(`[mergeMedia] Successfully merged audio and video to: ${outputPath}`);
|
|
640
563
|
} else {
|
|
641
|
-
console.log(`[mergeMedia] No audio found, copying video only...`);
|
|
642
564
|
await fs2.promises.copyFile(visualsPath, outputPath);
|
|
643
|
-
console.log(`[mergeMedia] Successfully copied video to: ${outputPath}`);
|
|
644
565
|
}
|
|
645
566
|
if (fullTempDir.endsWith("-undefined")) {
|
|
646
|
-
|
|
647
|
-
await fs2.promises.rm(fullTempDir, { recursive: true, force: true }).catch((err) => {
|
|
648
|
-
console.warn(`[mergeMedia] Failed to clean up temp directory:`, err);
|
|
567
|
+
await fs2.promises.rm(fullTempDir, { recursive: true, force: true }).catch(() => {
|
|
649
568
|
});
|
|
650
569
|
}
|
|
651
|
-
console.log(`[mergeMedia] Media merge completed`);
|
|
652
570
|
}
|
|
653
571
|
|
|
654
572
|
// src/video-frame-extractor.ts
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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 * as 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', (err: Error) => reject(err));\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 * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\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 ffmpeg = require('fluent-ffmpeg');\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 console.log(`[getAssetPlacement] Asset ${asset.key}:`);\n console.log(` - currentTime range: ${timeInfo.start} to ${timeInfo.end}`);\n console.log(` - durationInSeconds (from currentTime): ${asset.durationInSeconds}`);\n }\n // Recalculate the original duration based on frame count.\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n console.log(` - frame range: ${asset.startInVideo} to ${asset.endInVideo}`);\n console.log(` - duration (frames): ${asset.duration}`);\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 console.log(`[prepareAudio] Processing asset: ${asset.key}`);\n console.log(`[prepareAudio] Asset src: ${asset.src}`);\n console.log(`[prepareAudio] Asset type: ${asset.type}`);\n console.log(`[prepareAudio] Playback rate: ${asset.playbackRate}`);\n console.log(`[prepareAudio] Volume: ${asset.volume}`);\n \n // Construct the output path\n const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n console.log(`[prepareAudio] Output path: ${outputPath}`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n \n // Calculate duration from frames if durationInSeconds is 0 or suspiciously small\n let effectiveDurationInSeconds = asset.durationInSeconds;\n if (effectiveDurationInSeconds < 0.1) {\n // Fallback: use frame-based duration\n effectiveDurationInSeconds = asset.duration / fps;\n console.log(`[prepareAudio] WARNING: durationInSeconds was ${asset.durationInSeconds}, using frame-based duration: ${effectiveDurationInSeconds}s`);\n }\n \n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + effectiveDurationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n \n console.log(`[prepareAudio] Trim calculation:`);\n console.log(` - trimLeft: ${trimLeft}s`);\n console.log(` - effectiveDurationInSeconds: ${effectiveDurationInSeconds}s`);\n console.log(` - trimRight: ${trimRight}s`);\n console.log(` - padStart: ${padStart}ms`);\n \n const resolvedPath = resolvePath(outputDir, asset.src);\n console.log(`[prepareAudio] Resolved path: ${resolvedPath}`);\n \n const assetSampleRate = await getSampleRate(resolvedPath);\n console.log(`[prepareAudio] Sample rate: ${assetSampleRate}`);\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 console.log(`[prepareAudio] Atempo filters: ${atempoFilters.join(', ')}`);\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 console.log(`[prepareAudio] Audio filters: ${audioFilters}`);\n console.log(`[prepareAudio] Starting ffmpeg processing...`);\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 console.log(`[prepareAudio] Successfully processed audio for ${asset.key}`);\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`[prepareAudio] Error processing audio for asset key: ${asset.key}`, err);\n reject(err);\n })\n .save(outputPath);\n });\n\n console.log(`[prepareAudio] Audio file saved: ${outputPath}`);\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n console.log(`[mergeAudioTracks] Starting merge of ${audioFilenames.length} tracks`);\n audioFilenames.forEach((filename, idx) => {\n console.log(`[mergeAudioTracks] Track ${idx + 1}: ${filename}`);\n });\n \n const outputPath = path.join(tempDir, `audio.wav`);\n console.log(`[mergeAudioTracks] Output path: ${outputPath}`);\n \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 const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;\n console.log(`[mergeAudioTracks] Complex filter: ${complexFilter}`);\n\n command\n .complexFilter([complexFilter])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => {\n console.log(`[mergeAudioTracks] Successfully merged audio tracks to: ${outputPath}`);\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`[mergeAudioTracks] Error merging audio tracks:`, err);\n reject(err);\n })\n .save(outputPath);\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 console.log(`[generateAudio] Starting audio generation`);\n console.log(`[generateAudio] Output dir: ${outputDir}`);\n console.log(`[generateAudio] Temp dir: ${tempDir}`);\n console.log(`[generateAudio] Start frame: ${startFrame}, End frame: ${endFrame}`);\n console.log(`[generateAudio] FPS: ${fps}`);\n console.log(`[generateAudio] Total frames: ${assets.length}`);\n \n const fullTempDir = path.join(os.tmpdir(), tempDir);\n console.log(`[generateAudio] Full temp dir: ${fullTempDir}`);\n \n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const assetPositions = getAssetPlacement(assets);\n console.log(`[generateAudio] Found ${assetPositions.length} unique assets`);\n \n assetPositions.forEach((asset, idx) => {\n console.log(`[generateAudio] Asset ${idx + 1}: key=${asset.key}, src=${asset.src}, type=${asset.type}, playbackRate=${asset.playbackRate}, volume=${asset.volume}`);\n });\n \n const audioFilenames: string[] = [];\n\n for (const asset of assetPositions) {\n console.log(`[generateAudio] Processing asset: ${asset.key}`);\n \n let hasAudioStream = true;\n if (asset.type !== 'audio') {\n const resolvedPath = resolvePath(outputDir, asset.src);\n console.log(`[generateAudio] Checking for audio stream in: ${resolvedPath}`);\n hasAudioStream = await checkForAudioStream(resolvedPath);\n console.log(`[generateAudio] Has audio stream: ${hasAudioStream}`);\n }\n\n if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {\n console.log(`[generateAudio] Asset ${asset.key} will be processed (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);\n const filename = await prepareAudio(\n outputDir,\n fullTempDir,\n asset,\n startFrame,\n endFrame,\n fps,\n );\n audioFilenames.push(filename);\n console.log(`[generateAudio] Added audio file to list: ${filename}`);\n } else {\n console.log(`[generateAudio] Skipping asset ${asset.key} (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);\n }\n }\n\n console.log(`[generateAudio] Total audio files to merge: ${audioFilenames.length}`);\n\n if (audioFilenames.length > 0) {\n console.log(`[generateAudio] Merging ${audioFilenames.length} audio tracks...`);\n await mergeAudioTracks(fullTempDir, audioFilenames);\n console.log(`[generateAudio] Audio tracks merged successfully`);\n } else {\n console.warn(`[generateAudio] No audio files to merge!`);\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 console.log(`[mergeMedia] Starting media merge`);\n console.log(`[mergeMedia] Output filename: ${outputFilename}`);\n console.log(`[mergeMedia] Output dir: ${outputDir}`);\n console.log(`[mergeMedia] Temp dir: ${tempDir}`);\n console.log(`[mergeMedia] Format: ${format}`);\n \n const fullTempDir = path.join(os.tmpdir(), tempDir);\n console.log(`[mergeMedia] Full temp dir: ${fullTempDir}`);\n \n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const audioWavPath = path.join(fullTempDir, `audio.wav`);\n const audioWavExists = fs.existsSync(audioWavPath);\n console.log(`[mergeMedia] Audio WAV exists: ${audioWavExists} (${audioWavPath})`);\n \n const visualsPath = path.join(fullTempDir, `visuals.${extensions[format]}`);\n const visualsExists = fs.existsSync(visualsPath);\n console.log(`[mergeMedia] Visuals exist: ${visualsExists} (${visualsPath})`);\n \n const outputPath = path.join(outputDir, `${outputFilename}.${extensions[format]}`);\n \n if (audioWavExists) {\n console.log(`[mergeMedia] Merging audio and video...`);\n await mergeAudioWithVideo(\n audioWavPath,\n visualsPath,\n outputPath,\n audioCodecs[format],\n );\n console.log(`[mergeMedia] Successfully merged audio and video to: ${outputPath}`);\n } else {\n console.log(`[mergeMedia] No audio found, copying video only...`);\n await fs.promises.copyFile(visualsPath, outputPath);\n console.log(`[mergeMedia] Successfully copied video to: ${outputPath}`);\n }\n \n if (fullTempDir.endsWith('-undefined')) {\n console.log(`[mergeMedia] Cleaning up temp directory: ${fullTempDir}`);\n await fs.promises\n .rm(fullTempDir, {recursive: true, force: true})\n .catch((err) => {\n console.warn(`[mergeMedia] Failed to clean up temp directory:`, err);\n });\n }\n \n console.log(`[mergeMedia] Media merge completed`);\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","fs","os","path","os","path","uuidv4","ffmpeg","import_telemetry","fs","os","path","import_uuid","ffmpeg","uuidv4","err","process","image"]}
|
|
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 * as 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', (err: Error) => reject(err));\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 * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\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 ffmpeg = require('fluent-ffmpeg');\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 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 const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n let effectiveDurationInSeconds = asset.durationInSeconds;\n if (effectiveDurationInSeconds < 0.1) {\n effectiveDurationInSeconds = asset.duration / fps;\n }\n\n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + effectiveDurationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n\n const resolvedPath = resolvePath(outputDir, asset.src);\n const assetSampleRate = await getSampleRate(resolvedPath);\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);\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', () => resolve())\n .on('error', (err: Error) => reject(err))\n .save(outputPath);\n });\n\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n const outputPath = path.join(tempDir, `audio.wav`);\n return new Promise((resolve, reject) => {\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const command = ffmpeg();\n audioFilenames.forEach((filename) => command.input(filename));\n const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;\n command\n .complexFilter([complexFilter])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => resolve())\n .on('error', (err: Error) => reject(err))\n .save(outputPath);\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 const resolvedPath = resolvePath(outputDir, asset.src);\n hasAudioStream = await checkForAudioStream(resolvedPath);\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 audioWavPath = path.join(fullTempDir, `audio.wav`);\n const audioWavExists = fs.existsSync(audioWavPath);\n const visualsPath = path.join(fullTempDir, `visuals.${extensions[format]}`);\n const outputPath = path.join(outputDir, `${outputFilename}.${extensions[format]}`);\n\n if (audioWavExists) {\n await mergeAudioWithVideo(\n audioWavPath,\n visualsPath,\n outputPath,\n audioCodecs[format],\n );\n } else {\n await fs.promises.copyFile(visualsPath, outputPath);\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,uBAAmC;AACnC,aAAwB;AACxB,SAAoB;AACpB,IAAAA,QAAsB;;;ACRtB,oBAAuB;AAEhB,IAAM,cAAN,cAA0B,uBAAS;AAAA,EAAnC;AAAA;AACL,SAAQ,QAAuB;AAC/B,SAAQ,UAAU;AAAA;AAAA,EAEX,UAAU,OAAsB;AACrC,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,MAAM;AAAA,EACb;AAAA;AAAA,EAGgB,QAAQ;AACtB,QAAI,KAAK,SAAS;AAChB,WAAK,UAAU;AACf,WAAK,KAAK,KAAK,KAAK;AAAA,IACtB;AAAA,EACF;AACF;;;ACnBA,sBAAiC;AACjC,uBAAkC;AAElC,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUA,IAAM,qBAAN,MAAyB;AAAA,EAKhB,cAAc;AACnB,SAAK,aAA6B;AAClC,SAAK,cAA+B;AAGpC,QAAI,QAAQ,IAAI,aAAa;AAC3B,WAAK,aAAa,QAAQ,IAAI;AAAA,IAChC;AAGA,QAAI,QAAQ,IAAI,cAAc;AAC5B,WAAK,cAAc,QAAQ,IAAI;AAAA,IACjC;AAEA,SAAK,WAAW;AAGhB,QACE,QAAQ,IAAI,oBACZ,gBAAgB,SAAS,QAAQ,IAAI,gBAA4B,GACjE;AACA,WAAK,WAAW,QAAQ,IAAI;AAAA,IAC9B;AAAA,EACF;AAAA,EAEO,gBAAgB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,cAAc,YAAoB;AACvC,SAAK,aAAa;AAAA,EACpB;AAAA,EAEO,iBAAiB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,eAAe,aAAqB;AACzC,SAAK,cAAc;AAAA,EACrB;AAAA,EAEO,cAAc;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,YAAY,UAAoB;AACrC,SAAK,WAAW;AAAA,EAClB;AACF;AAEO,IAAM,iBAAiB,IAAI,mBAAmB;;;AF5DrD,IAAM,eAAgE;AAAA,EACpE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AACV;AAEO,IAAM,aAA8D;AAAA,EACzE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AACV;AAKO,IAAM,uBAAN,MAA2B;AAAA,EAQzB,YAAY,UAAkC;AACnD,QAAI,SAAS,SAAS,SAAS,sBAAsB;AACnD,YAAM,IAAI,MAAM,kBAAkB;AAAA,IACpC;AAEA,SAAK,WAAW;AAChB,SAAK,SAAS,SAAS,SAAS,QAAQ;AAExC,SAAK,YAAiB;AAAA,MACjB,UAAO;AAAA,MACV,SAAS,KAAK,SAAS,IAAI,IAAI,SAAS,cAAc;AAAA,IACxD;AACA,SAAK,SAAS,IAAI,YAAY;AAE9B,IAAO,qBAAc,eAAe,cAAc,CAAC;AACnD,SAAK,UAAU,OAAO;AAGtB,SAAK,QACF,MAAM,KAAK,MAAM,EACjB,YAAY,YAAY,EACxB,SAAS,SAAS,GAAG;AAGxB,UAAM,OAAO;AAAA,MACX,GAAG,KAAK,MAAM,SAAS,KAAK,IAAI,SAAS,eAAe;AAAA,MACxD,GAAG,KAAK,MAAM,SAAS,KAAK,IAAI,SAAS,eAAe;AAAA,IAC1D;AACA,SAAK,QACF,OAAY,WAAK,KAAK,WAAW,WAAW,WAAW,KAAK,MAAM,CAAC,EAAE,CAAC,EACtE,cAAc,CAAC,YAAY,aAAa,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,EACpE,UAAU,SAAS,GAAG,EACtB,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE;AAE7B,QAAI,KAAK,WAAW,UAAU;AAC5B,WAAK,QAAQ,cAAc,CAAC,kBAAkB,iBAAiB,CAAC;AAAA,IAClE;AAEA,SAAK,QAAQ,cAAc,CAAC,sBAAsB,CAAC;AACnD,SAAK,UAAU,IAAI,QAAc,CAAC,SAAS,WAAW;AACpD,WAAK,QAAQ,GAAG,OAAO,MAAM,QAAQ,CAAC,EAAE,GAAG,SAAS,CAAC,QAAe,OAAO,GAAG,CAAC;AAAA,IACjF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,QAAQ;AACnB,SAAK,QAAQ,IAAI;AAAA,EACnB;AAAA,EAEA,MAAa,YAAY,EAAC,KAAI,GAAmB;AAC/C,UAAM,aAAa,KAAK,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;AACnD,SAAK,OAAO,UAAU,OAAO,KAAK,YAAY,QAAQ,CAAC;AAAA,EACzD;AAAA,EAEA,MAAa,IAAI,QAAwB;AACvC,SAAK,OAAO,UAAU,IAAI;AAC1B,QAAI,WAAW,GAAG;AAChB,UAAI;AACF,aAAK,QAAQ,KAAK,SAAS;AAC3B,cAAM,KAAK;AAAA,MACb,SAAS,KAAK;AACZ,wCAAU,2BAAU,OAAO,EAAC,SAAU,IAAc,QAAO,CAAC;AAAA,MAC9D;AAAA,IACF,OAAO;AACL,YAAM,KAAK;AAAA,IACb;AAAA,EACF;AAAA,EAEA,MAAa,OAAO;AAClB,QAAI;AACF,WAAK,QAAQ,KAAK,SAAS;AAC3B,YAAM,KAAK;AAAA,IACb,SAAS,GAAG;AACV;AAAA,IACF;AAAA,EACF;AACF;;;AGnHA,IAAAC,MAAoB;AACpB,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;;;ACHtB,SAAoB;AACpB,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,kBAA2B;AAK3B,IAAM,eAAe,QAAQ,eAAe;AAIrC,SAAS,YAAY,QAAgB,WAAmB;AAC7D,MAAI;AACJ,MACE,UAAU,WAAW,SAAS,KAC9B,UAAU,WAAW,UAAU,KAC/B,UAAU,WAAW,OAAO,GAC5B;AACA,mBAAe;AAAA,EACjB,OAAO;AACL,mBAAoB,WAAK,QAAQ,aAAa,SAAS;AAAA,EACzD;AACA,SAAO;AACT;AAEA,eAAsB,qBAAqB,YAAoB;AAC7D,MACE,MAAS,YACN,OAAO,UAAU,EACjB,KAAK,MAAM,KAAK,EAChB,MAAM,MAAM,IAAI,GACnB;AACA,UAAS,YAAS,MAAM,YAAY,EAAC,WAAW,KAAI,CAAC;AAAA,EACvD;AACF;AAEA,eAAsB,iBACpB,OACA,YACe;AACf,QAAM,WAAgB,WAAQ,WAAO,GAAG,OAAG,YAAAC,IAAO,CAAC,MAAM;AACzD,QAAM,cAAc,MACjB,IAAI,UAAQ,SAAS,KAAK,QAAQ,MAAM,KAAK,CAAC,GAAG,EACjD,KAAK,IAAI;AACZ,QAAS,YAAS,UAAU,UAAU,WAAW;AAEjD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,cAAc,eAAe,cAAc,CAAC;AACzD,UAAM,gBAAgB,aAAa;AAEnC,kBACG,MAAM,QAAQ,EACd,aAAa;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,EACA,cAAc,CAAC,SAAS,CAAC,EACzB,GAAG,SAAS,CAAC,QAAe;AAC3B,cAAQ,MAAM,UAAU,GAAG;AAC3B,MAAG,YAAS,OAAO,QAAQ,EAAE,MAAM,QAAQ,KAAK;AAChD,aAAO,GAAG;AAAA,IACZ,CAAC,EACA,GAAG,OAAO,MAAM;AACf,MAAG,YAAS,OAAO,QAAQ,EAAE,MAAM,QAAQ,KAAK;AAChD,cAAQ;AAAA,IACV,CAAC,EACA,KAAK,UAAU;AAAA,EACpB,CAAC;AACH;AAEA,eAAsB,sBACpB,UACA,UACA;AACA,eAAa,cAAc,eAAe,cAAc,CAAC;AAEzD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,EACV,SAAS,8CAA8C,IAAK,EAAE,EAC9D,YAAY,OAAO,EACnB,SAAS,QAAQ,EACjB,GAAG,OAAO,MAAM;AACf,cAAQ,QAAQ;AAAA,IAClB,CAAC,EACA,GAAG,SAAS,CAAC,QAAe;AAC3B,cAAQ,MAAM,qCAAqC,GAAG;AACtD,aAAO,GAAG;AAAA,IACZ,CAAC,EACA,KAAK,QAAQ;AAAA,EAClB,CAAC;AACH;AAEA,eAAsB,iBAAiB,UAAmC;AACtE,eAAa,eAAe,eAAe,eAAe,CAAC;AAE7D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,QAAQ,UAAU,CAAC,KAAmB,aAAkB;AACnE,UAAI,KAAK;AACP,eAAO,GAAG;AACV;AAAA,MACF;AACA,YAAM,WAAW,SAAS,OAAO;AACjC,UAAI,UAAU;AACZ,gBAAQ,QAAQ;AAAA,MAClB,OAAO;AACL,eAAO,IAAI,MAAM,qCAAqC,CAAC;AAAA,MACzD;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,mBACpB,UAC0C;AAC1C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,QAAQ,UAAU,CAAC,KAAmB,aAAkB;AACnE,UAAI,KAAK;AACP,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,eAAO,IAAI,MAAM,gCAAgC,CAAC;AAClD;AAAA,MACF;AAEA,YAAM,cAAc,SAAS,QAAQ;AAAA,QACnC,CAAC,WAAgB,OAAO,eAAe;AAAA,MACzC;AACA,UAAI,eAAe,YAAY,SAAS,YAAY,QAAQ;AAC1D,gBAAQ;AAAA,UACN,OAAO,YAAY;AAAA,UACnB,QAAQ,YAAY;AAAA,QACtB,CAAC;AAAA,MACH;AACA,aAAO,IAAI,MAAM,6CAA6C,CAAC;AAAA,IACjE,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,cAAc,UAAoC;AACtE,MAAI;AACF,UAAS,YAAS,OAAO,UAAa,aAAU,IAAI;AACpD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,oBACpB,WACA,WACA,YACA,aAAyB,OACV;AACf,eAAa,cAAc,eAAe,cAAc,CAAC;AAEzD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,EACV,MAAM,SAAS,EACf,MAAM,SAAS,EACf,cAAc;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,EACA,GAAG,OAAO,MAAM;AACf,cAAQ;AAAA,IACV,CAAC,EACA,GAAG,SAAS,CAAC,QAAe;AAC3B,cAAQ,MAAM,kCAAkC,IAAI,OAAO,EAAE;AAC7D,aAAO,GAAG;AAAA,IACZ,CAAC,EACA,KAAK,UAAU;AAAA,EACpB,CAAC;AACH;AAEA,eAAsB,oBAAoB,MAAgC;AACtE,eAAa,eAAe,eAAe,eAAe,CAAC;AAE7D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,QAAQ,MAAM,CAAC,KAAmB,aAAkB;AAC/D,UAAI,KAAK;AACP,gBAAQ,MAAM,2CAA2C,IAAI,IAAI,GAAG;AACpE,eAAO,GAAG;AACV;AAAA,MACF;AAEA,YAAM,eAAe,SAAS,QAAQ;AAAA,QACpC,CAAC,MAAW,EAAE,eAAe;AAAA,MAC/B;AACA,cAAQ,aAAa,SAAS,CAAC;AAAA,IACjC,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,cAAc,UAAmC;AACnE,eAAa,eAAe,eAAe,eAAe,CAAC;AAE7D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,QAAQ,UAAU,CAAC,KAAmB,aAAkB;AACnE,UAAI,KAAK;AACP,eAAO,GAAG;AACV;AAAA,MACF;AACA,YAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,MAAW,EAAE,eAAe,OAAO;AAC9E,UAAI,eAAe,YAAY,aAAa;AAC1C,gBAAQ,YAAY,WAAW;AAAA,MACjC,OAAO;AACL,eAAO,IAAI,MAAM,uBAAuB,CAAC;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,cAAc,UAAkB;AAClD,eAAa,eAAe,eAAe,eAAe,CAAC;AAE7D,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,iBAAa,QAAQ,UAAU,CAAC,KAAmB,aAAkB;AACnE,UAAI,KAAK;AACP,eAAO,GAAG;AACV;AAAA,MACF;AACA,YAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,MAAW,EAAE,eAAe,OAAO;AAC9E,UAAI,eAAe,YAAY,YAAY;AACzC,gBAAQ,YAAY,UAAU;AAAA,MAChC,OAAO;AACL,eAAO,IAAI,MAAM,uBAAuB,CAAC;AAAA,MAC3C;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,iBACpB,UACyD;AACvD,eAAa,eAAe,eAAe,eAAe,CAAC;AAE7D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,iBAAa,QAAQ,UAAU,CAAC,KAAmB,aAAkB;AACnE,UAAI,KAAK;AACP,eAAO,GAAG;AACV;AAAA,MACF;AACA,YAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,MAAW,EAAE,eAAe,OAAO;AAC9E,UACE,eACA,YAAY,cACZ,YAAY,SACZ,YAAY,QACZ;AACA,gBAAQ;AAAA,UACN,OAAO,YAAY;AAAA,UACnB,OAAO,YAAY;AAAA,UACnB,QAAQ,YAAY;AAAA,QACtB,CAAC;AAAA,MACH,OAAO;AACL,eAAO,IAAI,MAAM,+CAA+C,CAAC;AAAA,MACnE;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;;;ADhQA,IAAMC,UAAS,QAAQ,eAAe;AAY/B,IAAM,cACX;AAAA,EACE,KAAK;AAAA,EACL,MAAM;AAAA,EACN,QAAQ;AACV;AAeF,IAAM,cAAc;AAEpB,SAAS,kBAAkB,QAAqC;AAC9D,QAAM,SAAuB,CAAC;AAG9B,QAAM,eAAe,oBAAI,IAA0C;AAEnE,WAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;AAClD,eAAW,SAAS,OAAO,KAAK,GAAG;AACjC,UAAI,CAAC,aAAa,IAAI,MAAM,GAAG,GAAG;AAEhC,qBAAa,IAAI,MAAM,KAAK;AAAA,UAC1B,OAAO,MAAM;AAAA,UACb,KAAK,MAAM;AAAA,QACb,CAAC;AACD,eAAO,KAAK;AAAA,UACV,KAAK,MAAM;AAAA,UACX,KAAK,MAAM;AAAA,UACX,MAAM,MAAM;AAAA,UACZ,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,UAAU;AAAA;AAAA,UACV,mBAAmB;AAAA;AAAA,UACnB,cAAc,MAAM;AAAA,UACpB,QAAQ,MAAM;AAAA,UACd,mBAAmB,MAAM;AAAA,QAC3B,CAAC;AAAA,MACH,OAAO;AAEL,cAAM,WAAW,aAAa,IAAI,MAAM,GAAG;AAC3C,YAAI,UAAU;AACZ,mBAAS,MAAM,MAAM;AACrB,uBAAa,IAAI,MAAM,KAAK,QAAQ;AAAA,QACtC;AAEA,cAAM,gBAAgB,OAAO,KAAK,OAAK,EAAE,QAAQ,MAAM,GAAG;AAC1D,YAAI,eAAe;AACjB,wBAAc,aAAa;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,SAAO,QAAQ,WAAS;AACtB,UAAM,WAAW,aAAa,IAAI,MAAM,GAAG;AAC3C,QAAI,UAAU;AAEZ,YAAM,qBACH,SAAS,MAAM,SAAS,SAAS,MAAM;AAAA,IAC5C;AACA,UAAM,WAAW,MAAM,aAAa,MAAM,eAAe;AAAA,EAC3D,CAAC;AAED,SAAO;AACT;AAEA,SAAS,uBAAuB,cAAsB;AACpD,QAAM,gBAAgB,CAAC;AAGvB,MAAI,OAAO;AACX,SAAO,OAAO,KAAO;AACnB,kBAAc,KAAK,cAAc;AACjC,YAAQ;AAAA,EACV;AAEA,MAAI,OAAO,GAAK;AACd,kBAAc,KAAK,UAAU,IAAI,EAAE;AAAA,EACrC;AAGA,SAAO;AACP,SAAO,OAAO,KAAK;AACjB,kBAAc,KAAK,YAAY;AAC/B,YAAQ;AAAA,EACV;AAEA,MAAI,OAAO,GAAK;AACd,kBAAc,KAAK,UAAU,IAAI,EAAE;AAAA,EACrC;AAEA,SAAO;AACT;AACA,eAAe,aACb,WACA,SACA,OACA,YACA,UACA,KACiB;AACjB,QAAM,eAAe,MAAM,IAAI,QAAQ,WAAW,GAAG;AACrD,QAAM,aAAkB,WAAK,SAAS,GAAG,YAAY,MAAM;AAE3D,QAAM,WAAW,MAAM,oBAAoB,MAAM;AACjD,MAAI,6BAA6B,MAAM;AACvC,MAAI,6BAA6B,KAAK;AACpC,iCAA6B,MAAM,WAAW;AAAA,EAChD;AAEA,QAAM,YACJ,IAAI,MACJ,KAAK;AAAA,IACH,WAAW;AAAA,IACX,YAAY,WAAW,cAAc;AAAA,EACvC;AACF,QAAM,WAAY,MAAM,eAAe,MAAO;AAE9C,QAAM,eAAe,YAAY,WAAW,MAAM,GAAG;AACrD,QAAM,kBAAkB,MAAM,cAAc,YAAY;AAExD,QAAM,SAAS,KAAK;AAAA,IAClB;AAAA,IACC,mBAAmB,WAAW,aAAa,KAAM,MAC/C,kBAAkB,MAAM,WAAY,MACpC,kBAAkB,WAAY;AAAA,EACnC;AAEA,QAAM,gBAAgB,uBAAuB,MAAM,YAAY;AAC/D,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,eAAe;AAAA,MACnB,GAAG;AAAA,MACH,eAAe,QAAQ,QAAQ,SAAS;AAAA,MACxC,gBAAgB,MAAM;AAAA,MACtB,UAAU,QAAQ,IAAI,QAAQ,IAAI,QAAQ;AAAA,MAC1C,UAAU,MAAM,MAAM;AAAA,IACxB,EAAE,KAAK,GAAG;AAEV,IAAAA,QAAO,cAAc,eAAe,cAAc,CAAC;AACnD,IAAAA,QAAO,YAAY,EAChB,cAAc,CAAC,EACf,WAAW,WAAW,EACtB,eAAe,WAAW,EAC1B,cAAc,CAAC,OAAO,YAAY,CAAC,EACnC,GAAG,OAAO,MAAM,QAAQ,CAAC,EACzB,GAAG,SAAS,CAAC,QAAe,OAAO,GAAG,CAAC,EACvC,KAAK,UAAU;AAAA,EACpB,CAAC;AAED,SAAO;AACT;AAEA,eAAe,iBACb,SACA,gBACe;AACf,QAAM,aAAkB,WAAK,SAAS,WAAW;AACjD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,IAAAA,QAAO,cAAc,eAAe,cAAc,CAAC;AACnD,UAAM,UAAUA,QAAO;AACvB,mBAAe,QAAQ,CAAC,aAAa,QAAQ,MAAM,QAAQ,CAAC;AAC5D,UAAM,gBAAgB,eAAe,eAAe,MAAM,4BAA4B,eAAe,MAAM;AAC3G,YACG,cAAc,CAAC,aAAa,CAAC,EAC7B,cAAc,CAAC,QAAQ,WAAW,CAAC,EACnC,GAAG,OAAO,MAAM,QAAQ,CAAC,EACzB,GAAG,SAAS,CAAC,QAAe,OAAO,GAAG,CAAC,EACvC,KAAK,UAAU;AAAA,EACpB,CAAC;AACH;AAEA,eAAsB,cAAc;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;AACD,QAAM,cAAmB,WAAQ,WAAO,GAAG,OAAO;AAClD,QAAM,qBAAqB,SAAS;AACpC,QAAM,qBAAqB,WAAW;AAEtC,QAAM,iBAAiB,kBAAkB,MAAM;AAC/C,QAAM,iBAA2B,CAAC;AAElC,aAAW,SAAS,gBAAgB;AAClC,QAAI,iBAAiB;AACrB,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,eAAe,YAAY,WAAW,MAAM,GAAG;AACrD,uBAAiB,MAAM,oBAAoB,YAAY;AAAA,IACzD;AAEA,QAAI,MAAM,iBAAiB,KAAK,MAAM,WAAW,KAAK,gBAAgB;AACpE,YAAM,WAAW,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,qBAAe,KAAK,QAAQ;AAAA,IAC9B;AAAA,EACF;AAEA,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,iBAAiB,aAAa,cAAc;AAAA,EACpD;AAEA,SAAO;AACT;AAEA,eAAsB,WACpB,gBACA,WACA,SACA,QACA;AACA,QAAM,cAAmB,WAAQ,WAAO,GAAG,OAAO;AAClD,QAAM,qBAAqB,SAAS;AACpC,QAAM,qBAAqB,WAAW;AAEtC,QAAM,eAAoB,WAAK,aAAa,WAAW;AACvD,QAAM,iBAAoB,eAAW,YAAY;AACjD,QAAM,cAAmB,WAAK,aAAa,WAAW,WAAW,MAAM,CAAC,EAAE;AAC1E,QAAM,aAAkB,WAAK,WAAW,GAAG,cAAc,IAAI,WAAW,MAAM,CAAC,EAAE;AAEjF,MAAI,gBAAgB;AAClB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,MAAM;AAAA,IACpB;AAAA,EACF,OAAO;AACL,UAAS,aAAS,SAAS,aAAa,UAAU;AAAA,EACpD;AAEA,MAAI,YAAY,SAAS,YAAY,GAAG;AACtC,UAAS,aACN,GAAG,aAAa,EAAC,WAAW,MAAM,OAAO,KAAI,CAAC,EAC9C,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB;AACF;;;AE1RA,IAAAC,oBAAmC;AAInC,IAAAC,MAAoB;AACpB,IAAAC,MAAoB;AACpB,IAAAC,QAAsB;AACtB,IAAAC,eAA2B;AAL3B,IAAMC,UAAS,QAAQ,eAAe;AAc/B,IAAM,uBAAN,MAAM,qBAAoB;AAAA,EAmCxB,YACL,UACA,WACA,KACA,UACA;AArCF,SAAiB,aAAa,eAAe,cAAc;AAM3D,SAAQ,SAAiB,OAAO,MAAM,CAAC;AACvC,SAAQ,eAAuB;AAG/B;AAAA,SAAQ,eAAyB,CAAC;AAClC,SAAQ,YAA2B;AAOnC,SAAQ,kBAA0B;AAElC,SAAQ,QAAgB;AACxB,SAAQ,SAAiB;AACzB,SAAQ,YAAoB;AAC5B,SAAQ,QAAuB;AAC/B,SAAQ,UAA4C;AACpD,SAAQ,aAAsB;AAa5B,SAAK,QAAQ;AACb,SAAK,WAAW;AAChB,SAAK,qBAAqB,qBAAoB,mBAAmB;AAAA,MAC/D;AAAA,IACF,GAAG;AACH,SAAK,kBAAkB,qBAAoB,mBAAmB,IAAI,QAAQ,GACtE;AAEJ,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,SAAS,KAAK,WAAW,KAAK,SAAS;AAC5C,SAAK,MAAM;AAEX,qBAAiB,KAAK,kBAAkB,EAAE,KAAK,cAAY;AACzD,WAAK,QAAQ,SAAS;AACtB,WAAK,SAAS,SAAS;AACvB,WAAK,YAAY,KAAK,QAAQ,KAAK,SAAS;AAC5C,WAAK,SAAS,OAAO,MAAM,KAAK,SAAS;AACzC,WAAK,QAAQ,SAAS;AAEtB,UAAI,KAAK,aAAa,KAAK,UAAU;AACnC,aAAK,UAAU,KAAK;AAAA,UAClB,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AACA;AAAA,MACF;AAEA,WAAK,UAAU,KAAK;AAAA,QAClB,KAAK,YAAY,KAAK;AAAA,QACtB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,OAAc,mBACZ,KACA,WACA,SACA;AACA,UAAM,YAAiB,WAAQ,WAAO,GAAG,sBAAsB;AAC/D,QAAI,CAAI,eAAW,SAAS,GAAG;AAC7B,MAAG,cAAU,WAAW,EAAC,WAAW,KAAI,CAAC;AAAA,IAC3C;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,MAAAA,QAAO,QAAQ,KAAK,CAAC,KAAmB,aAAkB;AACxD,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AAEA,cAAM,SAAS,SAAS,OAAO,aAAa,MAAM,GAAG,EAAE,EAAE,KAAK;AAC9D,cAAM,iBAAiB,aAAS,aAAAC,IAAO,CAAC,IAAI,MAAM;AAClD,cAAM,aAAkB,WAAK,WAAW,cAAc;AACtD,cAAM,qBAAqB;AAE3B,cAAM,oBAAoB,KAAK,IAAI,YAAY,oBAAoB,CAAC;AAEpE,QAAAD,QAAO,GAAG,EACP,cAAc,eAAe,cAAc,CAAC,EAC5C,aAAa;AAAA,UACZ,OAAO,iBAAiB;AAAA,UACxB,OAAO,UAAU,kBAAkB;AAAA,QACrC,CAAC,EACA,cAAc,CAAC,SAAS,CAAC,EACzB,OAAO,UAAU,EACjB,GAAG,OAAO,MAAM;AACf,eAAK,mBAAmB,IAAI,KAAK;AAAA,YAC/B,WAAW;AAAA,YACX,iBAAiB;AAAA,UACnB,CAAC;AACD,kBAAQ,UAAU;AAAA,QACpB,CAAC,EACA,GAAG,SAAS,CAACE,SAAe,OAAOA,IAAG,CAAC,EACvC,IAAI;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEO,UAAU;AACf,WAAO,KAAK,YAAY,KAAK,kBAAkB,KAAK;AAAA,EACtD;AAAA,EAEO,cAAc;AACnB,WAAO,KAAK,aAAa,KAAK,kBAAkB,KAAK,KAAK;AAAA,EAC5D;AAAA,EAEO,eAAe;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,WAAW;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,YAAY;AACjB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,WAAW,WAAmB;AACpC,WAAO,KAAK;AAAA,MACV,YAAY,qBAAoB;AAAA,MAChC,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEQ,QACN,OACA,OACA,KACmD;AACnD,UAAM,eAAe,CAAC;AACtB,UAAM,gBAAgB,CAAC;AAEvB,iBAAa,KAAK,aAAa,eAAe,YAAY,CAAC;AAE3D,QAAI,OAAO;AACT,mBAAa;AAAA,QACX,GAAG,CAAC,OAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,OAAO,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,MAC5D;AAAA,IACF;AAEA,QAAI,UAAU,OAAO;AACnB,mBAAa,KAAK,WAAW,YAAY;AAAA,IAC3C;AAEA,QAAI,KAAK;AACP,oBAAc,KAAK,OAAO,WAAW,GAAG,EAAE;AAAA,IAC5C;AAEA,QAAI,CAAC,OAAO;AACV,oBAAc,KAAK,YAAY,GAAG;AAAA,IACpC;AAEA,kBAAc,KAAK,MAAM,UAAU;AACnC,kBAAc,KAAK,YAAY,MAAM;AAErC,WAAO,EAAC,cAAc,cAAa;AAAA,EACrC;AAAA,EAEQ,oBACN,WACA,QACA,UACA,KACA,OAC2B;AAC3B,UAAM,EAAC,cAAc,cAAa,IAAI,KAAK;AAAA,MACzC;AAAA,MACA,CAAC,WAAW,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,UAAMC,WAAUH,QAAO,QAAQ,EAC5B,cAAc,KAAK,UAAU,EAC7B,aAAa,YAAY,EACzB,cAAc,aAAa,EAC3B,GAAG,OAAO,MAAM;AACf,WAAK,YAAY,CAAC;AAAA,IACpB,CAAC,EACA,GAAG,SAAS,CAAC,QAAe;AAC3B,WAAK,YAAY,GAAG;AAAA,IACtB,CAAC,EACA,GAAG,UAAU,CAAC,eAAuB;AACpC,cAAQ,IAAI,UAAU;AAAA,IACxB,CAAC,EACA,GAAG,UAAU,CAAC,eAAuB;AACpC,cAAQ,IAAI,UAAU;AAAA,IACxB,CAAC;AAEH,UAAM,WAAWG,SAAQ,KAAK;AAC9B,aAAS,GAAG,QAAQ,CAAC,SAAiB;AACpC,WAAK,YAAY,IAAI;AAAA,IACvB,CAAC;AAED,WAAOA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,uCACN,UACA,OAC2B;AAC3B,UAAM,EAAC,cAAc,cAAa,IAAI,KAAK;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAMA,WAAUH,QAAO,QAAQ,EAC5B,cAAc,KAAK,UAAU,EAC7B,aAAa,YAAY,EACzB,cAAc,aAAa,EAC3B,GAAG,OAAO,MAAM;AACf,WAAK,YAAY,CAAC;AAAA,IACpB,CAAC,EACA,GAAG,SAAS,CAAC,QAAe;AAC3B,WAAK,YAAY,GAAG;AAAA,IACtB,CAAC,EACA,GAAG,UAAU,CAAC,eAAuB;AACpC,cAAQ,IAAI,UAAU;AAAA,IACxB,CAAC,EACA,GAAG,UAAU,CAAC,eAAuB;AACpC,cAAQ,IAAI,UAAU;AAAA,IACxB,CAAC;AAEH,UAAM,WAAWG,SAAQ,KAAK;AAC9B,aAAS,GAAG,QAAQ,CAAC,SAAiB;AACpC,WAAK,YAAY,IAAI;AAAA,IACvB,CAAC;AAED,WAAOA;AAAA,EACT;AAAA,EAEQ,YAAY,MAAc;AAChC,QAAI,aAAa;AAEjB,WAAO,aAAa,KAAK,QAAQ;AAC/B,YAAM,iBAAiB,KAAK,YAAY,KAAK;AAC7C,YAAM,YAAY,KAAK,IAAI,gBAAgB,KAAK,SAAS,UAAU;AAEnE,WAAK;AAAA,QACH,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,aAAa;AAAA,MACf;AACA,WAAK,gBAAgB;AACrB,oBAAc;AAGd,UAAI,KAAK,iBAAiB,KAAK,WAAW;AACxC,aAAK,aAAa,KAAK,OAAO,KAAK,KAAK,MAAoB,CAAC;AAC7D,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAa,WAAW;AACtB,QAAI,KAAK,aAAa,QAAQ;AAC5B,YAAMC,SAAQ,KAAK,aAAa,MAAM;AACtC,WAAK;AACL,WAAK,YAAYA;AACjB,aAAOA;AAAA,IACT;AAEA,QAAI,KAAK,UAAU,SAAS;AAC1B,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AAGA,QAAI,KAAK,UAAU,UAAU,KAAK,UAAU,KAAK,UAAU;AACzD,aAAO,KAAK;AAAA,IACd;AAGA,QAAI,KAAK,UAAU,QAAQ;AACzB,WAAK,YAAY,KAAK;AACtB,WAAK,SAAS,KAAK;AAAA,QACjB,KAAK,YAAY,qBAAoB;AAAA,QACrC,KAAK;AAAA,MACP;AAEA,UAAI,CAAC,KAAK,OAAO;AACf,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,WAAK,UAAU,KAAK;AAAA,QAClB,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AAEA,WAAK,QAAQ;AAAA,IACf;AAEA,WAAO,KAAK,aAAa,SAAS,GAAG;AACnC,YAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AAAA,IACtD;AAEA,UAAM,QAAQ,KAAK,aAAa,MAAM;AACtC,SAAK;AACL,SAAK,YAAY;AACjB,WAAO;AAAA,EACT;AAAA,EAEQ,YAAY,MAAc;AAChC,SAAK,QAAQ,SAAS,IAAI,SAAS;AAAA,EACrC;AAAA,EAEA,MAAc,YAAY,KAAU;AAClC,UAAM,OAAO,IAAI;AAEjB,QAAI,KAAK,YAAY;AACnB;AAAA,IACF;AAEA,QAAI,SAAS,UAAU;AACrB,uCAAU,4BAAU,OAAO,EAAC,OAAO,mBAAkB,CAAC;AACtD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF,WAAW,IAAI,QAAQ,SAAS,SAAS,GAAG;AAC1C,uCAAU,4BAAU,OAAO;AAAA,QACzB,OAAO;AAAA,QACP,SAAS,IAAI;AAAA,MACf,CAAC;AACD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF,OAAO;AACL,gBAAM,6BAAU,4BAAU,OAAO;AAAA,QAC/B,OAAO;AAAA,QACP,SAAS,IAAI;AAAA,MACf,CAAC;AACD,YAAM,IAAI;AAAA,QACR,oEAAoE,KAAK,QAAQ,KAAK,GAAG;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAAA,EAEO,UAAU;AACf,SAAK,aAAa;AAClB,SAAK,SAAS,KAAK,SAAS;AAAA,EAC9B;AACF;AA9Xa,qBACa,uBAAuB;AADpC,qBA8BG,qBAGV,oBAAI,IAAI;AAjCP,IAAM,sBAAN;","names":["path","fs","os","path","os","path","uuidv4","ffmpeg","import_telemetry","fs","os","path","import_uuid","ffmpeg","uuidv4","err","process","image"]}
|
package/dist/index.js
CHANGED
|
@@ -394,13 +394,8 @@ function getAssetPlacement(frames) {
|
|
|
394
394
|
const timeInfo = assetTimeMap.get(asset.key);
|
|
395
395
|
if (timeInfo) {
|
|
396
396
|
asset.durationInSeconds = (timeInfo.end - timeInfo.start) / asset.playbackRate;
|
|
397
|
-
console.log(`[getAssetPlacement] Asset ${asset.key}:`);
|
|
398
|
-
console.log(` - currentTime range: ${timeInfo.start} to ${timeInfo.end}`);
|
|
399
|
-
console.log(` - durationInSeconds (from currentTime): ${asset.durationInSeconds}`);
|
|
400
397
|
}
|
|
401
398
|
asset.duration = asset.endInVideo - asset.startInVideo + 1;
|
|
402
|
-
console.log(` - frame range: ${asset.startInVideo} to ${asset.endInVideo}`);
|
|
403
|
-
console.log(` - duration (frames): ${asset.duration}`);
|
|
404
399
|
});
|
|
405
400
|
return assets;
|
|
406
401
|
}
|
|
@@ -425,40 +420,25 @@ function calculateAtempoFilters(playbackRate) {
|
|
|
425
420
|
return atempoFilters;
|
|
426
421
|
}
|
|
427
422
|
async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps) {
|
|
428
|
-
console.log(`[prepareAudio] Processing asset: ${asset.key}`);
|
|
429
|
-
console.log(`[prepareAudio] Asset src: ${asset.src}`);
|
|
430
|
-
console.log(`[prepareAudio] Asset type: ${asset.type}`);
|
|
431
|
-
console.log(`[prepareAudio] Playback rate: ${asset.playbackRate}`);
|
|
432
|
-
console.log(`[prepareAudio] Volume: ${asset.volume}`);
|
|
433
423
|
const sanitizedKey = asset.key.replace(/[/[\]]/g, "-");
|
|
434
424
|
const outputPath = path5.join(tempDir, `${sanitizedKey}.wav`);
|
|
435
|
-
console.log(`[prepareAudio] Output path: ${outputPath}`);
|
|
436
425
|
const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;
|
|
437
426
|
let effectiveDurationInSeconds = asset.durationInSeconds;
|
|
438
427
|
if (effectiveDurationInSeconds < 0.1) {
|
|
439
428
|
effectiveDurationInSeconds = asset.duration / fps;
|
|
440
|
-
console.log(`[prepareAudio] WARNING: durationInSeconds was ${asset.durationInSeconds}, using frame-based duration: ${effectiveDurationInSeconds}s`);
|
|
441
429
|
}
|
|
442
430
|
const trimRight = 1 / fps + Math.min(
|
|
443
431
|
trimLeft + effectiveDurationInSeconds,
|
|
444
432
|
trimLeft + (endFrame - startFrame) / fps
|
|
445
433
|
);
|
|
446
434
|
const padStart = asset.startInVideo / fps * 1e3;
|
|
447
|
-
console.log(`[prepareAudio] Trim calculation:`);
|
|
448
|
-
console.log(` - trimLeft: ${trimLeft}s`);
|
|
449
|
-
console.log(` - effectiveDurationInSeconds: ${effectiveDurationInSeconds}s`);
|
|
450
|
-
console.log(` - trimRight: ${trimRight}s`);
|
|
451
|
-
console.log(` - padStart: ${padStart}ms`);
|
|
452
435
|
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
453
|
-
console.log(`[prepareAudio] Resolved path: ${resolvedPath}`);
|
|
454
436
|
const assetSampleRate = await getSampleRate(resolvedPath);
|
|
455
|
-
console.log(`[prepareAudio] Sample rate: ${assetSampleRate}`);
|
|
456
437
|
const padEnd = Math.max(
|
|
457
438
|
0,
|
|
458
439
|
assetSampleRate * (endFrame - startFrame + 1) / fps - assetSampleRate * asset.duration / fps - assetSampleRate * padStart / 1e3
|
|
459
440
|
);
|
|
460
441
|
const atempoFilters = calculateAtempoFilters(asset.playbackRate);
|
|
461
|
-
console.log(`[prepareAudio] Atempo filters: ${atempoFilters.join(", ")}`);
|
|
462
442
|
await new Promise((resolve, reject) => {
|
|
463
443
|
const audioFilters = [
|
|
464
444
|
...atempoFilters,
|
|
@@ -467,42 +447,19 @@ async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps
|
|
|
467
447
|
`adelay=${padStart}|${padStart}|${padStart}`,
|
|
468
448
|
`volume=${asset.volume}`
|
|
469
449
|
].join(",");
|
|
470
|
-
console.log(`[prepareAudio] Audio filters: ${audioFilters}`);
|
|
471
|
-
console.log(`[prepareAudio] Starting ffmpeg processing...`);
|
|
472
450
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
473
|
-
ffmpeg2(resolvedPath).audioChannels(2).audioCodec("pcm_s16le").audioFrequency(SAMPLE_RATE).outputOptions([`-af`, audioFilters]).on("end", () =>
|
|
474
|
-
console.log(`[prepareAudio] Successfully processed audio for ${asset.key}`);
|
|
475
|
-
resolve();
|
|
476
|
-
}).on("error", (err) => {
|
|
477
|
-
console.error(`[prepareAudio] Error processing audio for asset key: ${asset.key}`, err);
|
|
478
|
-
reject(err);
|
|
479
|
-
}).save(outputPath);
|
|
451
|
+
ffmpeg2(resolvedPath).audioChannels(2).audioCodec("pcm_s16le").audioFrequency(SAMPLE_RATE).outputOptions([`-af`, audioFilters]).on("end", () => resolve()).on("error", (err) => reject(err)).save(outputPath);
|
|
480
452
|
});
|
|
481
|
-
console.log(`[prepareAudio] Audio file saved: ${outputPath}`);
|
|
482
453
|
return outputPath;
|
|
483
454
|
}
|
|
484
455
|
async function mergeAudioTracks(tempDir, audioFilenames) {
|
|
485
|
-
console.log(`[mergeAudioTracks] Starting merge of ${audioFilenames.length} tracks`);
|
|
486
|
-
audioFilenames.forEach((filename, idx) => {
|
|
487
|
-
console.log(`[mergeAudioTracks] Track ${idx + 1}: ${filename}`);
|
|
488
|
-
});
|
|
489
456
|
const outputPath = path5.join(tempDir, `audio.wav`);
|
|
490
|
-
console.log(`[mergeAudioTracks] Output path: ${outputPath}`);
|
|
491
457
|
return new Promise((resolve, reject) => {
|
|
492
458
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
493
459
|
const command = ffmpeg2();
|
|
494
|
-
audioFilenames.forEach((filename) =>
|
|
495
|
-
command.input(filename);
|
|
496
|
-
});
|
|
460
|
+
audioFilenames.forEach((filename) => command.input(filename));
|
|
497
461
|
const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;
|
|
498
|
-
|
|
499
|
-
command.complexFilter([complexFilter]).outputOptions(["-c:a", "pcm_s16le"]).on("end", () => {
|
|
500
|
-
console.log(`[mergeAudioTracks] Successfully merged audio tracks to: ${outputPath}`);
|
|
501
|
-
resolve();
|
|
502
|
-
}).on("error", (err) => {
|
|
503
|
-
console.error(`[mergeAudioTracks] Error merging audio tracks:`, err);
|
|
504
|
-
reject(err);
|
|
505
|
-
}).save(outputPath);
|
|
462
|
+
command.complexFilter([complexFilter]).outputOptions(["-c:a", "pcm_s16le"]).on("end", () => resolve()).on("error", (err) => reject(err)).save(outputPath);
|
|
506
463
|
});
|
|
507
464
|
}
|
|
508
465
|
async function generateAudio({
|
|
@@ -513,33 +470,18 @@ async function generateAudio({
|
|
|
513
470
|
endFrame,
|
|
514
471
|
fps
|
|
515
472
|
}) {
|
|
516
|
-
console.log(`[generateAudio] Starting audio generation`);
|
|
517
|
-
console.log(`[generateAudio] Output dir: ${outputDir}`);
|
|
518
|
-
console.log(`[generateAudio] Temp dir: ${tempDir}`);
|
|
519
|
-
console.log(`[generateAudio] Start frame: ${startFrame}, End frame: ${endFrame}`);
|
|
520
|
-
console.log(`[generateAudio] FPS: ${fps}`);
|
|
521
|
-
console.log(`[generateAudio] Total frames: ${assets.length}`);
|
|
522
473
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
523
|
-
console.log(`[generateAudio] Full temp dir: ${fullTempDir}`);
|
|
524
474
|
await makeSureFolderExists(outputDir);
|
|
525
475
|
await makeSureFolderExists(fullTempDir);
|
|
526
476
|
const assetPositions = getAssetPlacement(assets);
|
|
527
|
-
console.log(`[generateAudio] Found ${assetPositions.length} unique assets`);
|
|
528
|
-
assetPositions.forEach((asset, idx) => {
|
|
529
|
-
console.log(`[generateAudio] Asset ${idx + 1}: key=${asset.key}, src=${asset.src}, type=${asset.type}, playbackRate=${asset.playbackRate}, volume=${asset.volume}`);
|
|
530
|
-
});
|
|
531
477
|
const audioFilenames = [];
|
|
532
478
|
for (const asset of assetPositions) {
|
|
533
|
-
console.log(`[generateAudio] Processing asset: ${asset.key}`);
|
|
534
479
|
let hasAudioStream = true;
|
|
535
480
|
if (asset.type !== "audio") {
|
|
536
481
|
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
537
|
-
console.log(`[generateAudio] Checking for audio stream in: ${resolvedPath}`);
|
|
538
482
|
hasAudioStream = await checkForAudioStream(resolvedPath);
|
|
539
|
-
console.log(`[generateAudio] Has audio stream: ${hasAudioStream}`);
|
|
540
483
|
}
|
|
541
484
|
if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {
|
|
542
|
-
console.log(`[generateAudio] Asset ${asset.key} will be processed (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);
|
|
543
485
|
const filename = await prepareAudio(
|
|
544
486
|
outputDir,
|
|
545
487
|
fullTempDir,
|
|
@@ -549,59 +491,35 @@ async function generateAudio({
|
|
|
549
491
|
fps
|
|
550
492
|
);
|
|
551
493
|
audioFilenames.push(filename);
|
|
552
|
-
console.log(`[generateAudio] Added audio file to list: ${filename}`);
|
|
553
|
-
} else {
|
|
554
|
-
console.log(`[generateAudio] Skipping asset ${asset.key} (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);
|
|
555
494
|
}
|
|
556
495
|
}
|
|
557
|
-
console.log(`[generateAudio] Total audio files to merge: ${audioFilenames.length}`);
|
|
558
496
|
if (audioFilenames.length > 0) {
|
|
559
|
-
console.log(`[generateAudio] Merging ${audioFilenames.length} audio tracks...`);
|
|
560
497
|
await mergeAudioTracks(fullTempDir, audioFilenames);
|
|
561
|
-
console.log(`[generateAudio] Audio tracks merged successfully`);
|
|
562
|
-
} else {
|
|
563
|
-
console.warn(`[generateAudio] No audio files to merge!`);
|
|
564
498
|
}
|
|
565
499
|
return audioFilenames;
|
|
566
500
|
}
|
|
567
501
|
async function mergeMedia(outputFilename, outputDir, tempDir, format) {
|
|
568
|
-
console.log(`[mergeMedia] Starting media merge`);
|
|
569
|
-
console.log(`[mergeMedia] Output filename: ${outputFilename}`);
|
|
570
|
-
console.log(`[mergeMedia] Output dir: ${outputDir}`);
|
|
571
|
-
console.log(`[mergeMedia] Temp dir: ${tempDir}`);
|
|
572
|
-
console.log(`[mergeMedia] Format: ${format}`);
|
|
573
502
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
574
|
-
console.log(`[mergeMedia] Full temp dir: ${fullTempDir}`);
|
|
575
503
|
await makeSureFolderExists(outputDir);
|
|
576
504
|
await makeSureFolderExists(fullTempDir);
|
|
577
505
|
const audioWavPath = path5.join(fullTempDir, `audio.wav`);
|
|
578
506
|
const audioWavExists = fs2.existsSync(audioWavPath);
|
|
579
|
-
console.log(`[mergeMedia] Audio WAV exists: ${audioWavExists} (${audioWavPath})`);
|
|
580
507
|
const visualsPath = path5.join(fullTempDir, `visuals.${extensions[format]}`);
|
|
581
|
-
const visualsExists = fs2.existsSync(visualsPath);
|
|
582
|
-
console.log(`[mergeMedia] Visuals exist: ${visualsExists} (${visualsPath})`);
|
|
583
508
|
const outputPath = path5.join(outputDir, `${outputFilename}.${extensions[format]}`);
|
|
584
509
|
if (audioWavExists) {
|
|
585
|
-
console.log(`[mergeMedia] Merging audio and video...`);
|
|
586
510
|
await mergeAudioWithVideo(
|
|
587
511
|
audioWavPath,
|
|
588
512
|
visualsPath,
|
|
589
513
|
outputPath,
|
|
590
514
|
audioCodecs[format]
|
|
591
515
|
);
|
|
592
|
-
console.log(`[mergeMedia] Successfully merged audio and video to: ${outputPath}`);
|
|
593
516
|
} else {
|
|
594
|
-
console.log(`[mergeMedia] No audio found, copying video only...`);
|
|
595
517
|
await fs2.promises.copyFile(visualsPath, outputPath);
|
|
596
|
-
console.log(`[mergeMedia] Successfully copied video to: ${outputPath}`);
|
|
597
518
|
}
|
|
598
519
|
if (fullTempDir.endsWith("-undefined")) {
|
|
599
|
-
|
|
600
|
-
await fs2.promises.rm(fullTempDir, { recursive: true, force: true }).catch((err) => {
|
|
601
|
-
console.warn(`[mergeMedia] Failed to clean up temp directory:`, err);
|
|
520
|
+
await fs2.promises.rm(fullTempDir, { recursive: true, force: true }).catch(() => {
|
|
602
521
|
});
|
|
603
522
|
}
|
|
604
|
-
console.log(`[mergeMedia] Media merge completed`);
|
|
605
523
|
}
|
|
606
524
|
|
|
607
525
|
// src/video-frame-extractor.ts
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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 * as 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', (err: Error) => reject(err));\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 * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\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 ffmpeg = require('fluent-ffmpeg');\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 console.log(`[getAssetPlacement] Asset ${asset.key}:`);\n console.log(` - currentTime range: ${timeInfo.start} to ${timeInfo.end}`);\n console.log(` - durationInSeconds (from currentTime): ${asset.durationInSeconds}`);\n }\n // Recalculate the original duration based on frame count.\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n console.log(` - frame range: ${asset.startInVideo} to ${asset.endInVideo}`);\n console.log(` - duration (frames): ${asset.duration}`);\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 console.log(`[prepareAudio] Processing asset: ${asset.key}`);\n console.log(`[prepareAudio] Asset src: ${asset.src}`);\n console.log(`[prepareAudio] Asset type: ${asset.type}`);\n console.log(`[prepareAudio] Playback rate: ${asset.playbackRate}`);\n console.log(`[prepareAudio] Volume: ${asset.volume}`);\n \n // Construct the output path\n const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n console.log(`[prepareAudio] Output path: ${outputPath}`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n \n // Calculate duration from frames if durationInSeconds is 0 or suspiciously small\n let effectiveDurationInSeconds = asset.durationInSeconds;\n if (effectiveDurationInSeconds < 0.1) {\n // Fallback: use frame-based duration\n effectiveDurationInSeconds = asset.duration / fps;\n console.log(`[prepareAudio] WARNING: durationInSeconds was ${asset.durationInSeconds}, using frame-based duration: ${effectiveDurationInSeconds}s`);\n }\n \n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + effectiveDurationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n \n console.log(`[prepareAudio] Trim calculation:`);\n console.log(` - trimLeft: ${trimLeft}s`);\n console.log(` - effectiveDurationInSeconds: ${effectiveDurationInSeconds}s`);\n console.log(` - trimRight: ${trimRight}s`);\n console.log(` - padStart: ${padStart}ms`);\n \n const resolvedPath = resolvePath(outputDir, asset.src);\n console.log(`[prepareAudio] Resolved path: ${resolvedPath}`);\n \n const assetSampleRate = await getSampleRate(resolvedPath);\n console.log(`[prepareAudio] Sample rate: ${assetSampleRate}`);\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 console.log(`[prepareAudio] Atempo filters: ${atempoFilters.join(', ')}`);\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 console.log(`[prepareAudio] Audio filters: ${audioFilters}`);\n console.log(`[prepareAudio] Starting ffmpeg processing...`);\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 console.log(`[prepareAudio] Successfully processed audio for ${asset.key}`);\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`[prepareAudio] Error processing audio for asset key: ${asset.key}`, err);\n reject(err);\n })\n .save(outputPath);\n });\n\n console.log(`[prepareAudio] Audio file saved: ${outputPath}`);\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n console.log(`[mergeAudioTracks] Starting merge of ${audioFilenames.length} tracks`);\n audioFilenames.forEach((filename, idx) => {\n console.log(`[mergeAudioTracks] Track ${idx + 1}: ${filename}`);\n });\n \n const outputPath = path.join(tempDir, `audio.wav`);\n console.log(`[mergeAudioTracks] Output path: ${outputPath}`);\n \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 const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;\n console.log(`[mergeAudioTracks] Complex filter: ${complexFilter}`);\n\n command\n .complexFilter([complexFilter])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => {\n console.log(`[mergeAudioTracks] Successfully merged audio tracks to: ${outputPath}`);\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`[mergeAudioTracks] Error merging audio tracks:`, err);\n reject(err);\n })\n .save(outputPath);\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 console.log(`[generateAudio] Starting audio generation`);\n console.log(`[generateAudio] Output dir: ${outputDir}`);\n console.log(`[generateAudio] Temp dir: ${tempDir}`);\n console.log(`[generateAudio] Start frame: ${startFrame}, End frame: ${endFrame}`);\n console.log(`[generateAudio] FPS: ${fps}`);\n console.log(`[generateAudio] Total frames: ${assets.length}`);\n \n const fullTempDir = path.join(os.tmpdir(), tempDir);\n console.log(`[generateAudio] Full temp dir: ${fullTempDir}`);\n \n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const assetPositions = getAssetPlacement(assets);\n console.log(`[generateAudio] Found ${assetPositions.length} unique assets`);\n \n assetPositions.forEach((asset, idx) => {\n console.log(`[generateAudio] Asset ${idx + 1}: key=${asset.key}, src=${asset.src}, type=${asset.type}, playbackRate=${asset.playbackRate}, volume=${asset.volume}`);\n });\n \n const audioFilenames: string[] = [];\n\n for (const asset of assetPositions) {\n console.log(`[generateAudio] Processing asset: ${asset.key}`);\n \n let hasAudioStream = true;\n if (asset.type !== 'audio') {\n const resolvedPath = resolvePath(outputDir, asset.src);\n console.log(`[generateAudio] Checking for audio stream in: ${resolvedPath}`);\n hasAudioStream = await checkForAudioStream(resolvedPath);\n console.log(`[generateAudio] Has audio stream: ${hasAudioStream}`);\n }\n\n if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {\n console.log(`[generateAudio] Asset ${asset.key} will be processed (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);\n const filename = await prepareAudio(\n outputDir,\n fullTempDir,\n asset,\n startFrame,\n endFrame,\n fps,\n );\n audioFilenames.push(filename);\n console.log(`[generateAudio] Added audio file to list: ${filename}`);\n } else {\n console.log(`[generateAudio] Skipping asset ${asset.key} (playbackRate=${asset.playbackRate}, volume=${asset.volume}, hasAudio=${hasAudioStream})`);\n }\n }\n\n console.log(`[generateAudio] Total audio files to merge: ${audioFilenames.length}`);\n\n if (audioFilenames.length > 0) {\n console.log(`[generateAudio] Merging ${audioFilenames.length} audio tracks...`);\n await mergeAudioTracks(fullTempDir, audioFilenames);\n console.log(`[generateAudio] Audio tracks merged successfully`);\n } else {\n console.warn(`[generateAudio] No audio files to merge!`);\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 console.log(`[mergeMedia] Starting media merge`);\n console.log(`[mergeMedia] Output filename: ${outputFilename}`);\n console.log(`[mergeMedia] Output dir: ${outputDir}`);\n console.log(`[mergeMedia] Temp dir: ${tempDir}`);\n console.log(`[mergeMedia] Format: ${format}`);\n \n const fullTempDir = path.join(os.tmpdir(), tempDir);\n console.log(`[mergeMedia] Full temp dir: ${fullTempDir}`);\n \n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const audioWavPath = path.join(fullTempDir, `audio.wav`);\n const audioWavExists = fs.existsSync(audioWavPath);\n console.log(`[mergeMedia] Audio WAV exists: ${audioWavExists} (${audioWavPath})`);\n \n const visualsPath = path.join(fullTempDir, `visuals.${extensions[format]}`);\n const visualsExists = fs.existsSync(visualsPath);\n console.log(`[mergeMedia] Visuals exist: ${visualsExists} (${visualsPath})`);\n \n const outputPath = path.join(outputDir, `${outputFilename}.${extensions[format]}`);\n \n if (audioWavExists) {\n console.log(`[mergeMedia] Merging audio and video...`);\n await mergeAudioWithVideo(\n audioWavPath,\n visualsPath,\n outputPath,\n audioCodecs[format],\n );\n console.log(`[mergeMedia] Successfully merged audio and video to: ${outputPath}`);\n } else {\n console.log(`[mergeMedia] No audio found, copying video only...`);\n await fs.promises.copyFile(visualsPath, outputPath);\n console.log(`[mergeMedia] Successfully copied video to: ${outputPath}`);\n }\n \n if (fullTempDir.endsWith('-undefined')) {\n console.log(`[mergeMedia] Cleaning up temp directory: ${fullTempDir}`);\n await fs.promises\n .rm(fullTempDir, {recursive: true, force: true})\n .catch((err) => {\n console.warn(`[mergeMedia] Failed to clean up temp directory:`, err);\n });\n }\n \n console.log(`[mergeMedia] Media merge completed`);\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","fs","os","path","os","path","ffmpeg","EventName","sendEvent","fs","os","path","uuidv4","ffmpeg","uuidv4","err","process","image","sendEvent","EventName"]}
|
|
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 * as 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', (err: Error) => reject(err));\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 * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\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 ffmpeg = require('fluent-ffmpeg');\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 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 const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n let effectiveDurationInSeconds = asset.durationInSeconds;\n if (effectiveDurationInSeconds < 0.1) {\n effectiveDurationInSeconds = asset.duration / fps;\n }\n\n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + effectiveDurationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n\n const resolvedPath = resolvePath(outputDir, asset.src);\n const assetSampleRate = await getSampleRate(resolvedPath);\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);\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', () => resolve())\n .on('error', (err: Error) => reject(err))\n .save(outputPath);\n });\n\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n const outputPath = path.join(tempDir, `audio.wav`);\n return new Promise((resolve, reject) => {\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const command = ffmpeg();\n audioFilenames.forEach((filename) => command.input(filename));\n const complexFilter = `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`;\n command\n .complexFilter([complexFilter])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => resolve())\n .on('error', (err: Error) => reject(err))\n .save(outputPath);\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 const resolvedPath = resolvePath(outputDir, asset.src);\n hasAudioStream = await checkForAudioStream(resolvedPath);\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 audioWavPath = path.join(fullTempDir, `audio.wav`);\n const audioWavExists = fs.existsSync(audioWavPath);\n const visualsPath = path.join(fullTempDir, `visuals.${extensions[format]}`);\n const outputPath = path.join(outputDir, `${outputFilename}.${extensions[format]}`);\n\n if (audioWavExists) {\n await mergeAudioWithVideo(\n audioWavPath,\n visualsPath,\n outputPath,\n audioCodecs[format],\n );\n } else {\n await fs.promises.copyFile(visualsPath, outputPath);\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","fs","os","path","os","path","ffmpeg","EventName","sendEvent","fs","os","path","uuidv4","ffmpeg","uuidv4","err","process","image","sendEvent","EventName"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twick/ffmpeg",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.8",
|
|
4
4
|
"description": "Ffmpeg utilities for twick",
|
|
5
5
|
"author": "twick",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
30
30
|
"@ffprobe-installer/ffprobe": "^2.0.0",
|
|
31
|
-
"@twick/core": "0.15.
|
|
32
|
-
"@twick/telemetry": "0.15.
|
|
31
|
+
"@twick/core": "0.15.8",
|
|
32
|
+
"@twick/telemetry": "0.15.8",
|
|
33
33
|
"fluent-ffmpeg": "^2.1.2",
|
|
34
34
|
"uuid": "^10.0.0"
|
|
35
35
|
},
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"url": "https://github.com/ncounterspecialist/twick-base.git"
|
|
46
46
|
},
|
|
47
47
|
"bugs": "https://github.com/ncounterspecialist/twick-base/issues",
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "58765d4a040f6ad8b07780d7a48f78a79c82f176"
|
|
49
49
|
}
|