vidpipe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -0
- package/assets/fonts/Montserrat-Bold.ttf +0 -0
- package/assets/fonts/Montserrat-Regular.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/dist/__tests__/agents.test.d.ts +2 -0
- package/dist/__tests__/agents.test.d.ts.map +1 -0
- package/dist/__tests__/agents.test.js +434 -0
- package/dist/__tests__/agents.test.js.map +1 -0
- package/dist/__tests__/aspectRatio.test.d.ts +2 -0
- package/dist/__tests__/aspectRatio.test.d.ts.map +1 -0
- package/dist/__tests__/aspectRatio.test.js +406 -0
- package/dist/__tests__/aspectRatio.test.js.map +1 -0
- package/dist/__tests__/captionGenerator.test.d.ts +2 -0
- package/dist/__tests__/captionGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/captionGenerator.test.js +435 -0
- package/dist/__tests__/captionGenerator.test.js.map +1 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +81 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/faceDetection.test.js +372 -0
- package/dist/__tests__/faceDetection.test.js.map +1 -0
- package/dist/__tests__/ffmpegTools.test.d.ts +2 -0
- package/dist/__tests__/ffmpegTools.test.d.ts.map +1 -0
- package/dist/__tests__/ffmpegTools.test.js +464 -0
- package/dist/__tests__/ffmpegTools.test.js.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts +2 -0
- package/dist/__tests__/integration/captionBurn.test.d.ts.map +1 -0
- package/dist/__tests__/integration/captionBurn.test.js +103 -0
- package/dist/__tests__/integration/captionBurn.test.js.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts +2 -0
- package/dist/__tests__/integration/clipComposite.test.d.ts.map +1 -0
- package/dist/__tests__/integration/clipComposite.test.js +56 -0
- package/dist/__tests__/integration/clipComposite.test.js.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts +2 -0
- package/dist/__tests__/integration/faceDetection.test.d.ts.map +1 -0
- package/dist/__tests__/integration/faceDetection.test.js +85 -0
- package/dist/__tests__/integration/faceDetection.test.js.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js +88 -0
- package/dist/__tests__/integration/ffmpegPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/fixture.d.ts +19 -0
- package/dist/__tests__/integration/fixture.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.js +112 -0
- package/dist/__tests__/integration/fixture.js.map +1 -0
- package/dist/__tests__/integration/fixture.test.d.ts +2 -0
- package/dist/__tests__/integration/fixture.test.d.ts.map +1 -0
- package/dist/__tests__/integration/fixture.test.js +27 -0
- package/dist/__tests__/integration/fixture.test.js.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts +2 -0
- package/dist/__tests__/integration/realCaptions.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realCaptions.test.js +226 -0
- package/dist/__tests__/integration/realCaptions.test.js.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts +2 -0
- package/dist/__tests__/integration/realPipeline.test.d.ts.map +1 -0
- package/dist/__tests__/integration/realPipeline.test.js +210 -0
- package/dist/__tests__/integration/realPipeline.test.js.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/integration/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/integration/silenceRemoval.test.js +93 -0
- package/dist/__tests__/integration/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/pipeline.test.d.ts +2 -0
- package/dist/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.test.js +434 -0
- package/dist/__tests__/pipeline.test.js.map +1 -0
- package/dist/__tests__/services.test.d.ts +2 -0
- package/dist/__tests__/services.test.d.ts.map +1 -0
- package/dist/__tests__/services.test.js +655 -0
- package/dist/__tests__/services.test.js.map +1 -0
- package/dist/__tests__/silenceRemoval.test.d.ts +2 -0
- package/dist/__tests__/silenceRemoval.test.d.ts.map +1 -0
- package/dist/__tests__/silenceRemoval.test.js +266 -0
- package/dist/__tests__/silenceRemoval.test.js.map +1 -0
- package/dist/__tests__/singlePassEdit.test.d.ts +2 -0
- package/dist/__tests__/singlePassEdit.test.d.ts.map +1 -0
- package/dist/__tests__/singlePassEdit.test.js +321 -0
- package/dist/__tests__/singlePassEdit.test.js.map +1 -0
- package/dist/__tests__/smoke.test.d.ts +2 -0
- package/dist/__tests__/smoke.test.d.ts.map +1 -0
- package/dist/__tests__/smoke.test.js +8 -0
- package/dist/__tests__/smoke.test.js.map +1 -0
- package/dist/__tests__/utilities.test.d.ts +2 -0
- package/dist/__tests__/utilities.test.d.ts.map +1 -0
- package/dist/__tests__/utilities.test.js +268 -0
- package/dist/__tests__/utilities.test.js.map +1 -0
- package/dist/agents/BaseAgent.d.ts +52 -0
- package/dist/agents/BaseAgent.d.ts.map +1 -0
- package/dist/agents/BaseAgent.js +108 -0
- package/dist/agents/BaseAgent.js.map +1 -0
- package/dist/agents/BlogAgent.d.ts +3 -0
- package/dist/agents/BlogAgent.d.ts.map +1 -0
- package/dist/agents/BlogAgent.js +163 -0
- package/dist/agents/BlogAgent.js.map +1 -0
- package/dist/agents/ChapterAgent.d.ts +11 -0
- package/dist/agents/ChapterAgent.d.ts.map +1 -0
- package/dist/agents/ChapterAgent.js +191 -0
- package/dist/agents/ChapterAgent.js.map +1 -0
- package/dist/agents/MediumVideoAgent.d.ts +3 -0
- package/dist/agents/MediumVideoAgent.d.ts.map +1 -0
- package/dist/agents/MediumVideoAgent.js +219 -0
- package/dist/agents/MediumVideoAgent.js.map +1 -0
- package/dist/agents/ShortsAgent.d.ts +3 -0
- package/dist/agents/ShortsAgent.d.ts.map +1 -0
- package/dist/agents/ShortsAgent.js +243 -0
- package/dist/agents/ShortsAgent.js.map +1 -0
- package/dist/agents/SilenceRemovalAgent.d.ts +9 -0
- package/dist/agents/SilenceRemovalAgent.d.ts.map +1 -0
- package/dist/agents/SilenceRemovalAgent.js +208 -0
- package/dist/agents/SilenceRemovalAgent.js.map +1 -0
- package/dist/agents/SocialMediaAgent.d.ts +4 -0
- package/dist/agents/SocialMediaAgent.d.ts.map +1 -0
- package/dist/agents/SocialMediaAgent.js +248 -0
- package/dist/agents/SocialMediaAgent.js.map +1 -0
- package/dist/agents/SummaryAgent.d.ts +11 -0
- package/dist/agents/SummaryAgent.d.ts.map +1 -0
- package/dist/agents/SummaryAgent.js +333 -0
- package/dist/agents/SummaryAgent.js.map +1 -0
- package/dist/config/brand.d.ts +29 -0
- package/dist/config/brand.d.ts.map +1 -0
- package/dist/config/brand.js +83 -0
- package/dist/config/brand.js.map +1 -0
- package/dist/config/environment.d.ts +36 -0
- package/dist/config/environment.d.ts.map +1 -0
- package/dist/config/environment.js +44 -0
- package/dist/config/environment.js.map +1 -0
- package/dist/config/logger.d.ts +5 -0
- package/dist/config/logger.d.ts.map +1 -0
- package/dist/config/logger.js +13 -0
- package/dist/config/logger.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline.d.ts +57 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +287 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/services/captionGeneration.d.ts +7 -0
- package/dist/services/captionGeneration.d.ts.map +1 -0
- package/dist/services/captionGeneration.js +29 -0
- package/dist/services/captionGeneration.js.map +1 -0
- package/dist/services/fileWatcher.d.ts +19 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +120 -0
- package/dist/services/fileWatcher.js.map +1 -0
- package/dist/services/gitOperations.d.ts +3 -0
- package/dist/services/gitOperations.d.ts.map +1 -0
- package/dist/services/gitOperations.js +43 -0
- package/dist/services/gitOperations.js.map +1 -0
- package/dist/services/socialPosting.d.ts +38 -0
- package/dist/services/socialPosting.d.ts.map +1 -0
- package/dist/services/socialPosting.js +102 -0
- package/dist/services/socialPosting.js.map +1 -0
- package/dist/services/transcription.d.ts +3 -0
- package/dist/services/transcription.d.ts.map +1 -0
- package/dist/services/transcription.js +100 -0
- package/dist/services/transcription.js.map +1 -0
- package/dist/services/videoIngestion.d.ts +3 -0
- package/dist/services/videoIngestion.d.ts.map +1 -0
- package/dist/services/videoIngestion.js +103 -0
- package/dist/services/videoIngestion.js.map +1 -0
- package/dist/tools/captions/captionGenerator.d.ts +84 -0
- package/dist/tools/captions/captionGenerator.d.ts.map +1 -0
- package/dist/tools/captions/captionGenerator.js +390 -0
- package/dist/tools/captions/captionGenerator.js.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts +101 -0
- package/dist/tools/ffmpeg/aspectRatio.d.ts.map +1 -0
- package/dist/tools/ffmpeg/aspectRatio.js +338 -0
- package/dist/tools/ffmpeg/aspectRatio.js.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts +16 -0
- package/dist/tools/ffmpeg/audioExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/audioExtraction.js +86 -0
- package/dist/tools/ffmpeg/audioExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts +8 -0
- package/dist/tools/ffmpeg/captionBurning.d.ts.map +1 -0
- package/dist/tools/ffmpeg/captionBurning.js +71 -0
- package/dist/tools/ffmpeg/captionBurning.js.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts +23 -0
- package/dist/tools/ffmpeg/clipExtraction.d.ts.map +1 -0
- package/dist/tools/ffmpeg/clipExtraction.js +178 -0
- package/dist/tools/ffmpeg/clipExtraction.js.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts +127 -0
- package/dist/tools/ffmpeg/faceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/faceDetection.js +500 -0
- package/dist/tools/ffmpeg/faceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts +10 -0
- package/dist/tools/ffmpeg/frameCapture.d.ts.map +1 -0
- package/dist/tools/ffmpeg/frameCapture.js +48 -0
- package/dist/tools/ffmpeg/frameCapture.js.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts +10 -0
- package/dist/tools/ffmpeg/silenceDetection.d.ts.map +1 -0
- package/dist/tools/ffmpeg/silenceDetection.js +55 -0
- package/dist/tools/ffmpeg/silenceDetection.js.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts +25 -0
- package/dist/tools/ffmpeg/singlePassEdit.d.ts.map +1 -0
- package/dist/tools/ffmpeg/singlePassEdit.js +123 -0
- package/dist/tools/ffmpeg/singlePassEdit.js.map +1 -0
- package/dist/tools/search/exaClient.d.ts +8 -0
- package/dist/tools/search/exaClient.d.ts.map +1 -0
- package/dist/tools/search/exaClient.js +38 -0
- package/dist/tools/search/exaClient.js.map +1 -0
- package/dist/tools/whisper/whisperClient.d.ts +3 -0
- package/dist/tools/whisper/whisperClient.d.ts.map +1 -0
- package/dist/tools/whisper/whisperClient.js +77 -0
- package/dist/tools/whisper/whisperClient.js.map +1 -0
- package/dist/types/index.d.ts +305 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +44 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { isFFmpegAvailable, getVideoDuration, fileExistsWithSize } from './fixture.js';
|
|
9
|
+
import { generateSRT, generateVTT, generateStyledASS, generateStyledASSForSegment, } from '../../tools/captions/captionGenerator.js';
|
|
10
|
+
import { burnCaptions } from '../../tools/ffmpeg/captionBurning.js';
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
const ffprobePath = process.env.FFPROBE_PATH || 'ffprobe';
|
|
13
|
+
const ffmpegOk = await isFFmpegAvailable();
|
|
14
|
+
async function getStreams(videoPath) {
|
|
15
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
16
|
+
'-v', 'error', '-show_entries', 'stream=codec_type', '-of', 'csv=p=0', videoPath,
|
|
17
|
+
]);
|
|
18
|
+
const types = stdout.trim().split('\n').map((t) => t.trim());
|
|
19
|
+
return { video: types.includes('video'), audio: types.includes('audio') };
|
|
20
|
+
}
|
|
21
|
+
describe.skipIf(!ffmpegOk)('Real Video Caption Tests', () => {
|
|
22
|
+
let tempDir;
|
|
23
|
+
let transcript;
|
|
24
|
+
const fixtureDir = path.join(import.meta.dirname, 'fixtures');
|
|
25
|
+
const videoPath = path.join(fixtureDir, 'sample-speech.mp4');
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'vantr-realcap-'));
|
|
28
|
+
const raw = await fs.readFile(path.join(fixtureDir, 'sample-speech-transcript.json'), 'utf-8');
|
|
29
|
+
transcript = JSON.parse(raw);
|
|
30
|
+
}, 30000);
|
|
31
|
+
afterAll(async () => {
|
|
32
|
+
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
33
|
+
});
|
|
34
|
+
// ── 1. Caption File Generation ──────────────────────────────────────────
|
|
35
|
+
describe('Caption file generation', () => {
|
|
36
|
+
let srtPath;
|
|
37
|
+
let vttPath;
|
|
38
|
+
let assPath;
|
|
39
|
+
beforeAll(async () => {
|
|
40
|
+
srtPath = path.join(tempDir, 'captions.srt');
|
|
41
|
+
vttPath = path.join(tempDir, 'captions.vtt');
|
|
42
|
+
assPath = path.join(tempDir, 'captions.ass');
|
|
43
|
+
await fs.writeFile(srtPath, generateSRT(transcript));
|
|
44
|
+
await fs.writeFile(vttPath, generateVTT(transcript));
|
|
45
|
+
await fs.writeFile(assPath, generateStyledASS(transcript));
|
|
46
|
+
});
|
|
47
|
+
it('generates SRT, VTT, and ASS files with non-zero size', async () => {
|
|
48
|
+
for (const fp of [srtPath, vttPath, assPath]) {
|
|
49
|
+
const { exists, size } = await fileExistsWithSize(fp);
|
|
50
|
+
expect(exists).toBe(true);
|
|
51
|
+
expect(size).toBeGreaterThan(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
it('ASS file contains karaoke \\k tags via active-word font-size overrides', async () => {
|
|
55
|
+
const ass = await fs.readFile(assPath, 'utf-8');
|
|
56
|
+
// Active-word highlighting uses \fs (font-size) and \c (color) overrides
|
|
57
|
+
expect(ass).toContain('\\fs54');
|
|
58
|
+
expect(ass).toContain('\\c&H00FFFF&'); // yellow active color
|
|
59
|
+
});
|
|
60
|
+
it('ASS file has multi-line \\N separator', async () => {
|
|
61
|
+
const ass = await fs.readFile(assPath, 'utf-8');
|
|
62
|
+
expect(ass).toContain('\\N');
|
|
63
|
+
});
|
|
64
|
+
it('word count in ASS roughly matches transcript word count', async () => {
|
|
65
|
+
const ass = await fs.readFile(assPath, 'utf-8');
|
|
66
|
+
const dialogueLines = ass.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
67
|
+
// Each dialogue line shows all words in the group but highlights one;
|
|
68
|
+
// total unique words referenced should be close to transcript.words.length (81)
|
|
69
|
+
// Count unique words by looking at the last dialogue for each group
|
|
70
|
+
// Simpler: total dialogue lines >= transcript word count (one per word-state)
|
|
71
|
+
expect(dialogueLines.length).toBeGreaterThanOrEqual(transcript.words.length);
|
|
72
|
+
});
|
|
73
|
+
it('caption timestamps span from near 0s to near 31s', async () => {
|
|
74
|
+
const ass = await fs.readFile(assPath, 'utf-8');
|
|
75
|
+
const dialogueLines = ass.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
76
|
+
// Parse first dialogue start time
|
|
77
|
+
const firstMatch = dialogueLines[0].match(/Dialogue: 0,(\d+:\d{2}:\d{2}\.\d{2}),/);
|
|
78
|
+
expect(firstMatch).not.toBeNull();
|
|
79
|
+
const firstSeconds = parseAssTimestamp(firstMatch[1]);
|
|
80
|
+
expect(firstSeconds).toBeLessThan(1); // starts near 0s
|
|
81
|
+
// Parse last dialogue end time
|
|
82
|
+
const lastLine = dialogueLines[dialogueLines.length - 1];
|
|
83
|
+
const lastMatch = lastLine.match(/Dialogue: 0,\d+:\d{2}:\d{2}\.\d{2},(\d+:\d{2}:\d{2}\.\d{2}),/);
|
|
84
|
+
expect(lastMatch).not.toBeNull();
|
|
85
|
+
const lastSeconds = parseAssTimestamp(lastMatch[1]);
|
|
86
|
+
expect(lastSeconds).toBeGreaterThan(29); // ends near 31s
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// ── 2. Caption Style Variants ───────────────────────────────────────────
|
|
90
|
+
describe('Caption style variants', () => {
|
|
91
|
+
it('shorts style uses larger font sizes (54 / 42)', () => {
|
|
92
|
+
const ass = generateStyledASS(transcript, 'shorts');
|
|
93
|
+
expect(ass).toMatch(/Style: Default,Montserrat,42/);
|
|
94
|
+
expect(ass).toContain('\\fs54');
|
|
95
|
+
expect(ass).toContain('\\fs42');
|
|
96
|
+
});
|
|
97
|
+
it('medium style uses smaller font sizes (40 / 32)', () => {
|
|
98
|
+
const ass = generateStyledASS(transcript, 'medium');
|
|
99
|
+
expect(ass).toMatch(/Style: Default,Montserrat,32/);
|
|
100
|
+
expect(ass).toContain('\\fs40');
|
|
101
|
+
expect(ass).toContain('\\fs32');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
// ── 3. Speech Gap Detection ─────────────────────────────────────────────
|
|
105
|
+
describe('Speech gap detection', () => {
|
|
106
|
+
it('ASS has multiple Dialogue entries (not one giant block)', () => {
|
|
107
|
+
const ass = generateStyledASS(transcript);
|
|
108
|
+
const dialogueLines = ass.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
109
|
+
expect(dialogueLines.length).toBeGreaterThan(10);
|
|
110
|
+
});
|
|
111
|
+
it('no caption events span across detected speech gaps (>0.8s)', () => {
|
|
112
|
+
const ass = generateStyledASS(transcript);
|
|
113
|
+
const dialogueLines = ass.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
114
|
+
// Find gaps >0.8s between consecutive words in transcript
|
|
115
|
+
const gaps = [];
|
|
116
|
+
for (let i = 0; i < transcript.words.length - 1; i++) {
|
|
117
|
+
const gap = transcript.words[i + 1].start - transcript.words[i].end;
|
|
118
|
+
if (gap > 0.8) {
|
|
119
|
+
gaps.push({
|
|
120
|
+
gapStart: transcript.words[i].end,
|
|
121
|
+
gapEnd: transcript.words[i + 1].start,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
expect(gaps.length).toBeGreaterThan(0); // sanity: real speech has gaps
|
|
126
|
+
// No dialogue event should span entirely across a gap
|
|
127
|
+
for (const { gapStart, gapEnd } of gaps) {
|
|
128
|
+
for (const line of dialogueLines) {
|
|
129
|
+
const m = line.match(/Dialogue: 0,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),/);
|
|
130
|
+
if (!m)
|
|
131
|
+
continue;
|
|
132
|
+
const start = parseAssTimestamp(m[1]);
|
|
133
|
+
const end = parseAssTimestamp(m[2]);
|
|
134
|
+
// A dialogue event should not start before the gap and end after it
|
|
135
|
+
const spansGap = start < gapStart + 0.01 && end > gapEnd - 0.01;
|
|
136
|
+
expect(spansGap).toBe(false);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// ── 4. Real Caption Burning ─────────────────────────────────────────────
|
|
142
|
+
describe('Real caption burning', () => {
|
|
143
|
+
it('burns captions onto real video with valid output', async () => {
|
|
144
|
+
const assContent = generateStyledASS(transcript);
|
|
145
|
+
const assPath = path.join(tempDir, 'burn-captions.ass');
|
|
146
|
+
await fs.writeFile(assPath, assContent);
|
|
147
|
+
const outputPath = path.join(tempDir, 'burned-output.mp4');
|
|
148
|
+
await burnCaptions(videoPath, assPath, outputPath);
|
|
149
|
+
// Output exists with non-zero size
|
|
150
|
+
const { exists, size } = await fileExistsWithSize(outputPath);
|
|
151
|
+
expect(exists).toBe(true);
|
|
152
|
+
expect(size).toBeGreaterThan(0);
|
|
153
|
+
// Duration matches input (±0.5s)
|
|
154
|
+
const inputDuration = await getVideoDuration(videoPath);
|
|
155
|
+
const outputDuration = await getVideoDuration(outputPath);
|
|
156
|
+
expect(Math.abs(outputDuration - inputDuration)).toBeLessThan(0.5);
|
|
157
|
+
// Has both video and audio streams
|
|
158
|
+
const { video, audio } = await getStreams(outputPath);
|
|
159
|
+
expect(video).toBe(true);
|
|
160
|
+
expect(audio).toBe(true);
|
|
161
|
+
}, 90000);
|
|
162
|
+
});
|
|
163
|
+
// ── 5. Segment-specific Caption Generation ──────────────────────────────
|
|
164
|
+
describe('Segment-specific caption generation', () => {
|
|
165
|
+
it('generates captions only for the 5s–15s time range', () => {
|
|
166
|
+
const fullASS = generateStyledASS(transcript);
|
|
167
|
+
const segmentASS = generateStyledASSForSegment(transcript, 5, 15);
|
|
168
|
+
const fullDialogues = fullASS.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
169
|
+
const segDialogues = segmentASS.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
170
|
+
// Segment captions should have fewer dialogue lines than full
|
|
171
|
+
expect(segDialogues.length).toBeGreaterThan(0);
|
|
172
|
+
expect(segDialogues.length).toBeLessThan(fullDialogues.length);
|
|
173
|
+
// All timestamps in segment ASS should be within the buffered range
|
|
174
|
+
// buffer=1.0 → words from 4s to 16s, timestamps rebased to 0
|
|
175
|
+
for (const line of segDialogues) {
|
|
176
|
+
const m = line.match(/Dialogue: 0,(\d+:\d{2}:\d{2}\.\d{2}),(\d+:\d{2}:\d{2}\.\d{2}),/);
|
|
177
|
+
if (!m)
|
|
178
|
+
continue;
|
|
179
|
+
const start = parseAssTimestamp(m[1]);
|
|
180
|
+
const end = parseAssTimestamp(m[2]);
|
|
181
|
+
// Rebased timestamps: max duration = (15+1) - (5-1) = 12s
|
|
182
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
183
|
+
expect(end).toBeLessThanOrEqual(13);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
it('contains fewer words than full-video captions', () => {
|
|
187
|
+
const fullASS = generateStyledASS(transcript);
|
|
188
|
+
const segmentASS = generateStyledASSForSegment(transcript, 5, 15);
|
|
189
|
+
// Count unique word text tokens in dialogue lines
|
|
190
|
+
const countWords = (ass) => {
|
|
191
|
+
const dialogues = ass.split('\n').filter((l) => l.startsWith('Dialogue:'));
|
|
192
|
+
return dialogues.length; // one dialogue per word-state
|
|
193
|
+
};
|
|
194
|
+
expect(countWords(segmentASS)).toBeLessThan(countWords(fullASS));
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
// ── 6. Portrait Captions on Real Video ──────────────────────────────────
|
|
198
|
+
describe('Portrait caption burning', () => {
|
|
199
|
+
it('burns portrait captions onto real video with valid output', async () => {
|
|
200
|
+
const assContent = generateStyledASS(transcript, 'portrait');
|
|
201
|
+
const assPath = path.join(tempDir, 'portrait-captions.ass');
|
|
202
|
+
await fs.writeFile(assPath, assContent);
|
|
203
|
+
// Verify portrait header properties
|
|
204
|
+
expect(assContent).toContain('PlayResX: 1080');
|
|
205
|
+
expect(assContent).toContain('PlayResY: 1920');
|
|
206
|
+
expect(assContent).toContain('\\c&H00FF00&'); // green active
|
|
207
|
+
const outputPath = path.join(tempDir, 'portrait-burned-output.mp4');
|
|
208
|
+
await burnCaptions(videoPath, assPath, outputPath);
|
|
209
|
+
// Output exists with non-zero size
|
|
210
|
+
const { exists, size } = await fileExistsWithSize(outputPath);
|
|
211
|
+
expect(exists).toBe(true);
|
|
212
|
+
expect(size).toBeGreaterThan(0);
|
|
213
|
+
// Duration matches input (±0.5s)
|
|
214
|
+
const inputDuration = await getVideoDuration(videoPath);
|
|
215
|
+
const outputDuration = await getVideoDuration(outputPath);
|
|
216
|
+
expect(Math.abs(outputDuration - inputDuration)).toBeLessThan(0.5);
|
|
217
|
+
}, 90000);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
/** Parse ASS timestamp "H:MM:SS.cc" → seconds */
|
|
221
|
+
function parseAssTimestamp(ts) {
|
|
222
|
+
const [h, m, rest] = ts.split(':');
|
|
223
|
+
const [s, cs] = rest.split('.');
|
|
224
|
+
return parseInt(h) * 3600 + parseInt(m) * 60 + parseInt(s) + parseInt(cs) / 100;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=realCaptions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realCaptions.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/realCaptions.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realPipeline.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/realPipeline.test.ts"],"names":[],"mappings":"AAMA,OAAO,eAAe,CAAC"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { execFile } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
import { isFFmpegAvailable, getVideoDuration, fileExistsWithSize } from './fixture.js';
|
|
9
|
+
import { extractAudio } from '../../tools/ffmpeg/audioExtraction.js';
|
|
10
|
+
import { detectSilence } from '../../tools/ffmpeg/silenceDetection.js';
|
|
11
|
+
import { extractClip, extractCompositeClip } from '../../tools/ffmpeg/clipExtraction.js';
|
|
12
|
+
import { captureFrame } from '../../tools/ffmpeg/frameCapture.js';
|
|
13
|
+
import { singlePassEdit, singlePassEditAndCaption } from '../../tools/ffmpeg/singlePassEdit.js';
|
|
14
|
+
import { generateStyledASS } from '../../tools/captions/captionGenerator.js';
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
const ffprobePath = process.env.FFPROBE_PATH || 'ffprobe';
|
|
17
|
+
const ffmpegOk = await isFFmpegAvailable();
|
|
18
|
+
async function getStreamInfo(filePath) {
|
|
19
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
20
|
+
'-v', 'error', '-show_entries', 'stream=codec_type', '-of', 'csv=p=0', filePath,
|
|
21
|
+
]);
|
|
22
|
+
const types = stdout.trim().split('\n').map(s => s.trim());
|
|
23
|
+
return { hasVideo: types.includes('video'), hasAudio: types.includes('audio') };
|
|
24
|
+
}
|
|
25
|
+
async function getAudioChannels(filePath) {
|
|
26
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
27
|
+
'-v', 'error', '-select_streams', 'a:0',
|
|
28
|
+
'-show_entries', 'stream=channels', '-of', 'csv=p=0', filePath,
|
|
29
|
+
]);
|
|
30
|
+
return parseInt(stdout.trim(), 10);
|
|
31
|
+
}
|
|
32
|
+
describe.skipIf(!ffmpegOk)('Real Video Pipeline Tests', () => {
|
|
33
|
+
let tempDir;
|
|
34
|
+
const fixtureDir = path.join(import.meta.dirname, 'fixtures');
|
|
35
|
+
const videoPath = path.join(fixtureDir, 'sample-speech.mp4');
|
|
36
|
+
const transcriptPath = path.join(fixtureDir, 'sample-speech-transcript.json');
|
|
37
|
+
let transcript;
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'vantr-realpipe-'));
|
|
40
|
+
const raw = await fs.readFile(transcriptPath, 'utf-8');
|
|
41
|
+
transcript = JSON.parse(raw);
|
|
42
|
+
}, 30000);
|
|
43
|
+
afterAll(async () => {
|
|
44
|
+
await fs.rm(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
45
|
+
});
|
|
46
|
+
// ── Audio Extraction ──────────────────────────────────────────────
|
|
47
|
+
describe('Audio Extraction', () => {
|
|
48
|
+
it('extracts MP3 with non-zero size', async () => {
|
|
49
|
+
const out = path.join(tempDir, 'speech-audio.mp3');
|
|
50
|
+
await extractAudio(videoPath, out);
|
|
51
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
52
|
+
expect(exists).toBe(true);
|
|
53
|
+
expect(size).toBeGreaterThan(0);
|
|
54
|
+
}, 90000);
|
|
55
|
+
it('audio duration matches video duration (±1s)', async () => {
|
|
56
|
+
const out = path.join(tempDir, 'speech-audio-dur.mp3');
|
|
57
|
+
await extractAudio(videoPath, out);
|
|
58
|
+
const videoDur = await getVideoDuration(videoPath);
|
|
59
|
+
const audioDur = await getVideoDuration(out);
|
|
60
|
+
expect(audioDur).toBeGreaterThanOrEqual(videoDur - 1);
|
|
61
|
+
expect(audioDur).toBeLessThanOrEqual(videoDur + 1);
|
|
62
|
+
}, 90000);
|
|
63
|
+
it('audio is mono (1 channel)', async () => {
|
|
64
|
+
const out = path.join(tempDir, 'speech-audio-mono.mp3');
|
|
65
|
+
await extractAudio(videoPath, out);
|
|
66
|
+
const channels = await getAudioChannels(out);
|
|
67
|
+
expect(channels).toBe(1);
|
|
68
|
+
}, 90000);
|
|
69
|
+
});
|
|
70
|
+
// ── Silence Detection ─────────────────────────────────────────────
|
|
71
|
+
describe('Silence Detection', () => {
|
|
72
|
+
let audioPath;
|
|
73
|
+
beforeAll(async () => {
|
|
74
|
+
audioPath = path.join(tempDir, 'silence-detect-audio.mp3');
|
|
75
|
+
await extractAudio(videoPath, audioPath);
|
|
76
|
+
}, 90000);
|
|
77
|
+
it('returns an array of SilenceRegion objects', async () => {
|
|
78
|
+
const regions = await detectSilence(audioPath, 0.3, '-30dB');
|
|
79
|
+
expect(Array.isArray(regions)).toBe(true);
|
|
80
|
+
}, 90000);
|
|
81
|
+
it('each region has valid start < end and duration > 0', async () => {
|
|
82
|
+
const regions = await detectSilence(audioPath, 0.3, '-30dB');
|
|
83
|
+
for (const r of regions) {
|
|
84
|
+
expect(r.start).toBeLessThan(r.end);
|
|
85
|
+
expect(r.duration).toBeGreaterThan(0);
|
|
86
|
+
}
|
|
87
|
+
}, 90000);
|
|
88
|
+
it('regions are within the audio duration range', async () => {
|
|
89
|
+
const duration = await getVideoDuration(audioPath);
|
|
90
|
+
const regions = await detectSilence(audioPath, 0.3, '-30dB');
|
|
91
|
+
for (const r of regions) {
|
|
92
|
+
expect(r.start).toBeGreaterThanOrEqual(0);
|
|
93
|
+
expect(r.end).toBeLessThanOrEqual(duration + 0.5);
|
|
94
|
+
}
|
|
95
|
+
}, 90000);
|
|
96
|
+
it('speech audio has at least some silence between sentences', async () => {
|
|
97
|
+
const regions = await detectSilence(audioPath, 0.3, '-30dB');
|
|
98
|
+
expect(regions.length).toBeGreaterThanOrEqual(1);
|
|
99
|
+
}, 90000);
|
|
100
|
+
it('higher threshold detects more silence than lower threshold', async () => {
|
|
101
|
+
const lowRegions = await detectSilence(audioPath, 0.3, '-40dB');
|
|
102
|
+
const highRegions = await detectSilence(audioPath, 0.3, '-20dB');
|
|
103
|
+
expect(highRegions.length).toBeGreaterThanOrEqual(lowRegions.length);
|
|
104
|
+
}, 90000);
|
|
105
|
+
});
|
|
106
|
+
// ── Clip Extraction ───────────────────────────────────────────────
|
|
107
|
+
describe('Clip Extraction', () => {
|
|
108
|
+
it('extracts a 5-second clip from the middle', async () => {
|
|
109
|
+
const out = path.join(tempDir, 'clip-5s.mp4');
|
|
110
|
+
await extractClip(videoPath, 10, 15, out, 0);
|
|
111
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
112
|
+
expect(exists).toBe(true);
|
|
113
|
+
expect(size).toBeGreaterThan(0);
|
|
114
|
+
// Stream copy (-c copy) snaps to keyframes, so duration may exceed requested range
|
|
115
|
+
const dur = await getVideoDuration(out);
|
|
116
|
+
expect(dur).toBeGreaterThanOrEqual(4.5);
|
|
117
|
+
expect(dur).toBeLessThanOrEqual(7.0);
|
|
118
|
+
const streams = await getStreamInfo(out);
|
|
119
|
+
expect(streams.hasVideo).toBe(true);
|
|
120
|
+
expect(streams.hasAudio).toBe(true);
|
|
121
|
+
}, 90000);
|
|
122
|
+
});
|
|
123
|
+
// ── Composite Clip ────────────────────────────────────────────────
|
|
124
|
+
describe('Composite Clip Extraction', () => {
|
|
125
|
+
it('extracts 2 non-contiguous segments and concats', async () => {
|
|
126
|
+
const out = path.join(tempDir, 'composite-clip.mp4');
|
|
127
|
+
const segments = [
|
|
128
|
+
{ start: 2, end: 6, description: 'First segment' },
|
|
129
|
+
{ start: 14, end: 18, description: 'Second segment' },
|
|
130
|
+
];
|
|
131
|
+
await extractCompositeClip(videoPath, segments, out, 0);
|
|
132
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
133
|
+
expect(exists).toBe(true);
|
|
134
|
+
expect(size).toBeGreaterThan(0);
|
|
135
|
+
const dur = await getVideoDuration(out);
|
|
136
|
+
const expectedDur = (6 - 2) + (18 - 14); // 8s total
|
|
137
|
+
expect(dur).toBeGreaterThanOrEqual(expectedDur - 1);
|
|
138
|
+
expect(dur).toBeLessThanOrEqual(expectedDur + 1);
|
|
139
|
+
const streams = await getStreamInfo(out);
|
|
140
|
+
expect(streams.hasVideo).toBe(true);
|
|
141
|
+
expect(streams.hasAudio).toBe(true);
|
|
142
|
+
}, 90000);
|
|
143
|
+
});
|
|
144
|
+
// ── Frame Capture ─────────────────────────────────────────────────
|
|
145
|
+
describe('Frame Capture', () => {
|
|
146
|
+
it('captures frames at multiple timestamps as valid PNGs', async () => {
|
|
147
|
+
const timestamps = [1.0, 10.0, 25.0];
|
|
148
|
+
const PNG_HEADER = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
149
|
+
for (let i = 0; i < timestamps.length; i++) {
|
|
150
|
+
const out = path.join(tempDir, `frame-${i}.png`);
|
|
151
|
+
await captureFrame(videoPath, timestamps[i], out);
|
|
152
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
153
|
+
expect(exists).toBe(true);
|
|
154
|
+
expect(size).toBeGreaterThan(0);
|
|
155
|
+
const header = Buffer.alloc(4);
|
|
156
|
+
const fh = await fs.open(out, 'r');
|
|
157
|
+
await fh.read(header, 0, 4, 0);
|
|
158
|
+
await fh.close();
|
|
159
|
+
expect(header.subarray(0, 4).equals(PNG_HEADER)).toBe(true);
|
|
160
|
+
}
|
|
161
|
+
}, 90000);
|
|
162
|
+
});
|
|
163
|
+
// ── Single-Pass Edit ──────────────────────────────────────────────
|
|
164
|
+
describe('Single-Pass Edit', () => {
|
|
165
|
+
it('keeps only 2 segments with correct duration', async () => {
|
|
166
|
+
const out = path.join(tempDir, 'singlepass-edit.mp4');
|
|
167
|
+
const segments = [
|
|
168
|
+
{ start: 0, end: 5 },
|
|
169
|
+
{ start: 15, end: 20 },
|
|
170
|
+
];
|
|
171
|
+
await singlePassEdit(videoPath, segments, out);
|
|
172
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
173
|
+
expect(exists).toBe(true);
|
|
174
|
+
expect(size).toBeGreaterThan(0);
|
|
175
|
+
const dur = await getVideoDuration(out);
|
|
176
|
+
const expectedDur = (5 - 0) + (20 - 15); // 10s
|
|
177
|
+
expect(dur).toBeGreaterThanOrEqual(expectedDur - 0.5);
|
|
178
|
+
expect(dur).toBeLessThanOrEqual(expectedDur + 0.5);
|
|
179
|
+
const streams = await getStreamInfo(out);
|
|
180
|
+
expect(streams.hasVideo).toBe(true);
|
|
181
|
+
expect(streams.hasAudio).toBe(true);
|
|
182
|
+
}, 90000);
|
|
183
|
+
});
|
|
184
|
+
// ── Single-Pass Edit with Captions ────────────────────────────────
|
|
185
|
+
describe('Single-Pass Edit with Captions', () => {
|
|
186
|
+
it('trims and burns captions with matching duration', async () => {
|
|
187
|
+
const segments = [
|
|
188
|
+
{ start: 0, end: 5 },
|
|
189
|
+
{ start: 15, end: 20 },
|
|
190
|
+
];
|
|
191
|
+
// Generate ASS captions from the real transcript
|
|
192
|
+
const assContent = generateStyledASS(transcript);
|
|
193
|
+
const assPath = path.join(tempDir, 'real-captions.ass');
|
|
194
|
+
await fs.writeFile(assPath, assContent);
|
|
195
|
+
const out = path.join(tempDir, 'singlepass-captioned.mp4');
|
|
196
|
+
await singlePassEditAndCaption(videoPath, segments, assPath, out);
|
|
197
|
+
const { exists, size } = await fileExistsWithSize(out);
|
|
198
|
+
expect(exists).toBe(true);
|
|
199
|
+
expect(size).toBeGreaterThan(0);
|
|
200
|
+
const dur = await getVideoDuration(out);
|
|
201
|
+
const expectedDur = (5 - 0) + (20 - 15); // 10s
|
|
202
|
+
expect(dur).toBeGreaterThanOrEqual(expectedDur - 0.5);
|
|
203
|
+
expect(dur).toBeLessThanOrEqual(expectedDur + 0.5);
|
|
204
|
+
const streams = await getStreamInfo(out);
|
|
205
|
+
expect(streams.hasVideo).toBe(true);
|
|
206
|
+
expect(streams.hasAudio).toBe(true);
|
|
207
|
+
}, 90000);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=realPipeline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realPipeline.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/realPipeline.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silenceRemoval.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/silenceRemoval.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
|
+
import { promisify } from 'util';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { setupFixtures, cleanupFixtures, isFFmpegAvailable, fileExistsWithSize, getVideoDuration, } from './fixture.js';
|
|
6
|
+
import { singlePassEdit } from '../../tools/ffmpeg/singlePassEdit.js';
|
|
7
|
+
import { singlePassEditAndCaption } from '../../tools/ffmpeg/singlePassEdit.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const ffprobePath = process.env.FFPROBE_PATH || 'ffprobe';
|
|
10
|
+
const ffmpegOk = await isFFmpegAvailable();
|
|
11
|
+
async function getStreams(videoPath) {
|
|
12
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
13
|
+
'-v', 'error', '-show_entries', 'stream=codec_type', '-of', 'csv=p=0', videoPath,
|
|
14
|
+
]);
|
|
15
|
+
const types = stdout.trim().split('\n').map((t) => t.trim());
|
|
16
|
+
return { video: types.includes('video'), audio: types.includes('audio') };
|
|
17
|
+
}
|
|
18
|
+
async function getCodecs(videoPath) {
|
|
19
|
+
const { stdout } = await execFileAsync(ffprobePath, [
|
|
20
|
+
'-v', 'error', '-show_entries', 'stream=codec_name,codec_type', '-of', 'csv=p=0', videoPath,
|
|
21
|
+
]);
|
|
22
|
+
const lines = stdout.trim().split('\n').map((l) => l.trim());
|
|
23
|
+
let videoCodec = '';
|
|
24
|
+
let audioCodec = '';
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const [codec, type] = line.split(',');
|
|
27
|
+
if (type === 'video')
|
|
28
|
+
videoCodec = codec;
|
|
29
|
+
if (type === 'audio')
|
|
30
|
+
audioCodec = codec;
|
|
31
|
+
}
|
|
32
|
+
return { videoCodec, audioCodec };
|
|
33
|
+
}
|
|
34
|
+
describe.skipIf(!ffmpegOk)('Silence Removal Integration', () => {
|
|
35
|
+
let fix;
|
|
36
|
+
const outputs = [];
|
|
37
|
+
beforeAll(async () => {
|
|
38
|
+
fix = await setupFixtures();
|
|
39
|
+
}, 30000);
|
|
40
|
+
afterAll(async () => {
|
|
41
|
+
await cleanupFixtures();
|
|
42
|
+
});
|
|
43
|
+
it('singlePassEdit with keep segments removes middle section', async () => {
|
|
44
|
+
const outputPath = path.join(fix.dir, 'trimmed-skip-middle.mp4');
|
|
45
|
+
outputs.push(outputPath);
|
|
46
|
+
const segments = [
|
|
47
|
+
{ start: 0, end: 2 },
|
|
48
|
+
{ start: 3, end: 5 },
|
|
49
|
+
];
|
|
50
|
+
await singlePassEdit(fix.videoPath, segments, outputPath);
|
|
51
|
+
const { exists, size } = await fileExistsWithSize(outputPath);
|
|
52
|
+
expect(exists).toBe(true);
|
|
53
|
+
expect(size).toBeGreaterThan(0);
|
|
54
|
+
const duration = await getVideoDuration(outputPath);
|
|
55
|
+
expect(duration).toBeCloseTo(4, 0);
|
|
56
|
+
}, 120000);
|
|
57
|
+
it('singlePassEdit with single segment keeps full video', async () => {
|
|
58
|
+
const outputPath = path.join(fix.dir, 'trimmed-full.mp4');
|
|
59
|
+
outputs.push(outputPath);
|
|
60
|
+
const segments = [{ start: 0, end: 5 }];
|
|
61
|
+
await singlePassEdit(fix.videoPath, segments, outputPath);
|
|
62
|
+
const { exists, size } = await fileExistsWithSize(outputPath);
|
|
63
|
+
expect(exists).toBe(true);
|
|
64
|
+
expect(size).toBeGreaterThan(0);
|
|
65
|
+
const duration = await getVideoDuration(outputPath);
|
|
66
|
+
expect(duration).toBeCloseTo(5, 0);
|
|
67
|
+
}, 120000);
|
|
68
|
+
it('singlePassEditAndCaption trims and burns captions', async () => {
|
|
69
|
+
const outputPath = path.join(fix.dir, 'trimmed-captioned.mp4');
|
|
70
|
+
outputs.push(outputPath);
|
|
71
|
+
const segments = [
|
|
72
|
+
{ start: 0, end: 2.5 },
|
|
73
|
+
{ start: 3, end: 5 },
|
|
74
|
+
];
|
|
75
|
+
await singlePassEditAndCaption(fix.videoPath, segments, fix.assPath, outputPath);
|
|
76
|
+
const { exists, size } = await fileExistsWithSize(outputPath);
|
|
77
|
+
expect(exists).toBe(true);
|
|
78
|
+
expect(size).toBeGreaterThan(0);
|
|
79
|
+
const duration = await getVideoDuration(outputPath);
|
|
80
|
+
expect(duration).toBeCloseTo(4.5, 0);
|
|
81
|
+
}, 120000);
|
|
82
|
+
it('all outputs are valid h264/aac video files', async () => {
|
|
83
|
+
for (const videoPath of outputs) {
|
|
84
|
+
const { video, audio } = await getStreams(videoPath);
|
|
85
|
+
expect(video).toBe(true);
|
|
86
|
+
expect(audio).toBe(true);
|
|
87
|
+
const { videoCodec, audioCodec } = await getCodecs(videoPath);
|
|
88
|
+
expect(videoCodec).toBe('h264');
|
|
89
|
+
expect(audioCodec).toBe('aac');
|
|
90
|
+
}
|
|
91
|
+
}, 120000);
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=silenceRemoval.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silenceRemoval.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/silenceRemoval.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pipeline.test.ts"],"names":[],"mappings":""}
|