@twick/ffmpeg 0.15.7 → 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 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
- console.log(`[mergeAudioTracks] Complex filter: ${complexFilter}`);
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
- console.log(`[mergeMedia] Cleaning up temp directory: ${fullTempDir}`);
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
@@ -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":"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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"]}
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
- console.log(`[mergeAudioTracks] Complex filter: ${complexFilter}`);
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
- console.log(`[mergeMedia] Cleaning up temp directory: ${fullTempDir}`);
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.7",
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.7",
32
- "@twick/telemetry": "0.15.7",
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": "2088b9cc48dce047cf1753453fe80866b17478a3"
48
+ "gitHead": "58765d4a040f6ad8b07780d7a48f78a79c82f176"
49
49
  }