@twick/ffmpeg 0.15.2 → 0.15.3
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 +103 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +97 -33
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/index.d.cts +0 -138
- package/dist/index.d.ts +0 -138
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
|
|
8
8
|
// src/ffmpeg-exporter-server.ts
|
|
9
9
|
import { EventName, sendEvent } from "@twick/telemetry";
|
|
10
|
-
import ffmpeg from "fluent-ffmpeg";
|
|
10
|
+
import * as ffmpeg from "fluent-ffmpeg";
|
|
11
11
|
import * as os from "os";
|
|
12
12
|
import * as path3 from "path";
|
|
13
13
|
|
|
@@ -119,7 +119,7 @@ var FFmpegExporterServer = class {
|
|
|
119
119
|
}
|
|
120
120
|
this.command.outputOptions(["-movflags +faststart"]);
|
|
121
121
|
this.promise = new Promise((resolve, reject) => {
|
|
122
|
-
this.command.on("end", resolve).on("error", reject);
|
|
122
|
+
this.command.on("end", () => resolve()).on("error", (err) => reject(err));
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
async start() {
|
|
@@ -153,7 +153,6 @@ var FFmpegExporterServer = class {
|
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
// src/generate-audio.ts
|
|
156
|
-
import ffmpeg2 from "fluent-ffmpeg";
|
|
157
156
|
import * as fs2 from "fs";
|
|
158
157
|
import * as os3 from "os";
|
|
159
158
|
import * as path5 from "path";
|
|
@@ -347,6 +346,7 @@ async function getVideoMetadata(filePath) {
|
|
|
347
346
|
}
|
|
348
347
|
|
|
349
348
|
// src/generate-audio.ts
|
|
349
|
+
var ffmpeg2 = __require("fluent-ffmpeg");
|
|
350
350
|
var audioCodecs = {
|
|
351
351
|
mp4: "aac",
|
|
352
352
|
webm: "libopus",
|
|
@@ -394,8 +394,13 @@ 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}`);
|
|
397
400
|
}
|
|
398
401
|
asset.duration = asset.endInVideo - asset.startInVideo + 1;
|
|
402
|
+
console.log(` - frame range: ${asset.startInVideo} to ${asset.endInVideo}`);
|
|
403
|
+
console.log(` - duration (frames): ${asset.duration}`);
|
|
399
404
|
});
|
|
400
405
|
return assets;
|
|
401
406
|
}
|
|
@@ -420,23 +425,40 @@ function calculateAtempoFilters(playbackRate) {
|
|
|
420
425
|
return atempoFilters;
|
|
421
426
|
}
|
|
422
427
|
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}`);
|
|
423
433
|
const sanitizedKey = asset.key.replace(/[/[\]]/g, "-");
|
|
424
434
|
const outputPath = path5.join(tempDir, `${sanitizedKey}.wav`);
|
|
435
|
+
console.log(`[prepareAudio] Output path: ${outputPath}`);
|
|
425
436
|
const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;
|
|
437
|
+
let effectiveDurationInSeconds = asset.durationInSeconds;
|
|
438
|
+
if (effectiveDurationInSeconds < 0.1) {
|
|
439
|
+
effectiveDurationInSeconds = asset.duration / fps;
|
|
440
|
+
console.log(`[prepareAudio] WARNING: durationInSeconds was ${asset.durationInSeconds}, using frame-based duration: ${effectiveDurationInSeconds}s`);
|
|
441
|
+
}
|
|
426
442
|
const trimRight = 1 / fps + Math.min(
|
|
427
|
-
trimLeft +
|
|
443
|
+
trimLeft + effectiveDurationInSeconds,
|
|
428
444
|
trimLeft + (endFrame - startFrame) / fps
|
|
429
445
|
);
|
|
430
446
|
const padStart = asset.startInVideo / fps * 1e3;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
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
|
+
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
453
|
+
console.log(`[prepareAudio] Resolved path: ${resolvedPath}`);
|
|
454
|
+
const assetSampleRate = await getSampleRate(resolvedPath);
|
|
455
|
+
console.log(`[prepareAudio] Sample rate: ${assetSampleRate}`);
|
|
434
456
|
const padEnd = Math.max(
|
|
435
457
|
0,
|
|
436
458
|
assetSampleRate * (endFrame - startFrame + 1) / fps - assetSampleRate * asset.duration / fps - assetSampleRate * padStart / 1e3
|
|
437
459
|
);
|
|
438
460
|
const atempoFilters = calculateAtempoFilters(asset.playbackRate);
|
|
439
|
-
|
|
461
|
+
console.log(`[prepareAudio] Atempo filters: ${atempoFilters.join(", ")}`);
|
|
440
462
|
await new Promise((resolve, reject) => {
|
|
441
463
|
const audioFilters = [
|
|
442
464
|
...atempoFilters,
|
|
@@ -445,34 +467,42 @@ async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps
|
|
|
445
467
|
`adelay=${padStart}|${padStart}|${padStart}`,
|
|
446
468
|
`volume=${asset.volume}`
|
|
447
469
|
].join(",");
|
|
470
|
+
console.log(`[prepareAudio] Audio filters: ${audioFilters}`);
|
|
471
|
+
console.log(`[prepareAudio] Starting ffmpeg processing...`);
|
|
448
472
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
449
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}`);
|
|
450
475
|
resolve();
|
|
451
476
|
}).on("error", (err) => {
|
|
452
|
-
console.error(
|
|
453
|
-
`Error processing audio for asset key: ${asset.key}`,
|
|
454
|
-
err
|
|
455
|
-
);
|
|
477
|
+
console.error(`[prepareAudio] Error processing audio for asset key: ${asset.key}`, err);
|
|
456
478
|
reject(err);
|
|
457
479
|
}).save(outputPath);
|
|
458
480
|
});
|
|
481
|
+
console.log(`[prepareAudio] Audio file saved: ${outputPath}`);
|
|
459
482
|
return outputPath;
|
|
460
483
|
}
|
|
461
484
|
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
|
+
const outputPath = path5.join(tempDir, `audio.wav`);
|
|
490
|
+
console.log(`[mergeAudioTracks] Output path: ${outputPath}`);
|
|
462
491
|
return new Promise((resolve, reject) => {
|
|
463
492
|
ffmpeg2.setFfmpegPath(ffmpegSettings.getFfmpegPath());
|
|
464
493
|
const command = ffmpeg2();
|
|
465
494
|
audioFilenames.forEach((filename) => {
|
|
466
495
|
command.input(filename);
|
|
467
496
|
});
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
]).outputOptions(["-c:a", "pcm_s16le"]).on("end", () => {
|
|
497
|
+
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}`);
|
|
471
501
|
resolve();
|
|
472
502
|
}).on("error", (err) => {
|
|
473
|
-
console.error(`Error merging audio tracks
|
|
503
|
+
console.error(`[mergeAudioTracks] Error merging audio tracks:`, err);
|
|
474
504
|
reject(err);
|
|
475
|
-
}).save(
|
|
505
|
+
}).save(outputPath);
|
|
476
506
|
});
|
|
477
507
|
}
|
|
478
508
|
async function generateAudio({
|
|
@@ -483,19 +513,33 @@ async function generateAudio({
|
|
|
483
513
|
endFrame,
|
|
484
514
|
fps
|
|
485
515
|
}) {
|
|
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}`);
|
|
486
522
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
523
|
+
console.log(`[generateAudio] Full temp dir: ${fullTempDir}`);
|
|
487
524
|
await makeSureFolderExists(outputDir);
|
|
488
525
|
await makeSureFolderExists(fullTempDir);
|
|
489
526
|
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
|
+
});
|
|
490
531
|
const audioFilenames = [];
|
|
491
532
|
for (const asset of assetPositions) {
|
|
533
|
+
console.log(`[generateAudio] Processing asset: ${asset.key}`);
|
|
492
534
|
let hasAudioStream = true;
|
|
493
535
|
if (asset.type !== "audio") {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
);
|
|
536
|
+
const resolvedPath = resolvePath(outputDir, asset.src);
|
|
537
|
+
console.log(`[generateAudio] Checking for audio stream in: ${resolvedPath}`);
|
|
538
|
+
hasAudioStream = await checkForAudioStream(resolvedPath);
|
|
539
|
+
console.log(`[generateAudio] Has audio stream: ${hasAudioStream}`);
|
|
497
540
|
}
|
|
498
541
|
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})`);
|
|
499
543
|
const filename = await prepareAudio(
|
|
500
544
|
outputDir,
|
|
501
545
|
fullTempDir,
|
|
@@ -505,39 +549,59 @@ async function generateAudio({
|
|
|
505
549
|
fps
|
|
506
550
|
);
|
|
507
551
|
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})`);
|
|
508
555
|
}
|
|
509
556
|
}
|
|
557
|
+
console.log(`[generateAudio] Total audio files to merge: ${audioFilenames.length}`);
|
|
510
558
|
if (audioFilenames.length > 0) {
|
|
559
|
+
console.log(`[generateAudio] Merging ${audioFilenames.length} audio tracks...`);
|
|
511
560
|
await mergeAudioTracks(fullTempDir, audioFilenames);
|
|
561
|
+
console.log(`[generateAudio] Audio tracks merged successfully`);
|
|
562
|
+
} else {
|
|
563
|
+
console.warn(`[generateAudio] No audio files to merge!`);
|
|
512
564
|
}
|
|
513
565
|
return audioFilenames;
|
|
514
566
|
}
|
|
515
567
|
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}`);
|
|
516
573
|
const fullTempDir = path5.join(os3.tmpdir(), tempDir);
|
|
574
|
+
console.log(`[mergeMedia] Full temp dir: ${fullTempDir}`);
|
|
517
575
|
await makeSureFolderExists(outputDir);
|
|
518
576
|
await makeSureFolderExists(fullTempDir);
|
|
519
|
-
const
|
|
577
|
+
const audioWavPath = path5.join(fullTempDir, `audio.wav`);
|
|
578
|
+
const audioWavExists = fs2.existsSync(audioWavPath);
|
|
579
|
+
console.log(`[mergeMedia] Audio WAV exists: ${audioWavExists} (${audioWavPath})`);
|
|
580
|
+
const visualsPath = path5.join(fullTempDir, `visuals.${extensions[format]}`);
|
|
581
|
+
const visualsExists = fs2.existsSync(visualsPath);
|
|
582
|
+
console.log(`[mergeMedia] Visuals exist: ${visualsExists} (${visualsPath})`);
|
|
583
|
+
const outputPath = path5.join(outputDir, `${outputFilename}.${extensions[format]}`);
|
|
520
584
|
if (audioWavExists) {
|
|
585
|
+
console.log(`[mergeMedia] Merging audio and video...`);
|
|
521
586
|
await mergeAudioWithVideo(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
587
|
+
audioWavPath,
|
|
588
|
+
visualsPath,
|
|
589
|
+
outputPath,
|
|
525
590
|
audioCodecs[format]
|
|
526
591
|
);
|
|
592
|
+
console.log(`[mergeMedia] Successfully merged audio and video to: ${outputPath}`);
|
|
527
593
|
} else {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
);
|
|
532
|
-
await fs2.promises.copyFile(
|
|
533
|
-
path5.join(fullTempDir, `visuals.${extensions[format]}`),
|
|
534
|
-
destination
|
|
535
|
-
);
|
|
594
|
+
console.log(`[mergeMedia] No audio found, copying video only...`);
|
|
595
|
+
await fs2.promises.copyFile(visualsPath, outputPath);
|
|
596
|
+
console.log(`[mergeMedia] Successfully copied video to: ${outputPath}`);
|
|
536
597
|
}
|
|
537
598
|
if (fullTempDir.endsWith("-undefined")) {
|
|
538
|
-
|
|
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);
|
|
539
602
|
});
|
|
540
603
|
}
|
|
604
|
+
console.log(`[mergeMedia] Media merge completed`);
|
|
541
605
|
}
|
|
542
606
|
|
|
543
607
|
// 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 ffmpeg from 'fluent-ffmpeg';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {ImageStream} from './image-stream';\nimport {ffmpegSettings} from './settings';\n\nexport interface FFmpegExporterSettings extends RendererSettings {\n fastStart: boolean;\n includeAudio: boolean;\n output: string;\n}\n\nconst pixelFormats: Record<FfmpegExporterOptions['format'], string> = {\n mp4: 'yuv420p',\n webm: 'yuva420p',\n proRes: 'yuva444p10le',\n};\n\nexport const extensions: Record<FfmpegExporterOptions['format'], string> = {\n mp4: 'mp4',\n webm: 'webm',\n proRes: 'mov',\n};\n\n/**\n * The server-side implementation of the FFmpeg video exporter.\n */\nexport class FFmpegExporterServer {\n private readonly stream: ImageStream;\n private readonly command: ffmpeg.FfmpegCommand;\n private readonly promise: Promise<void>;\n private readonly settings: FFmpegExporterSettings;\n private readonly jobFolder: string;\n private readonly format: FfmpegExporterOptions['format'];\n\n public constructor(settings: FFmpegExporterSettings) {\n if (settings.exporter.name !== '@twick/core/ffmpeg') {\n throw new Error('Invalid exporter');\n }\n\n this.settings = settings;\n this.format = settings.exporter.options.format;\n\n this.jobFolder = path.join(\n os.tmpdir(),\n `twick-${this.settings.name}-${settings.hiddenFolderId}`,\n );\n this.stream = new ImageStream();\n\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n this.command = ffmpeg();\n\n // Input image sequence\n this.command\n .input(this.stream)\n .inputFormat('image2pipe')\n .inputFps(settings.fps);\n\n // Output settings\n const size = {\n x: Math.round(settings.size.x * settings.resolutionScale),\n y: Math.round(settings.size.y * settings.resolutionScale),\n };\n this.command\n .output(path.join(this.jobFolder, `visuals.${extensions[this.format]}`))\n .outputOptions([`-pix_fmt ${pixelFormats[this.format]}`, '-shortest'])\n .outputFps(settings.fps)\n .size(`${size.x}x${size.y}`);\n\n if (this.format === 'proRes') {\n this.command.outputOptions(['-c:v prores_ks', '-profile:v 4444']);\n }\n\n this.command.outputOptions(['-movflags +faststart']);\n this.promise = new Promise<void>((resolve, reject) => {\n this.command.on('end', resolve).on('error', reject);\n });\n }\n\n public async start() {\n this.command.run();\n }\n\n public async handleFrame({data}: {data: string}) {\n const base64Data = data.slice(data.indexOf(',') + 1);\n this.stream.pushImage(Buffer.from(base64Data, 'base64'));\n }\n\n public async end(result: RendererResult) {\n this.stream.pushImage(null);\n if (result === 1) {\n try {\n this.command.kill('SIGKILL');\n await this.promise;\n } catch (err) {\n sendEvent(EventName.Error, {message: (err as Error).message});\n }\n } else {\n await this.promise;\n }\n }\n\n public async kill() {\n try {\n this.command.kill('SIGKILL');\n await this.promise;\n } catch (_) {\n return;\n }\n }\n}\n","import {Readable} from 'stream';\n\nexport class ImageStream extends Readable {\n private image: Buffer | null = null;\n private hasData = false;\n\n public pushImage(image: Buffer | null) {\n this.image = image;\n this.hasData = true;\n this._read();\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n public override _read() {\n if (this.hasData) {\n this.hasData = false;\n this.push(this.image);\n }\n }\n}\n","import * as ffmpegInstaller from '@ffmpeg-installer/ffmpeg';\nimport * as ffprobeInstaller from '@ffprobe-installer/ffprobe';\n\nconst ffmpegLogLevels = [\n 'quiet',\n 'panic',\n 'fatal',\n 'error',\n 'warning',\n 'info',\n 'verbose',\n 'debug',\n 'trace',\n] as const;\n\nexport type LogLevel = (typeof ffmpegLogLevels)[number];\n\nexport type FfmpegSettings = {\n ffmpegPath?: string;\n ffprobePath?: string;\n ffmpegLogLevel?: LogLevel;\n};\n\nclass FfmpegSettingState {\n private ffmpegPath: string;\n private ffprobePath: string;\n private logLevel: LogLevel;\n\n public constructor() {\n this.ffmpegPath = ffmpegInstaller.path as unknown as string;\n this.ffprobePath = ffprobeInstaller.path as unknown as string;\n\n // Use the FFMPEG_PATH environment variable if it is set\n if (process.env.FFMPEG_PATH) {\n this.ffmpegPath = process.env.FFMPEG_PATH;\n }\n\n // Use the FFPROBE_PATH environment variable if it is set\n if (process.env.FFPROBE_PATH) {\n this.ffprobePath = process.env.FFPROBE_PATH;\n }\n\n this.logLevel = 'error';\n\n // Use the FFMPEG_LOG_LEVEL environment variable if it is set\n if (\n process.env.FFMPEG_LOG_LEVEL &&\n ffmpegLogLevels.includes(process.env.FFMPEG_LOG_LEVEL as LogLevel)\n ) {\n this.logLevel = process.env.FFMPEG_LOG_LEVEL as LogLevel;\n }\n }\n\n public getFfmpegPath() {\n return this.ffmpegPath;\n }\n\n public setFfmpegPath(ffmpegPath: string) {\n this.ffmpegPath = ffmpegPath;\n }\n\n public getFfprobePath() {\n return this.ffprobePath;\n }\n\n public setFfprobePath(ffprobePath: string) {\n this.ffprobePath = ffprobePath;\n }\n\n public getLogLevel() {\n return this.logLevel;\n }\n\n public setLogLevel(logLevel: LogLevel) {\n this.logLevel = logLevel;\n }\n}\n\nexport const ffmpegSettings = new FfmpegSettingState();\n","import type {AssetInfo, FfmpegExporterOptions} from '@twick/core';\nimport ffmpeg from 'fluent-ffmpeg';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {extensions} from './ffmpeg-exporter-server';\nimport {ffmpegSettings} from './settings';\nimport type {AudioCodec} from './utils';\nimport {\n checkForAudioStream,\n getSampleRate,\n makeSureFolderExists,\n mergeAudioWithVideo,\n resolvePath,\n} from './utils';\n\nexport const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec> =\n {\n mp4: 'aac',\n webm: 'libopus',\n proRes: 'aac',\n };\n\ninterface MediaAsset {\n key: string;\n src: string;\n type: 'video' | 'audio';\n startInVideo: number;\n endInVideo: number;\n duration: number;\n playbackRate: number;\n volume: number;\n trimLeftInSeconds: number;\n durationInSeconds: number;\n}\n\nconst SAMPLE_RATE = 48000;\n\nfunction getAssetPlacement(frames: AssetInfo[][]): MediaAsset[] {\n const assets: MediaAsset[] = [];\n\n // A map to keep track of the first and last currentTime for each asset.\n const assetTimeMap = new Map<string, {start: number; end: number}>();\n\n for (let frame = 0; frame < frames.length; frame++) {\n for (const asset of frames[frame]) {\n if (!assetTimeMap.has(asset.key)) {\n // If the asset is not in the map, add it with its current time as both start and end.\n assetTimeMap.set(asset.key, {\n start: asset.currentTime,\n end: asset.currentTime,\n });\n assets.push({\n key: asset.key,\n src: asset.src,\n type: asset.type,\n startInVideo: frame,\n endInVideo: frame,\n duration: 0, // Placeholder, will be recalculated later based on frames\n durationInSeconds: 0, // Placeholder, will be calculated based on currentTime\n playbackRate: asset.playbackRate,\n volume: asset.volume,\n trimLeftInSeconds: asset.currentTime,\n });\n } else {\n // If the asset is already in the map, update the end time.\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n timeInfo.end = asset.currentTime;\n assetTimeMap.set(asset.key, timeInfo);\n }\n\n const existingAsset = assets.find(a => a.key === asset.key);\n if (existingAsset) {\n existingAsset.endInVideo = frame;\n }\n }\n }\n }\n\n // Calculate the duration based on frame count and durationInSeconds based on currentTime.\n assets.forEach(asset => {\n const timeInfo = assetTimeMap.get(asset.key);\n if (timeInfo) {\n // Calculate durationInSeconds based on the start and end currentTime values.\n asset.durationInSeconds =\n (timeInfo.end - timeInfo.start) / asset.playbackRate;\n }\n // Recalculate the original duration based on frame count.\n asset.duration = asset.endInVideo - asset.startInVideo + 1;\n });\n\n return assets;\n}\n\nfunction calculateAtempoFilters(playbackRate: number) {\n const atempoFilters = [];\n\n // Calculate how many times we need to 100x the speed\n let rate = playbackRate;\n while (rate > 100.0) {\n atempoFilters.push('atempo=100.0');\n rate /= 100.0;\n }\n // Add the last atempo filter with the remaining rate\n if (rate > 1.0) {\n atempoFilters.push(`atempo=${rate}`);\n }\n\n // Calculate how many times we need to halve the speed\n rate = playbackRate;\n while (rate < 0.5) {\n atempoFilters.push('atempo=0.5');\n rate *= 2.0;\n }\n // Add the last atempo filter with the remaining rate\n if (rate < 1.0) {\n atempoFilters.push(`atempo=${rate}`);\n }\n\n return atempoFilters;\n}\nasync function prepareAudio(\n outputDir: string,\n tempDir: string,\n asset: MediaAsset,\n startFrame: number,\n endFrame: number,\n fps: number,\n): Promise<string> {\n // Construct the output path\n const sanitizedKey = asset.key.replace(/[/[\\]]/g, '-');\n const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);\n\n const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;\n const trimRight =\n 1 / fps +\n Math.min(\n trimLeft + asset.durationInSeconds,\n trimLeft + (endFrame - startFrame) / fps,\n );\n const padStart = (asset.startInVideo / fps) * 1000;\n const assetSampleRate = await getSampleRate(\n resolvePath(outputDir, asset.src),\n );\n\n const padEnd = Math.max(\n 0,\n (assetSampleRate * (endFrame - startFrame + 1)) / fps -\n (assetSampleRate * asset.duration) / fps -\n (assetSampleRate * padStart) / 1000,\n );\n\n const atempoFilters = calculateAtempoFilters(asset.playbackRate); // atempo filter value must be >=0.5 and <=100. If the value is higher or lower, this function sets multiple atempo filters\n const resolvedPath = resolvePath(outputDir, asset.src);\n\n await new Promise<void>((resolve, reject) => {\n const audioFilters = [\n ...atempoFilters,\n `atrim=start=${trimLeft}:end=${trimRight}`,\n `apad=pad_len=${padEnd}`,\n `adelay=${padStart}|${padStart}|${padStart}`,\n `volume=${asset.volume}`,\n ].join(',');\n\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n ffmpeg(resolvedPath)\n .audioChannels(2)\n .audioCodec('pcm_s16le')\n .audioFrequency(SAMPLE_RATE)\n .outputOptions([`-af`, audioFilters])\n .on('end', () => {\n resolve();\n })\n .on('error', err => {\n console.error(\n `Error processing audio for asset key: ${asset.key}`,\n err,\n );\n reject(err);\n })\n .save(outputPath);\n });\n\n return outputPath;\n}\n\nasync function mergeAudioTracks(\n tempDir: string,\n audioFilenames: string[],\n): Promise<void> {\n return new Promise((resolve, reject) => {\n ffmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const command = ffmpeg();\n\n audioFilenames.forEach(filename => {\n command.input(filename);\n });\n\n command\n .complexFilter([\n `amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`,\n ])\n .outputOptions(['-c:a', 'pcm_s16le'])\n .on('end', () => {\n resolve();\n })\n .on('error', err => {\n console.error(`Error merging audio tracks: ${err.message}`);\n reject(err);\n })\n .save(path.join(tempDir, `audio.wav`));\n });\n}\n\nexport async function generateAudio({\n outputDir,\n tempDir,\n assets,\n startFrame,\n endFrame,\n fps,\n}: {\n outputDir: string;\n tempDir: string;\n assets: AssetInfo[][];\n startFrame: number;\n endFrame: number;\n fps: number;\n}) {\n const fullTempDir = path.join(os.tmpdir(), tempDir);\n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const assetPositions = getAssetPlacement(assets);\n const audioFilenames: string[] = [];\n\n for (const asset of assetPositions) {\n let hasAudioStream = true;\n if (asset.type !== 'audio') {\n hasAudioStream = await checkForAudioStream(\n resolvePath(outputDir, asset.src),\n );\n }\n\n if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {\n const filename = await prepareAudio(\n outputDir,\n fullTempDir,\n asset,\n startFrame,\n endFrame,\n fps,\n );\n audioFilenames.push(filename);\n }\n }\n\n if (audioFilenames.length > 0) {\n await mergeAudioTracks(fullTempDir, audioFilenames);\n }\n\n return audioFilenames;\n}\n\nexport async function mergeMedia(\n outputFilename: string,\n outputDir: string,\n tempDir: string,\n format: FfmpegExporterOptions['format'],\n) {\n const fullTempDir = path.join(os.tmpdir(), tempDir);\n await makeSureFolderExists(outputDir);\n await makeSureFolderExists(fullTempDir);\n\n const audioWavExists = fs.existsSync(path.join(fullTempDir, `audio.wav`));\n if (audioWavExists) {\n await mergeAudioWithVideo(\n path.join(fullTempDir, `audio.wav`),\n path.join(fullTempDir, `visuals.${extensions[format]}`),\n path.join(outputDir, `${outputFilename}.${extensions[format]}`),\n audioCodecs[format],\n );\n } else {\n const destination = path.join(\n outputDir,\n `${outputFilename}.${extensions[format]}`,\n );\n await fs.promises.copyFile(\n path.join(fullTempDir, `visuals.${extensions[format]}`),\n destination,\n );\n }\n if (fullTempDir.endsWith('-undefined')) {\n await fs.promises\n .rm(fullTempDir, {recursive: true, force: true})\n .catch(() => {});\n }\n}\n","import * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {v4 as uuidv4} from 'uuid';\nimport {ffmpegSettings} from './settings';\n\n// Import fluent-ffmpeg - handle both ESM and CJS\n// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires\nconst fluentFfmpeg = require('fluent-ffmpeg');\n\nexport type AudioCodec = 'aac' | 'libopus';\n\nexport function resolvePath(output: string, assetPath: string) {\n let resolvedPath: string;\n if (\n assetPath.startsWith('http://') ||\n assetPath.startsWith('https://') ||\n assetPath.startsWith('data:')\n ) {\n resolvedPath = assetPath;\n } else {\n resolvedPath = path.join(output, '../public', assetPath);\n }\n return resolvedPath;\n}\n\nexport async function makeSureFolderExists(folderPath: string) {\n if (\n await fs.promises\n .access(folderPath)\n .then(() => false)\n .catch(() => true)\n ) {\n await fs.promises.mkdir(folderPath, {recursive: true});\n }\n}\n\nexport async function concatenateMedia(\n files: string[],\n outputFile: string,\n): Promise<void> {\n const tempFile = path.join(os.tmpdir(), `${uuidv4()}.txt`);\n const fileContent = files\n .map(file => `file '${file.replace(/'/g, \"\\\\'\")}'`)\n .join('\\n');\n await fs.promises.writeFile(tempFile, fileContent);\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n const ffmpegCommand = fluentFfmpeg();\n\n ffmpegCommand\n .input(tempFile)\n .inputOptions([\n '-f concat',\n '-safe 0',\n '-protocol_whitelist file,http,https,tcp,tls',\n ])\n .outputOptions(['-c copy'])\n .on('error', (err: Error) => {\n console.error('Error:', err);\n fs.promises.unlink(tempFile).catch(console.error);\n reject(err); // Reject the promise on error\n })\n .on('end', () => {\n fs.promises.unlink(tempFile).catch(console.error);\n resolve(); // Resolve the promise on successful completion\n })\n .save(outputFile);\n });\n}\n\nexport async function createSilentAudioFile(\n filePath: string,\n duration: number,\n) {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg()\n .addInput(`anullsrc=channel_layout=stereo:sample_rate=${48000}`)\n .inputFormat('lavfi')\n .duration(duration)\n .on('end', () => {\n resolve(filePath);\n })\n .on('error', (err: Error) => {\n console.error('Error creating silent audio file:', err);\n reject(err);\n })\n .save(filePath);\n });\n}\n\nexport async function getVideoDuration(filePath: string): Promise<number> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const duration = metadata.format.duration;\n if (duration) {\n resolve(duration);\n } else {\n reject(new Error('Could not determine video duration.'));\n }\n });\n });\n}\n\nexport async function getVideoDimensions(\n filePath: string,\n): Promise<{width: number; height: number}> {\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n console.error('Error getting video dimensions:', err);\n reject(new Error('Failed to get video dimensions'));\n return;\n }\n\n const videoStream = metadata.streams.find(\n (stream: any) => stream.codec_type === 'video',\n );\n if (videoStream && videoStream.width && videoStream.height) {\n resolve({\n width: videoStream.width,\n height: videoStream.height,\n });\n }\n reject(new Error('Could not find video dimensions in metadata'));\n });\n });\n}\n\nexport async function doesFileExist(filePath: string): Promise<boolean> {\n try {\n await fs.promises.access(filePath, fs.constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function mergeAudioWithVideo(\n audioPath: string,\n videoPath: string,\n outputPath: string,\n audioCodec: AudioCodec = 'aac',\n): Promise<void> {\n fluentFfmpeg.setFfmpegPath(ffmpegSettings.getFfmpegPath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg()\n .input(videoPath)\n .input(audioPath)\n .outputOptions([\n '-c:v',\n 'copy',\n '-c:a',\n audioCodec,\n '-strict',\n 'experimental',\n ])\n .on('end', () => {\n resolve();\n })\n .on('error', (err: Error) => {\n console.error(`Error merging video and audio: ${err.message}`);\n reject(err);\n })\n .save(outputPath);\n });\n}\n\nexport async function checkForAudioStream(file: string): Promise<boolean> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(file, (err: Error | null, metadata: any) => {\n if (err) {\n console.error(`error checking for audioStream for file ${file}`, err);\n reject(err);\n return;\n }\n\n const audioStreams = metadata.streams.filter(\n (s: any) => s.codec_type === 'audio',\n );\n resolve(audioStreams.length > 0);\n });\n });\n}\n\nexport async function getSampleRate(filePath: string): Promise<number> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const audioStream = metadata.streams.find((s: any) => s.codec_type === 'audio');\n if (audioStream && audioStream.sample_rate) {\n resolve(audioStream.sample_rate);\n } else {\n reject(new Error('No audio stream found'));\n }\n });\n });\n}\n\nexport async function getVideoCodec(filePath: string) {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise<string>((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const videoStream = metadata.streams.find((s: any) => s.codec_type === 'video');\n if (videoStream && videoStream.codec_name) {\n resolve(videoStream.codec_name);\n } else {\n reject(new Error('No video stream found'));\n }\n });\n });\n}\n\nexport async function getVideoMetadata(\n filePath: string,\n): Promise<{codec: string; width: number; height: number}> {\n fluentFfmpeg.setFfprobePath(ffmpegSettings.getFfprobePath());\n\n return new Promise((resolve, reject) => {\n fluentFfmpeg.ffprobe(filePath, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n const videoStream = metadata.streams.find((s: any) => s.codec_type === 'video');\n if (\n videoStream &&\n videoStream.codec_name &&\n videoStream.width &&\n videoStream.height\n ) {\n resolve({\n codec: videoStream.codec_name,\n width: videoStream.width,\n height: videoStream.height,\n });\n } else {\n reject(new Error('Unable to retrieve complete video information'));\n }\n });\n });\n}\n","import {EventName, sendEvent} from '@twick/telemetry';\n// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires\nconst ffmpeg = require('fluent-ffmpeg');\nimport type * as FfmpegTypes from 'fluent-ffmpeg';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport {v4 as uuidv4} from 'uuid';\nimport {ffmpegSettings} from './settings';\nimport {getVideoMetadata} from './utils';\n\ntype VideoFrameExtractorState = 'processing' | 'done' | 'error';\n\n/**\n * Walks through a video file and extracts frames.\n */\nexport class VideoFrameExtractor {\n private static readonly chunkLengthInSeconds = 5;\n\n private readonly ffmpegPath = ffmpegSettings.getFfmpegPath();\n\n public state: VideoFrameExtractorState;\n public filePath: string;\n private downloadedFilePath: string;\n\n private buffer: Buffer = Buffer.alloc(0);\n private bufferOffset: number = 0;\n\n // Images are buffered in memory until they are requested.\n private imageBuffers: Buffer[] = [];\n private lastImage: Buffer | null = null;\n\n private startTime: number;\n private startTimeOffset: number;\n private duration: number;\n private toTime: number;\n private fps: number;\n private framesProcessed: number = 0;\n\n private width: number = 0;\n private height: number = 0;\n private frameSize: number = 0;\n private codec: string | null = null;\n private process: FfmpegTypes.FfmpegCommand | null = null;\n private terminated: boolean = false;\n\n public static downloadedVideoMap: Map<\n string,\n {localPath: string; startTimeOffset: number}\n > = new Map();\n\n public constructor(\n filePath: string,\n startTime: number,\n fps: number,\n duration: number,\n ) {\n this.state = 'processing';\n this.filePath = filePath;\n this.downloadedFilePath = VideoFrameExtractor.downloadedVideoMap.get(\n filePath,\n )?.localPath as string;\n this.startTimeOffset = VideoFrameExtractor.downloadedVideoMap.get(filePath)\n ?.startTimeOffset as number;\n\n this.startTime = startTime;\n this.duration = duration;\n this.toTime = this.getEndTime(this.startTime);\n this.fps = fps;\n\n getVideoMetadata(this.downloadedFilePath).then(metadata => {\n this.width = metadata.width;\n this.height = metadata.height;\n this.frameSize = this.width * this.height * 4;\n this.buffer = Buffer.alloc(this.frameSize);\n this.codec = metadata.codec;\n\n if (this.startTime >= this.duration) {\n this.process = this.createFfmpegProcessToExtractFirstFrame(\n this.downloadedFilePath,\n this.codec,\n );\n return;\n }\n\n this.process = this.createFfmpegProcess(\n this.startTime - this.startTimeOffset,\n this.toTime,\n this.downloadedFilePath,\n this.fps,\n this.codec,\n );\n });\n }\n\n public static downloadVideoChunk(\n url: string,\n startTime: number,\n endTime: number,\n ) {\n const outputDir = path.join(os.tmpdir(), `twick-decoder-chunks`);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {recursive: true});\n }\n\n return new Promise((resolve, reject) => {\n ffmpeg.ffprobe(url, (err: Error | null, metadata: any) => {\n if (err) {\n reject(err);\n return;\n }\n\n const format = metadata.format.format_name?.split(',')[-1] || 'mp4';\n const outputFileName = `chunk_${uuidv4()}.${format}`;\n const outputPath = path.join(outputDir, outputFileName);\n const toleranceInSeconds = 0.5;\n\n const adjustedStartTime = Math.max(startTime - toleranceInSeconds, 0);\n\n ffmpeg(url)\n .setFfmpegPath(ffmpegSettings.getFfmpegPath())\n .inputOptions([\n `-ss ${adjustedStartTime}`,\n `-to ${endTime + toleranceInSeconds}`,\n ])\n .outputOptions(['-c copy'])\n .output(outputPath)\n .on('end', () => {\n this.downloadedVideoMap.set(url, {\n localPath: outputPath,\n startTimeOffset: adjustedStartTime,\n });\n resolve(outputPath);\n })\n .on('error', (err: Error) => reject(err))\n .run();\n });\n });\n }\n\n public getTime() {\n return this.startTime + this.framesProcessed / this.fps;\n }\n\n public getLastTime() {\n return this.startTime + (this.framesProcessed - 1) / this.fps;\n }\n\n public getLastFrame() {\n return this.lastImage;\n }\n\n public getWidth() {\n return this.width;\n }\n\n public getHeight() {\n return this.height;\n }\n\n private getEndTime(startTime: number) {\n return Math.min(\n startTime + VideoFrameExtractor.chunkLengthInSeconds,\n this.duration,\n );\n }\n\n private getArgs(\n codec: string,\n range?: [number, number],\n fps?: number,\n ): {inputOptions: string[]; outputOptions: string[]} {\n const inputOptions = [];\n const outputOptions = [];\n\n inputOptions.push('-loglevel', ffmpegSettings.getLogLevel());\n\n if (range) {\n inputOptions.push(\n ...['-ss', range[0].toFixed(2), '-to', range[1].toFixed(2)],\n );\n }\n\n if (codec === 'vp9') {\n inputOptions.push('-vcodec', 'libvpx-vp9');\n }\n\n if (fps) {\n outputOptions.push('-vf', `fps=fps=${fps}`);\n }\n\n if (!range) {\n outputOptions.push('-vframes', '1');\n }\n\n outputOptions.push('-f', 'rawvideo');\n outputOptions.push('-pix_fmt', 'rgba');\n\n return {inputOptions, outputOptions};\n }\n\n private createFfmpegProcess(\n startTime: number,\n toTime: number,\n filePath: string,\n fps: number,\n codec: string,\n ): FfmpegTypes.FfmpegCommand {\n const {inputOptions, outputOptions} = this.getArgs(\n codec,\n [startTime, toTime],\n fps,\n );\n\n const process = ffmpeg(filePath)\n .setFfmpegPath(this.ffmpegPath)\n .inputOptions(inputOptions)\n .outputOptions(outputOptions)\n .on('end', () => {\n this.handleClose(0);\n })\n .on('error', (err: Error) => {\n this.handleError(err);\n })\n .on('stderr', (stderrLine: string) => {\n console.log(stderrLine);\n })\n .on('stdout', (stderrLine: string) => {\n console.log(stderrLine);\n });\n\n const ffstream = process.pipe();\n ffstream.on('data', (data: Buffer) => {\n this.processData(data);\n });\n\n return process;\n }\n\n /**\n * We call this in the case that the time requested is greater than the\n * duration of the video. In this case, we want to display the first frame\n * of the video.\n *\n * Note: This does NOT match the behavior of the old implementation\n * inside of 2d/src/lib/components/Video.ts. In the old implementation, the\n * last frame is shown instead of the first frame.\n */\n private createFfmpegProcessToExtractFirstFrame(\n filePath: string,\n codec: string,\n ): FfmpegTypes.FfmpegCommand {\n const {inputOptions, outputOptions} = this.getArgs(\n codec,\n undefined,\n undefined,\n );\n\n const process = ffmpeg(filePath)\n .setFfmpegPath(this.ffmpegPath)\n .inputOptions(inputOptions)\n .outputOptions(outputOptions)\n .on('end', () => {\n this.handleClose(0);\n })\n .on('error', (err: Error) => {\n this.handleError(err);\n })\n .on('stderr', (stderrLine: string) => {\n console.log(stderrLine);\n })\n .on('stdout', (stderrLine: string) => {\n console.log(stderrLine);\n });\n\n const ffstream = process.pipe();\n ffstream.on('data', (data: Buffer) => {\n this.processData(data);\n });\n\n return process;\n }\n\n private processData(data: Buffer) {\n let dataOffset = 0;\n\n while (dataOffset < data.length) {\n const remainingSpace = this.frameSize - this.bufferOffset;\n const chunkSize = Math.min(remainingSpace, data.length - dataOffset);\n\n data.copy(\n this.buffer as any,\n this.bufferOffset,\n dataOffset,\n dataOffset + chunkSize,\n );\n this.bufferOffset += chunkSize;\n dataOffset += chunkSize;\n\n // We have a complete frame\n if (this.bufferOffset === this.frameSize) {\n this.imageBuffers.push(Buffer.from(this.buffer as Uint8Array)); // Create a copy\n this.bufferOffset = 0; // Reset buffer for next frame\n }\n }\n }\n\n public async popImage() {\n if (this.imageBuffers.length) {\n const image = this.imageBuffers.shift()!;\n this.framesProcessed++;\n this.lastImage = image;\n return image;\n }\n\n if (this.state === 'error') {\n throw new Error('An error occurred while extracting the video frames.');\n }\n\n // If the video is done and there are no more frames to extract, return the last frame.\n if (this.state === 'done' && this.toTime >= this.duration) {\n return this.lastImage;\n }\n\n // If there are more frames to extract, request the next chunk.\n if (this.state === 'done') {\n this.startTime = this.toTime;\n this.toTime = Math.min(\n this.startTime + VideoFrameExtractor.chunkLengthInSeconds,\n this.duration,\n );\n\n if (!this.codec) {\n throw new Error(\n \"Can't extract frames without a codec. This error should never happen.\",\n );\n }\n\n this.process = this.createFfmpegProcess(\n this.startTime,\n this.toTime,\n this.downloadedFilePath,\n this.fps,\n this.codec,\n );\n\n this.state = 'processing';\n }\n\n while (this.imageBuffers.length < 1) {\n await new Promise(resolve => setTimeout(resolve, 50));\n }\n\n const image = this.imageBuffers.shift()!;\n this.framesProcessed++;\n this.lastImage = image;\n return image;\n }\n\n private handleClose(code: number) {\n this.state = code === 0 ? 'done' : 'error';\n }\n\n private async handleError(err: any) {\n const code = err.code;\n\n if (this.terminated) {\n return;\n }\n\n if (code === 'ENOENT') {\n sendEvent(EventName.Error, {error: 'ffmpeg-not-found'});\n throw new Error(\n 'Error: ffmpeg not found. Make sure ffmpeg is installed on your system.',\n );\n } else if (err.message.includes('SIGSEGV')) {\n sendEvent(EventName.Error, {\n error: 'ffmpeg-sigsegv',\n message: err.message,\n });\n throw new Error(\n `Error: Segmentation fault when running ffmpeg. This is a common issue on Linux, you might be able to fix it by installing nscd ('sudo apt-get install nscd'). For more information, see https://docs.re.video/common-issues/ffmpeg/`,\n );\n } else {\n await sendEvent(EventName.Error, {\n error: 'ffmpeg-error',\n message: err.message,\n });\n throw new Error(\n `An ffmpeg error occurred while fetching frames from source video ${this.filePath}: ${err}`,\n );\n }\n }\n\n public destroy() {\n this.terminated = true;\n this.process?.kill('SIGTERM');\n }\n}\n"],"mappings":"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","names":["path","ffmpeg","fs","os","path","os","path","ffmpeg","EventName","sendEvent","fs","os","path","uuidv4","ffmpeg","uuidv4","err","process","image","sendEvent","EventName"]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twick/ffmpeg",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.3",
|
|
4
4
|
"description": "Ffmpeg utilities for twick",
|
|
5
5
|
"author": "twick",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"require": "./dist/index.cjs"
|
|
16
|
-
}
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
17
18
|
},
|
|
18
19
|
"sideEffects": false,
|
|
19
20
|
"files": [
|
|
@@ -27,8 +28,8 @@
|
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
29
30
|
"@ffprobe-installer/ffprobe": "^2.0.0",
|
|
30
|
-
"@twick/core": "0.15.
|
|
31
|
-
"@twick/telemetry": "0.15.
|
|
31
|
+
"@twick/core": "0.15.3",
|
|
32
|
+
"@twick/telemetry": "0.15.3",
|
|
32
33
|
"fluent-ffmpeg": "^2.1.2",
|
|
33
34
|
"uuid": "^10.0.0"
|
|
34
35
|
},
|
|
@@ -44,5 +45,5 @@
|
|
|
44
45
|
"url": "https://github.com/ncounterspecialist/twick-base.git"
|
|
45
46
|
},
|
|
46
47
|
"bugs": "https://github.com/ncounterspecialist/twick-base/issues",
|
|
47
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "edc269e6d7f4d7d303aeec828f9962142eea16a5"
|
|
48
49
|
}
|