@juspay/neurolink 9.55.9 → 9.55.10
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/CHANGELOG.md +2 -0
- package/dist/browser/neurolink.min.js +507 -378
- package/dist/lib/processors/media/AudioProcessor.js +22 -3
- package/dist/lib/processors/media/VideoProcessor.js +48 -11
- package/dist/lib/types/processor.d.ts +27 -0
- package/dist/processors/media/AudioProcessor.js +22 -3
- package/dist/processors/media/VideoProcessor.js +48 -11
- package/dist/types/processor.d.ts +27 -0
- package/package.json +4 -4
|
@@ -36,10 +36,27 @@
|
|
|
36
36
|
* }
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
import { parseBuffer, selectCover } from "music-metadata";
|
|
40
39
|
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
|
|
41
40
|
import { SIZE_LIMITS_MB } from "../config/index.js";
|
|
42
41
|
import { FileErrorCode } from "../errors/index.js";
|
|
42
|
+
let _musicMetadata = null;
|
|
43
|
+
async function loadMusicMetadata() {
|
|
44
|
+
if (_musicMetadata) {
|
|
45
|
+
return _musicMetadata;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
_musicMetadata = await import(/* @vite-ignore */ "music-metadata");
|
|
49
|
+
return _musicMetadata;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const e = err instanceof Error ? err : null;
|
|
53
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
54
|
+
e.message.includes("music-metadata")) {
|
|
55
|
+
throw new Error('Audio processing requires the "music-metadata" package. Install it with:\n pnpm add music-metadata', { cause: err });
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
// =============================================================================
|
|
44
61
|
// TYPES
|
|
45
62
|
// =============================================================================
|
|
@@ -239,7 +256,7 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
239
256
|
// Step 5: Extract tags from common metadata
|
|
240
257
|
const tags = this.extractTags(audioMetadata);
|
|
241
258
|
// Step 6: Extract embedded cover art if present
|
|
242
|
-
const coverArt = this.extractCoverArt(audioMetadata);
|
|
259
|
+
const coverArt = await this.extractCoverArt(audioMetadata);
|
|
243
260
|
// Step 7: Attempt transcription if API key is available
|
|
244
261
|
const filename = this.getFilename(fileInfo);
|
|
245
262
|
const transcriptionResult = await this.attemptTranscription(buffer, filename, fileInfo.mimetype);
|
|
@@ -404,6 +421,7 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
404
421
|
// parseBuffer accepts (Uint8Array, fileInfo?: IFileInfo | string, options?)
|
|
405
422
|
// where string is interpreted as MIME type.
|
|
406
423
|
const mimeType = fileInfo.mimetype || undefined;
|
|
424
|
+
const { parseBuffer } = await loadMusicMetadata();
|
|
407
425
|
return parseBuffer(buffer, mimeType);
|
|
408
426
|
}
|
|
409
427
|
/**
|
|
@@ -467,11 +485,12 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
467
485
|
* @param audioMetadata - Parsed audio metadata from music-metadata
|
|
468
486
|
* @returns Cover art as Buffer, or null if no cover art is embedded
|
|
469
487
|
*/
|
|
470
|
-
extractCoverArt(audioMetadata) {
|
|
488
|
+
async extractCoverArt(audioMetadata) {
|
|
471
489
|
const pictures = audioMetadata.common.picture;
|
|
472
490
|
if (!pictures || pictures.length === 0) {
|
|
473
491
|
return null;
|
|
474
492
|
}
|
|
493
|
+
const { selectCover } = await loadMusicMetadata();
|
|
475
494
|
const cover = selectCover(pictures);
|
|
476
495
|
if (!cover) {
|
|
477
496
|
return null;
|
|
@@ -44,9 +44,7 @@
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
import { randomUUID } from "crypto";
|
|
47
|
-
import ffmpegCommand from "fluent-ffmpeg";
|
|
48
47
|
import { createWriteStream, existsSync, promises as fs } from "fs";
|
|
49
|
-
import { Input, FilePathSource, ALL_FORMATS } from "mediabunny";
|
|
50
48
|
import { tmpdir } from "os";
|
|
51
49
|
import { join } from "path";
|
|
52
50
|
import { Readable } from "stream";
|
|
@@ -56,6 +54,40 @@ import { SIZE_LIMITS_MB } from "../config/index.js";
|
|
|
56
54
|
import { FileErrorCode } from "../errors/index.js";
|
|
57
55
|
import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
|
|
58
56
|
import { logger } from "../../utils/logger.js";
|
|
57
|
+
// fluent-ffmpeg's default export is callable + has static methods — avoid caching
|
|
58
|
+
// the module type (it confuses TS); Node's module cache handles dedup.
|
|
59
|
+
async function loadFluentFfmpeg() {
|
|
60
|
+
try {
|
|
61
|
+
const mod = await import(/* @vite-ignore */ "fluent-ffmpeg");
|
|
62
|
+
return mod.default;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const e = err instanceof Error ? err : null;
|
|
66
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
67
|
+
e.message.includes("fluent-ffmpeg")) {
|
|
68
|
+
throw new Error('Video processing requires the "fluent-ffmpeg" package. Install it with:\n pnpm add fluent-ffmpeg', { cause: err });
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let _mediabunny = null;
|
|
74
|
+
async function loadMediaBunny() {
|
|
75
|
+
if (_mediabunny) {
|
|
76
|
+
return _mediabunny;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
_mediabunny = await import(/* @vite-ignore */ "mediabunny");
|
|
80
|
+
return _mediabunny;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const e = err instanceof Error ? err : null;
|
|
84
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
85
|
+
e.message.includes("mediabunny")) {
|
|
86
|
+
throw new Error('Video processing requires the "mediabunny" package. Install it with:\n pnpm add mediabunny', { cause: err });
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
59
91
|
// =============================================================================
|
|
60
92
|
// FFMPEG PATH INITIALIZATION
|
|
61
93
|
// =============================================================================
|
|
@@ -90,7 +122,8 @@ async function initFfmpegPaths() {
|
|
|
90
122
|
const ffmpegStatic = await import("ffmpeg-static");
|
|
91
123
|
const ffmpegPath = ffmpegStatic.default;
|
|
92
124
|
if (typeof ffmpegPath === "string" && existsSync(ffmpegPath)) {
|
|
93
|
-
|
|
125
|
+
const ff = await loadFluentFfmpeg();
|
|
126
|
+
ff.setFfmpegPath(ffmpegPath);
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
129
|
catch {
|
|
@@ -469,7 +502,8 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
469
502
|
* @param filePath - Path to the video file
|
|
470
503
|
* @returns Success result with probe data or error message
|
|
471
504
|
*/
|
|
472
|
-
probeVideo(filePath) {
|
|
505
|
+
async probeVideo(filePath) {
|
|
506
|
+
const ffmpeg = await loadFluentFfmpeg();
|
|
473
507
|
return new Promise((resolve) => {
|
|
474
508
|
const timeoutId = setTimeout(() => {
|
|
475
509
|
resolve({
|
|
@@ -477,7 +511,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
477
511
|
error: `ffprobe timed out after ${VIDEO_CONFIG.FFPROBE_TIMEOUT_MS}ms`,
|
|
478
512
|
});
|
|
479
513
|
}, VIDEO_CONFIG.FFPROBE_TIMEOUT_MS);
|
|
480
|
-
|
|
514
|
+
ffmpeg.ffprobe(filePath, (err, data) => {
|
|
481
515
|
clearTimeout(timeoutId);
|
|
482
516
|
if (err) {
|
|
483
517
|
resolve({
|
|
@@ -496,11 +530,12 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
496
530
|
* Falls back to ffprobe if mediabunny fails or doesn't support the format.
|
|
497
531
|
*/
|
|
498
532
|
async probeVideoWithMediabunny(filePath) {
|
|
533
|
+
const mb = await loadMediaBunny();
|
|
499
534
|
let input;
|
|
500
535
|
try {
|
|
501
|
-
input = new Input({
|
|
502
|
-
source: new FilePathSource(filePath),
|
|
503
|
-
formats: [...ALL_FORMATS],
|
|
536
|
+
input = new mb.Input({
|
|
537
|
+
source: new mb.FilePathSource(filePath),
|
|
538
|
+
formats: [...mb.ALL_FORMATS],
|
|
504
539
|
});
|
|
505
540
|
const duration = await input.computeDuration();
|
|
506
541
|
const videoTrack = await input.getPrimaryVideoTrack();
|
|
@@ -671,7 +706,8 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
671
706
|
* @param outputDir - Directory to write frame files
|
|
672
707
|
* @param timestamps - Array of timestamps in seconds
|
|
673
708
|
*/
|
|
674
|
-
runFfmpegFrameExtraction(videoPath, outputDir, timestamps, intervalSec) {
|
|
709
|
+
async runFfmpegFrameExtraction(videoPath, outputDir, timestamps, intervalSec) {
|
|
710
|
+
const ff = await loadFluentFfmpeg();
|
|
675
711
|
return new Promise((resolve, reject) => {
|
|
676
712
|
// Improved select expression to pick exactly one frame per interval
|
|
677
713
|
// instead of multiple frames within a 0.5s window.
|
|
@@ -679,7 +715,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
679
715
|
const timeoutId = setTimeout(() => {
|
|
680
716
|
reject(new Error(`ffmpeg frame extraction timed out after ${VIDEO_CONFIG.FFMPEG_TIMEOUT_MS}ms`));
|
|
681
717
|
}, VIDEO_CONFIG.FFMPEG_TIMEOUT_MS);
|
|
682
|
-
|
|
718
|
+
ff(videoPath)
|
|
683
719
|
.outputOptions([
|
|
684
720
|
"-vf",
|
|
685
721
|
`select='${selectExpr}',scale='min(${VIDEO_CONFIG.FRAME_MAX_DIMENSION}\\,iw):-2'`,
|
|
@@ -740,11 +776,12 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
740
776
|
*/
|
|
741
777
|
async extractSubtitles(videoPath, tempDir) {
|
|
742
778
|
const subtitlePath = join(tempDir, "subtitles.srt");
|
|
779
|
+
const ffSub = await loadFluentFfmpeg();
|
|
743
780
|
await new Promise((resolve, reject) => {
|
|
744
781
|
const timeoutId = setTimeout(() => {
|
|
745
782
|
reject(new Error(`ffmpeg subtitle extraction timed out after ${VIDEO_CONFIG.FFMPEG_TIMEOUT_MS}ms`));
|
|
746
783
|
}, VIDEO_CONFIG.FFMPEG_TIMEOUT_MS);
|
|
747
|
-
|
|
784
|
+
ffSub(videoPath)
|
|
748
785
|
.outputOptions(["-map", "0:s:0", "-c:s", "srt"])
|
|
749
786
|
.output(subtitlePath)
|
|
750
787
|
.on("end", () => {
|
|
@@ -543,6 +543,33 @@ export type ProcessedYaml = ProcessedFileBase & {
|
|
|
543
543
|
/** YAML content converted to JSON string for AI consumption */
|
|
544
544
|
asJson: string | null;
|
|
545
545
|
};
|
|
546
|
+
/**
|
|
547
|
+
* Structural types for fluent-ffmpeg probe data.
|
|
548
|
+
* Defined here so the optional fluent-ffmpeg package is not required at typecheck time.
|
|
549
|
+
*/
|
|
550
|
+
export type FfprobeStream = {
|
|
551
|
+
codec_type?: string;
|
|
552
|
+
codec_name?: string;
|
|
553
|
+
width?: number;
|
|
554
|
+
height?: number;
|
|
555
|
+
r_frame_rate?: string;
|
|
556
|
+
avg_frame_rate?: string;
|
|
557
|
+
bit_rate?: number | string;
|
|
558
|
+
channels?: number;
|
|
559
|
+
sample_rate?: number | string;
|
|
560
|
+
tags?: Record<string, string | number>;
|
|
561
|
+
[key: string]: unknown;
|
|
562
|
+
};
|
|
563
|
+
export type FfprobeData = {
|
|
564
|
+
streams: FfprobeStream[];
|
|
565
|
+
format: {
|
|
566
|
+
duration?: number;
|
|
567
|
+
size?: number | string;
|
|
568
|
+
bit_rate?: number | string;
|
|
569
|
+
tags?: Record<string, string | number>;
|
|
570
|
+
[key: string]: unknown;
|
|
571
|
+
};
|
|
572
|
+
};
|
|
546
573
|
/**
|
|
547
574
|
* Structural types for exceljs objects.
|
|
548
575
|
* Defined here so the optional exceljs package is not required at typecheck time.
|
|
@@ -36,10 +36,27 @@
|
|
|
36
36
|
* }
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
import { parseBuffer, selectCover } from "music-metadata";
|
|
40
39
|
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
|
|
41
40
|
import { SIZE_LIMITS_MB } from "../config/index.js";
|
|
42
41
|
import { FileErrorCode } from "../errors/index.js";
|
|
42
|
+
let _musicMetadata = null;
|
|
43
|
+
async function loadMusicMetadata() {
|
|
44
|
+
if (_musicMetadata) {
|
|
45
|
+
return _musicMetadata;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
_musicMetadata = await import(/* @vite-ignore */ "music-metadata");
|
|
49
|
+
return _musicMetadata;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const e = err instanceof Error ? err : null;
|
|
53
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
54
|
+
e.message.includes("music-metadata")) {
|
|
55
|
+
throw new Error('Audio processing requires the "music-metadata" package. Install it with:\n pnpm add music-metadata', { cause: err });
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
// =============================================================================
|
|
44
61
|
// TYPES
|
|
45
62
|
// =============================================================================
|
|
@@ -239,7 +256,7 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
239
256
|
// Step 5: Extract tags from common metadata
|
|
240
257
|
const tags = this.extractTags(audioMetadata);
|
|
241
258
|
// Step 6: Extract embedded cover art if present
|
|
242
|
-
const coverArt = this.extractCoverArt(audioMetadata);
|
|
259
|
+
const coverArt = await this.extractCoverArt(audioMetadata);
|
|
243
260
|
// Step 7: Attempt transcription if API key is available
|
|
244
261
|
const filename = this.getFilename(fileInfo);
|
|
245
262
|
const transcriptionResult = await this.attemptTranscription(buffer, filename, fileInfo.mimetype);
|
|
@@ -404,6 +421,7 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
404
421
|
// parseBuffer accepts (Uint8Array, fileInfo?: IFileInfo | string, options?)
|
|
405
422
|
// where string is interpreted as MIME type.
|
|
406
423
|
const mimeType = fileInfo.mimetype || undefined;
|
|
424
|
+
const { parseBuffer } = await loadMusicMetadata();
|
|
407
425
|
return parseBuffer(buffer, mimeType);
|
|
408
426
|
}
|
|
409
427
|
/**
|
|
@@ -467,11 +485,12 @@ export class AudioProcessor extends BaseFileProcessor {
|
|
|
467
485
|
* @param audioMetadata - Parsed audio metadata from music-metadata
|
|
468
486
|
* @returns Cover art as Buffer, or null if no cover art is embedded
|
|
469
487
|
*/
|
|
470
|
-
extractCoverArt(audioMetadata) {
|
|
488
|
+
async extractCoverArt(audioMetadata) {
|
|
471
489
|
const pictures = audioMetadata.common.picture;
|
|
472
490
|
if (!pictures || pictures.length === 0) {
|
|
473
491
|
return null;
|
|
474
492
|
}
|
|
493
|
+
const { selectCover } = await loadMusicMetadata();
|
|
475
494
|
const cover = selectCover(pictures);
|
|
476
495
|
if (!cover) {
|
|
477
496
|
return null;
|
|
@@ -44,9 +44,7 @@
|
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
import { randomUUID } from "crypto";
|
|
47
|
-
import ffmpegCommand from "fluent-ffmpeg";
|
|
48
47
|
import { createWriteStream, existsSync, promises as fs } from "fs";
|
|
49
|
-
import { Input, FilePathSource, ALL_FORMATS } from "mediabunny";
|
|
50
48
|
import { tmpdir } from "os";
|
|
51
49
|
import { join } from "path";
|
|
52
50
|
import { Readable } from "stream";
|
|
@@ -56,6 +54,40 @@ import { SIZE_LIMITS_MB } from "../config/index.js";
|
|
|
56
54
|
import { FileErrorCode } from "../errors/index.js";
|
|
57
55
|
import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
|
|
58
56
|
import { logger } from "../../utils/logger.js";
|
|
57
|
+
// fluent-ffmpeg's default export is callable + has static methods — avoid caching
|
|
58
|
+
// the module type (it confuses TS); Node's module cache handles dedup.
|
|
59
|
+
async function loadFluentFfmpeg() {
|
|
60
|
+
try {
|
|
61
|
+
const mod = await import(/* @vite-ignore */ "fluent-ffmpeg");
|
|
62
|
+
return mod.default;
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const e = err instanceof Error ? err : null;
|
|
66
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
67
|
+
e.message.includes("fluent-ffmpeg")) {
|
|
68
|
+
throw new Error('Video processing requires the "fluent-ffmpeg" package. Install it with:\n pnpm add fluent-ffmpeg', { cause: err });
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let _mediabunny = null;
|
|
74
|
+
async function loadMediaBunny() {
|
|
75
|
+
if (_mediabunny) {
|
|
76
|
+
return _mediabunny;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
_mediabunny = await import(/* @vite-ignore */ "mediabunny");
|
|
80
|
+
return _mediabunny;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const e = err instanceof Error ? err : null;
|
|
84
|
+
if (e?.code === "ERR_MODULE_NOT_FOUND" &&
|
|
85
|
+
e.message.includes("mediabunny")) {
|
|
86
|
+
throw new Error('Video processing requires the "mediabunny" package. Install it with:\n pnpm add mediabunny', { cause: err });
|
|
87
|
+
}
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
59
91
|
// =============================================================================
|
|
60
92
|
// FFMPEG PATH INITIALIZATION
|
|
61
93
|
// =============================================================================
|
|
@@ -90,7 +122,8 @@ async function initFfmpegPaths() {
|
|
|
90
122
|
const ffmpegStatic = await import("ffmpeg-static");
|
|
91
123
|
const ffmpegPath = ffmpegStatic.default;
|
|
92
124
|
if (typeof ffmpegPath === "string" && existsSync(ffmpegPath)) {
|
|
93
|
-
|
|
125
|
+
const ff = await loadFluentFfmpeg();
|
|
126
|
+
ff.setFfmpegPath(ffmpegPath);
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
129
|
catch {
|
|
@@ -469,7 +502,8 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
469
502
|
* @param filePath - Path to the video file
|
|
470
503
|
* @returns Success result with probe data or error message
|
|
471
504
|
*/
|
|
472
|
-
probeVideo(filePath) {
|
|
505
|
+
async probeVideo(filePath) {
|
|
506
|
+
const ffmpeg = await loadFluentFfmpeg();
|
|
473
507
|
return new Promise((resolve) => {
|
|
474
508
|
const timeoutId = setTimeout(() => {
|
|
475
509
|
resolve({
|
|
@@ -477,7 +511,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
477
511
|
error: `ffprobe timed out after ${VIDEO_CONFIG.FFPROBE_TIMEOUT_MS}ms`,
|
|
478
512
|
});
|
|
479
513
|
}, VIDEO_CONFIG.FFPROBE_TIMEOUT_MS);
|
|
480
|
-
|
|
514
|
+
ffmpeg.ffprobe(filePath, (err, data) => {
|
|
481
515
|
clearTimeout(timeoutId);
|
|
482
516
|
if (err) {
|
|
483
517
|
resolve({
|
|
@@ -496,11 +530,12 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
496
530
|
* Falls back to ffprobe if mediabunny fails or doesn't support the format.
|
|
497
531
|
*/
|
|
498
532
|
async probeVideoWithMediabunny(filePath) {
|
|
533
|
+
const mb = await loadMediaBunny();
|
|
499
534
|
let input;
|
|
500
535
|
try {
|
|
501
|
-
input = new Input({
|
|
502
|
-
source: new FilePathSource(filePath),
|
|
503
|
-
formats: [...ALL_FORMATS],
|
|
536
|
+
input = new mb.Input({
|
|
537
|
+
source: new mb.FilePathSource(filePath),
|
|
538
|
+
formats: [...mb.ALL_FORMATS],
|
|
504
539
|
});
|
|
505
540
|
const duration = await input.computeDuration();
|
|
506
541
|
const videoTrack = await input.getPrimaryVideoTrack();
|
|
@@ -671,7 +706,8 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
671
706
|
* @param outputDir - Directory to write frame files
|
|
672
707
|
* @param timestamps - Array of timestamps in seconds
|
|
673
708
|
*/
|
|
674
|
-
runFfmpegFrameExtraction(videoPath, outputDir, timestamps, intervalSec) {
|
|
709
|
+
async runFfmpegFrameExtraction(videoPath, outputDir, timestamps, intervalSec) {
|
|
710
|
+
const ff = await loadFluentFfmpeg();
|
|
675
711
|
return new Promise((resolve, reject) => {
|
|
676
712
|
// Improved select expression to pick exactly one frame per interval
|
|
677
713
|
// instead of multiple frames within a 0.5s window.
|
|
@@ -679,7 +715,7 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
679
715
|
const timeoutId = setTimeout(() => {
|
|
680
716
|
reject(new Error(`ffmpeg frame extraction timed out after ${VIDEO_CONFIG.FFMPEG_TIMEOUT_MS}ms`));
|
|
681
717
|
}, VIDEO_CONFIG.FFMPEG_TIMEOUT_MS);
|
|
682
|
-
|
|
718
|
+
ff(videoPath)
|
|
683
719
|
.outputOptions([
|
|
684
720
|
"-vf",
|
|
685
721
|
`select='${selectExpr}',scale='min(${VIDEO_CONFIG.FRAME_MAX_DIMENSION}\\,iw):-2'`,
|
|
@@ -740,11 +776,12 @@ export class VideoProcessor extends BaseFileProcessor {
|
|
|
740
776
|
*/
|
|
741
777
|
async extractSubtitles(videoPath, tempDir) {
|
|
742
778
|
const subtitlePath = join(tempDir, "subtitles.srt");
|
|
779
|
+
const ffSub = await loadFluentFfmpeg();
|
|
743
780
|
await new Promise((resolve, reject) => {
|
|
744
781
|
const timeoutId = setTimeout(() => {
|
|
745
782
|
reject(new Error(`ffmpeg subtitle extraction timed out after ${VIDEO_CONFIG.FFMPEG_TIMEOUT_MS}ms`));
|
|
746
783
|
}, VIDEO_CONFIG.FFMPEG_TIMEOUT_MS);
|
|
747
|
-
|
|
784
|
+
ffSub(videoPath)
|
|
748
785
|
.outputOptions(["-map", "0:s:0", "-c:s", "srt"])
|
|
749
786
|
.output(subtitlePath)
|
|
750
787
|
.on("end", () => {
|
|
@@ -543,6 +543,33 @@ export type ProcessedYaml = ProcessedFileBase & {
|
|
|
543
543
|
/** YAML content converted to JSON string for AI consumption */
|
|
544
544
|
asJson: string | null;
|
|
545
545
|
};
|
|
546
|
+
/**
|
|
547
|
+
* Structural types for fluent-ffmpeg probe data.
|
|
548
|
+
* Defined here so the optional fluent-ffmpeg package is not required at typecheck time.
|
|
549
|
+
*/
|
|
550
|
+
export type FfprobeStream = {
|
|
551
|
+
codec_type?: string;
|
|
552
|
+
codec_name?: string;
|
|
553
|
+
width?: number;
|
|
554
|
+
height?: number;
|
|
555
|
+
r_frame_rate?: string;
|
|
556
|
+
avg_frame_rate?: string;
|
|
557
|
+
bit_rate?: number | string;
|
|
558
|
+
channels?: number;
|
|
559
|
+
sample_rate?: number | string;
|
|
560
|
+
tags?: Record<string, string | number>;
|
|
561
|
+
[key: string]: unknown;
|
|
562
|
+
};
|
|
563
|
+
export type FfprobeData = {
|
|
564
|
+
streams: FfprobeStream[];
|
|
565
|
+
format: {
|
|
566
|
+
duration?: number;
|
|
567
|
+
size?: number | string;
|
|
568
|
+
bit_rate?: number | string;
|
|
569
|
+
tags?: Record<string, string | number>;
|
|
570
|
+
[key: string]: unknown;
|
|
571
|
+
};
|
|
572
|
+
};
|
|
546
573
|
/**
|
|
547
574
|
* Structural types for exceljs objects.
|
|
548
575
|
* Defined here so the optional exceljs package is not required at typecheck time.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.55.
|
|
3
|
+
"version": "9.55.10",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
|
|
6
6
|
"author": {
|
|
@@ -230,14 +230,11 @@
|
|
|
230
230
|
"croner": "^9.1.0",
|
|
231
231
|
"csv-parser": "^3.2.0",
|
|
232
232
|
"dotenv": "^17.3.1",
|
|
233
|
-
"fluent-ffmpeg": "^2.1.3",
|
|
234
233
|
"google-auth-library": "^10.6.1",
|
|
235
234
|
"hono": "^4.12.3",
|
|
236
235
|
"inquirer": "^13.3.0",
|
|
237
236
|
"jose": "^6.1.3",
|
|
238
237
|
"json-schema-to-zod": "^2.7.0",
|
|
239
|
-
"mediabunny": "^1.40.1",
|
|
240
|
-
"music-metadata": "^11.11.2",
|
|
241
238
|
"nanoid": "^5.1.5",
|
|
242
239
|
"ollama-ai-provider": "^1.2.0",
|
|
243
240
|
"open": "^11.0.0",
|
|
@@ -271,6 +268,9 @@
|
|
|
271
268
|
"@picovoice/cobra-node": "^3.0.2",
|
|
272
269
|
"bullmq": "^5.52.2",
|
|
273
270
|
"exceljs": "^4.4.0",
|
|
271
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
272
|
+
"mediabunny": "^1.40.1",
|
|
273
|
+
"music-metadata": "^11.11.2",
|
|
274
274
|
"mammoth": "^1.11.0",
|
|
275
275
|
"pptxgenjs": "^4.0.1",
|
|
276
276
|
"pdf-parse": "^2.4.5",
|