demofly 0.1.2 โ 0.1.4
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/commands/auth/login.js +1 -1
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/demos/delete.d.ts +3 -0
- package/dist/commands/demos/delete.d.ts.map +1 -0
- package/dist/commands/demos/delete.js +41 -0
- package/dist/commands/demos/delete.js.map +1 -0
- package/dist/commands/demos/get.d.ts +3 -0
- package/dist/commands/demos/get.d.ts.map +1 -0
- package/dist/commands/demos/get.js +30 -0
- package/dist/commands/demos/get.js.map +1 -0
- package/dist/commands/demos/index.d.ts +3 -0
- package/dist/commands/demos/index.d.ts.map +1 -0
- package/dist/commands/demos/index.js +17 -0
- package/dist/commands/demos/index.js.map +1 -0
- package/dist/commands/demos/list.d.ts +3 -0
- package/dist/commands/demos/list.d.ts.map +1 -0
- package/dist/commands/demos/list.js +117 -0
- package/dist/commands/demos/list.js.map +1 -0
- package/dist/commands/demos/open.d.ts +3 -0
- package/dist/commands/demos/open.d.ts.map +1 -0
- package/dist/commands/demos/open.js +45 -0
- package/dist/commands/demos/open.js.map +1 -0
- package/dist/commands/demos/update.d.ts +3 -0
- package/dist/commands/demos/update.d.ts.map +1 -0
- package/dist/commands/demos/update.js +31 -0
- package/dist/commands/demos/update.js.map +1 -0
- package/dist/commands/generate.d.ts +9 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +133 -18
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/push.d.ts +4 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +114 -23
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/render.d.ts +11 -0
- package/dist/commands/render.d.ts.map +1 -0
- package/dist/commands/render.js +161 -0
- package/dist/commands/render.js.map +1 -0
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/api-client.d.ts +2 -0
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +33 -1
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/audio-stitching.d.ts +64 -0
- package/dist/lib/audio-stitching.d.ts.map +1 -0
- package/dist/lib/audio-stitching.js +260 -0
- package/dist/lib/audio-stitching.js.map +1 -0
- package/dist/lib/credentials.d.ts.map +1 -1
- package/dist/lib/credentials.js +2 -0
- package/dist/lib/credentials.js.map +1 -1
- package/dist/lib/demo-scanner.d.ts +17 -0
- package/dist/lib/demo-scanner.d.ts.map +1 -0
- package/dist/lib/demo-scanner.js +70 -0
- package/dist/lib/demo-scanner.js.map +1 -0
- package/dist/lib/enhancement-client.d.ts +17 -0
- package/dist/lib/enhancement-client.d.ts.map +1 -0
- package/dist/lib/enhancement-client.js +129 -0
- package/dist/lib/enhancement-client.js.map +1 -0
- package/dist/lib/hybrid-rendering.d.ts +52 -0
- package/dist/lib/hybrid-rendering.d.ts.map +1 -0
- package/dist/lib/hybrid-rendering.js +161 -0
- package/dist/lib/hybrid-rendering.js.map +1 -0
- package/dist/lib/logger.d.ts +7 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +19 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/tts.d.ts +12 -0
- package/dist/lib/tts.d.ts.map +1 -1
- package/dist/lib/tts.js +64 -21
- package/dist/lib/tts.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Render Command
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the complete hybrid rendering pipeline:
|
|
5
|
+
* 1. Local audio generation with timing metadata
|
|
6
|
+
* 2. Optional cloud enhancement
|
|
7
|
+
* 3. Precise audio stitching and reassembly
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { generateLocalRender, prepareCloudEnhancement } from "../lib/hybrid-rendering.js";
|
|
13
|
+
import { stitchEnhancedAudio } from "../lib/audio-stitching.js";
|
|
14
|
+
import { createApiClient, requireAuth } from "../lib/api-client.js";
|
|
15
|
+
import { consumeEnhancementSSE, pollEnhancementStatus } from "../lib/enhancement-client.js";
|
|
16
|
+
async function hybridRender(projectDir, options) {
|
|
17
|
+
const transcriptPath = resolve(projectDir, "transcript.md");
|
|
18
|
+
const timingPath = resolve(projectDir, "timing.json");
|
|
19
|
+
const audioDir = resolve(projectDir, "audio");
|
|
20
|
+
// Validate inputs
|
|
21
|
+
if (!existsSync(transcriptPath)) {
|
|
22
|
+
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
23
|
+
}
|
|
24
|
+
if (!existsSync(audioDir)) {
|
|
25
|
+
mkdirSync(audioDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
console.log(`๐ฌ Starting hybrid render for ${projectDir}`);
|
|
28
|
+
console.log(`๐ Transcript: ${transcriptPath}`);
|
|
29
|
+
// Step 1: Generate local audio with timing metadata
|
|
30
|
+
console.log(`\n๐ต Step 1: Local Audio Generation`);
|
|
31
|
+
const ttsOptions = {
|
|
32
|
+
voice: options.voice,
|
|
33
|
+
speed: options.speed
|
|
34
|
+
};
|
|
35
|
+
const audioMetadata = generateLocalRender(transcriptPath, timingPath, projectDir, ttsOptions);
|
|
36
|
+
console.log(`โ
Local audio generated: ${audioMetadata.segments.length} segments, ${audioMetadata.totalDuration.toFixed(1)}s`);
|
|
37
|
+
// Determine output path
|
|
38
|
+
const outputFileName = options.output || `enhanced-audio.${options.format || 'wav'}`;
|
|
39
|
+
const outputPath = resolve(projectDir, outputFileName);
|
|
40
|
+
// Step 2: Cloud Enhancement (if enabled)
|
|
41
|
+
let enhancedTimeline = null;
|
|
42
|
+
if (options.enhance && !options.local) {
|
|
43
|
+
console.log(`\nโ๏ธ Step 2: Cloud Enhancement (${options.enhancementLevel || 'standard'})`);
|
|
44
|
+
try {
|
|
45
|
+
const token = await requireAuth();
|
|
46
|
+
const apiClient = createApiClient(token);
|
|
47
|
+
// Prepare enhancement payload
|
|
48
|
+
const payload = prepareCloudEnhancement(`project-${Date.now()}`, // Generate project ID
|
|
49
|
+
audioMetadata, ttsOptions);
|
|
50
|
+
// Submit enhancement job
|
|
51
|
+
console.log(`๐ Submitting enhancement job...`);
|
|
52
|
+
const response = await apiClient.fetch('/hybrid-rendering/enhance', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
body: JSON.stringify(payload)
|
|
56
|
+
});
|
|
57
|
+
const jobInfo = (await response.json());
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`Enhancement submission failed: ${jobInfo.error || response.statusText}`);
|
|
60
|
+
}
|
|
61
|
+
console.log(`๐ Enhancement job ${jobInfo.jobId} started`);
|
|
62
|
+
console.log(`โฑ๏ธ Estimated completion: ${(jobInfo.estimatedCompletionMs / 1000).toFixed(0)}s`);
|
|
63
|
+
// Stream progress via SSE, fall back to polling on failure
|
|
64
|
+
try {
|
|
65
|
+
const sseResult = await consumeEnhancementSSE(apiClient, jobInfo.jobId, audioMetadata.segments.length);
|
|
66
|
+
if (sseResult.status === "failed") {
|
|
67
|
+
throw new Error("Enhancement failed: all segments failed");
|
|
68
|
+
}
|
|
69
|
+
// Fetch the timeline for stitching
|
|
70
|
+
const timelineResponse = await apiClient.fetch(`/hybrid-rendering/timeline/${jobInfo.jobId}`);
|
|
71
|
+
const timeline = (await timelineResponse.json());
|
|
72
|
+
if (!timelineResponse.ok) {
|
|
73
|
+
throw new Error(`Failed to get timeline: ${timelineResponse.statusText}`);
|
|
74
|
+
}
|
|
75
|
+
enhancedTimeline = timeline;
|
|
76
|
+
}
|
|
77
|
+
catch (sseError) {
|
|
78
|
+
console.warn(`โ ๏ธ SSE streaming failed, falling back to polling:`, sseError);
|
|
79
|
+
enhancedTimeline = await pollEnhancementStatus(apiClient, jobInfo.jobId, (progress, _status) => {
|
|
80
|
+
const segments = Math.floor(progress * audioMetadata.segments.length);
|
|
81
|
+
console.log(` ๐ ${segments}/${audioMetadata.segments.length} segments processed`);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.warn(`โ ๏ธ Cloud enhancement failed, falling back to local audio:`, error);
|
|
87
|
+
options.local = true; // Force local-only rendering
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Step 3: Audio Stitching
|
|
91
|
+
console.log(`\n๐งต Step 3: Audio Assembly`);
|
|
92
|
+
if (enhancedTimeline && !options.local) {
|
|
93
|
+
// Stitch enhanced + original audio
|
|
94
|
+
console.log(`๐๏ธ Assembling enhanced audio with ${enhancedTimeline.segments.length} segments`);
|
|
95
|
+
const stitchingOptions = {
|
|
96
|
+
outputPath,
|
|
97
|
+
format: options.format,
|
|
98
|
+
qualityThreshold: options.qualityThreshold || 0.7,
|
|
99
|
+
sampleRate: 44100,
|
|
100
|
+
channels: 2
|
|
101
|
+
};
|
|
102
|
+
const stitchResult = await stitchEnhancedAudio(enhancedTimeline, audioDir, stitchingOptions);
|
|
103
|
+
if (stitchResult.success) {
|
|
104
|
+
console.log(`\n๐ Hybrid render completed!`);
|
|
105
|
+
console.log(`๐ Output: ${outputPath}`);
|
|
106
|
+
console.log(`โฑ๏ธ Duration: ${stitchResult.finalDuration.toFixed(1)}s`);
|
|
107
|
+
console.log(`๐ Quality Score: ${(stitchResult.qualityMetrics.avgQualityScore * 100).toFixed(1)}%`);
|
|
108
|
+
console.log(`๐ฏ Sync Accuracy: ${(stitchResult.syncAccuracy * 100).toFixed(1)}%`);
|
|
109
|
+
console.log(`๐ต Enhanced: ${stitchResult.segmentsUsed.enhanced}, Original: ${stitchResult.segmentsUsed.original}, Failed: ${stitchResult.segmentsUsed.failed}`);
|
|
110
|
+
if (stitchResult.segmentsUsed.enhanced > 0) {
|
|
111
|
+
const enhancementPercent = (stitchResult.segmentsUsed.enhanced / audioMetadata.segments.length) * 100;
|
|
112
|
+
console.log(`โจ ${enhancementPercent.toFixed(1)}% of audio enhanced in the cloud`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.error(`โ Audio stitching failed:`);
|
|
117
|
+
stitchResult.errors.forEach(error => console.error(` ${error}`));
|
|
118
|
+
throw new Error("Audio assembly failed");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Use local-only audio (concatenate segments)
|
|
123
|
+
console.log(`๐ Using local audio only`);
|
|
124
|
+
console.log(`๐ Audio files available in: ${audioDir}`);
|
|
125
|
+
console.log(`๐ Timing metadata: ${resolve(projectDir, 'audio-timing.json')}`);
|
|
126
|
+
// TODO: Implement simple local concatenation for local-only mode
|
|
127
|
+
// For now, just report that local audio is ready
|
|
128
|
+
console.log(`\nโ
Local render completed!`);
|
|
129
|
+
console.log(`๐ต ${audioMetadata.segments.length} audio segments generated`);
|
|
130
|
+
console.log(`โฑ๏ธ Total duration: ${audioMetadata.totalDuration.toFixed(1)}s`);
|
|
131
|
+
console.log(`๐ก Use --enhance to enable cloud quality improvement`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function registerRenderCommand(program) {
|
|
135
|
+
program.addCommand(renderCommand);
|
|
136
|
+
}
|
|
137
|
+
const renderCommand = new Command("render")
|
|
138
|
+
.description("Generate demo audio with optional cloud enhancement")
|
|
139
|
+
.argument("<project-dir>", "project directory containing transcript.md")
|
|
140
|
+
.option("-v, --voice <voice>", "TTS voice to use")
|
|
141
|
+
.option("-s, --speed <speed>", "speech speed multiplier", parseFloat, 1.0)
|
|
142
|
+
.option("-o, --output <file>", "output audio file name")
|
|
143
|
+
.option("-e, --enhance", "enable cloud enhancement", false)
|
|
144
|
+
.option("-l, --enhancement-level <level>", "enhancement quality level", "standard")
|
|
145
|
+
.option("-t, --quality-threshold <threshold>", "minimum quality for enhanced segments", parseFloat, 0.7)
|
|
146
|
+
.option("--local", "local-only mode (disable cloud enhancement)", false)
|
|
147
|
+
.option("-f, --format <format>", "output audio format", "wav")
|
|
148
|
+
.action(async (projectDir, options) => {
|
|
149
|
+
try {
|
|
150
|
+
const absoluteProjectDir = resolve(projectDir);
|
|
151
|
+
if (!existsSync(absoluteProjectDir)) {
|
|
152
|
+
throw new Error(`Project directory does not exist: ${absoluteProjectDir}`);
|
|
153
|
+
}
|
|
154
|
+
await hybridRender(absoluteProjectDir, options);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.error(`โ Render failed:`, error);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/commands/render.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ import { registerGenerateCommand } from "./commands/generate.js";
|
|
|
5
5
|
import { registerTtsCommand } from "./commands/tts.js";
|
|
6
6
|
import { registerAuthCommands } from "./commands/auth/index.js";
|
|
7
7
|
import { registerPushCommand } from "./commands/push.js";
|
|
8
|
-
import {
|
|
8
|
+
import { registerDemosCommands } from "./commands/demos/index.js";
|
|
9
|
+
import { setVerbose } from "./lib/logger.js";
|
|
9
10
|
const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
10
11
|
if (nodeVersion < 22) {
|
|
11
12
|
console.error(`demofly requires Node.js 22 or higher (found v${process.versions.node})`);
|
|
@@ -14,12 +15,18 @@ if (nodeVersion < 22) {
|
|
|
14
15
|
program
|
|
15
16
|
.name("demofly")
|
|
16
17
|
.version("0.1.1")
|
|
17
|
-
.description("CLI for demofly โ automated demo video generation")
|
|
18
|
+
.description("CLI for demofly โ automated demo video generation")
|
|
19
|
+
.option("--verbose", "Show detailed debug output");
|
|
20
|
+
program.hook("preAction", (thisCommand) => {
|
|
21
|
+
const opts = thisCommand.optsWithGlobals();
|
|
22
|
+
if (opts.verbose)
|
|
23
|
+
setVerbose(true);
|
|
24
|
+
});
|
|
18
25
|
registerInitCommand(program);
|
|
19
26
|
registerGenerateCommand(program);
|
|
20
27
|
registerTtsCommand(program);
|
|
21
28
|
registerAuthCommands(program);
|
|
22
29
|
registerPushCommand(program);
|
|
23
|
-
|
|
30
|
+
registerDemosCommands(program);
|
|
24
31
|
program.parse();
|
|
25
32
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtE,IAAI,WAAW,GAAG,EAAE,EAAE,CAAC;IACrB,OAAO,CAAC,KAAK,CACX,iDAAiD,OAAO,CAAC,QAAQ,CAAC,IAAI,GAAG,CAC1E,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,mDAAmD,CAAC;KAChE,MAAM,CAAC,WAAW,EAAE,4BAA4B,CAAC,CAAC;AAErD,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,WAAW,EAAE,EAAE;IACxC,MAAM,IAAI,GAAG,WAAW,CAAC,eAAe,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,OAAO;QAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAE/B,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export declare class ApiClient {
|
|
|
4
4
|
constructor(baseUrl: string, token: string);
|
|
5
5
|
get<T>(path: string): Promise<T>;
|
|
6
6
|
post<T>(path: string, body: unknown): Promise<T>;
|
|
7
|
+
postMultipart<T>(path: string, formData: FormData, _onProgress?: (bytes: number, total: number) => void): Promise<T>;
|
|
7
8
|
put<T>(path: string, body: unknown): Promise<T>;
|
|
8
9
|
delete(path: string): Promise<void>;
|
|
10
|
+
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
9
11
|
private request;
|
|
10
12
|
}
|
|
11
13
|
export declare function createApiClient(token: string): ApiClient;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAOA,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAAS;gBAEV,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAKpC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAIhD,aAAa,CAAC,CAAC,EACnB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,EAClB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACnD,OAAO,CAAC,CAAC,CAAC;IA6BP,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IAI/C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;YAOlD,OAAO;CAoCtB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAExD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAOnD"}
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getToken } from "./credentials.js";
|
|
2
|
-
|
|
2
|
+
import { debug } from "./logger.js";
|
|
3
|
+
const DEFAULT_BASE_URL = (process.env.DEMOFLY_URL ? `${process.env.DEMOFLY_URL}/api` : null) ||
|
|
4
|
+
"https://demofly.ai/api";
|
|
3
5
|
export class ApiClient {
|
|
4
6
|
baseUrl;
|
|
5
7
|
token;
|
|
@@ -13,12 +15,40 @@ export class ApiClient {
|
|
|
13
15
|
async post(path, body) {
|
|
14
16
|
return this.request("POST", path, body);
|
|
15
17
|
}
|
|
18
|
+
async postMultipart(path, formData, _onProgress) {
|
|
19
|
+
const url = `${this.baseUrl}${path}`;
|
|
20
|
+
const headers = {
|
|
21
|
+
Authorization: `Bearer ${this.token}`,
|
|
22
|
+
};
|
|
23
|
+
const init = { method: "POST", headers, body: formData };
|
|
24
|
+
debug(`POST ${url} (multipart)`);
|
|
25
|
+
const response = await fetch(url, init);
|
|
26
|
+
debug(`POST ${url} -> ${response.status}`);
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
throw new Error(`API request failed (${response.status}): ${text}`);
|
|
30
|
+
}
|
|
31
|
+
if (response.status === 204) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const contentType = response.headers.get("content-type");
|
|
35
|
+
if (contentType?.includes("application/json")) {
|
|
36
|
+
return (await response.json());
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
16
40
|
async put(path, body) {
|
|
17
41
|
return this.request("PUT", path, body);
|
|
18
42
|
}
|
|
19
43
|
async delete(path) {
|
|
20
44
|
await this.request("DELETE", path);
|
|
21
45
|
}
|
|
46
|
+
async fetch(path, init) {
|
|
47
|
+
const url = `${this.baseUrl}${path}`;
|
|
48
|
+
const headers = new Headers(init?.headers);
|
|
49
|
+
headers.set("Authorization", `Bearer ${this.token}`);
|
|
50
|
+
return fetch(url, { ...init, headers });
|
|
51
|
+
}
|
|
22
52
|
async request(method, path, body) {
|
|
23
53
|
const url = `${this.baseUrl}${path}`;
|
|
24
54
|
const headers = {
|
|
@@ -29,7 +59,9 @@ export class ApiClient {
|
|
|
29
59
|
if (body !== undefined) {
|
|
30
60
|
init.body = JSON.stringify(body);
|
|
31
61
|
}
|
|
62
|
+
debug(`${method} ${url}`);
|
|
32
63
|
const response = await fetch(url, init);
|
|
64
|
+
debug(`${method} ${url} -> ${response.status}`);
|
|
33
65
|
if (!response.ok) {
|
|
34
66
|
const text = await response.text();
|
|
35
67
|
throw new Error(`API request failed (${response.status}): ${text}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio Stitching Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Precisely reassemble enhanced audio clips at original timestamps,
|
|
5
|
+
* maintaining perfect synchronization with video timeline.
|
|
6
|
+
*/
|
|
7
|
+
export interface EnhancedTimeline {
|
|
8
|
+
jobId: string;
|
|
9
|
+
projectId: string;
|
|
10
|
+
totalDuration: number;
|
|
11
|
+
segments: EnhancedSegment[];
|
|
12
|
+
silenceRegions: SilenceRegion[];
|
|
13
|
+
assemblyInstructions: AssemblyInstructions;
|
|
14
|
+
}
|
|
15
|
+
export interface EnhancedSegment {
|
|
16
|
+
id: string;
|
|
17
|
+
startTime: number;
|
|
18
|
+
endTime: number;
|
|
19
|
+
originalDuration: number;
|
|
20
|
+
enhancedDuration: number;
|
|
21
|
+
audioUrl: string;
|
|
22
|
+
status: "success" | "fallback" | "failed";
|
|
23
|
+
qualityScore: number;
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
26
|
+
export interface SilenceRegion {
|
|
27
|
+
startTime: number;
|
|
28
|
+
endTime: number;
|
|
29
|
+
type: "natural_pause" | "scene_transition" | "breathing";
|
|
30
|
+
}
|
|
31
|
+
export interface AssemblyInstructions {
|
|
32
|
+
method: "timestamp_placement";
|
|
33
|
+
crossFadeDurationMs: number;
|
|
34
|
+
toleranceMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface StitchingOptions {
|
|
37
|
+
outputPath: string;
|
|
38
|
+
sampleRate?: number;
|
|
39
|
+
channels?: number;
|
|
40
|
+
format?: "wav" | "mp3" | "flac";
|
|
41
|
+
qualityThreshold?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface StitchingResult {
|
|
44
|
+
success: boolean;
|
|
45
|
+
outputPath: string;
|
|
46
|
+
finalDuration: number;
|
|
47
|
+
segmentsUsed: {
|
|
48
|
+
enhanced: number;
|
|
49
|
+
original: number;
|
|
50
|
+
failed: number;
|
|
51
|
+
};
|
|
52
|
+
syncAccuracy: number;
|
|
53
|
+
qualityMetrics: {
|
|
54
|
+
avgQualityScore: number;
|
|
55
|
+
maxDriftMs: number;
|
|
56
|
+
totalArtifacts: number;
|
|
57
|
+
};
|
|
58
|
+
errors: string[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Main audio stitching function
|
|
62
|
+
*/
|
|
63
|
+
export declare function stitchEnhancedAudio(timeline: EnhancedTimeline, audioDir: string, options: StitchingOptions): Promise<StitchingResult>;
|
|
64
|
+
//# sourceMappingURL=audio-stitching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-stitching.d.ts","sourceRoot":"","sources":["../../src/lib/audio-stitching.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,oBAAoB,EAAE,oBAAoB,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,eAAe,GAAG,kBAAkB,GAAG,WAAW,CAAC;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE;QACd,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAqPD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAqE1B"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio Stitching Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Precisely reassemble enhanced audio clips at original timestamps,
|
|
5
|
+
* maintaining perfect synchronization with video timeline.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
/**
|
|
11
|
+
* Download enhanced audio files from cloud service
|
|
12
|
+
*/
|
|
13
|
+
async function downloadEnhancedAudio(segments, audioDir) {
|
|
14
|
+
const downloadedPaths = new Map();
|
|
15
|
+
for (const segment of segments) {
|
|
16
|
+
if (segment.audioUrl && segment.status === "success") {
|
|
17
|
+
const enhancedPath = resolve(audioDir, `enhanced_${segment.id}.mp3`);
|
|
18
|
+
try {
|
|
19
|
+
console.log(`โฌ๏ธ Downloading ${segment.id}...`);
|
|
20
|
+
// Download enhanced audio file
|
|
21
|
+
const response = await fetch(segment.audioUrl);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const audioData = await response.arrayBuffer();
|
|
26
|
+
writeFileSync(enhancedPath, Buffer.from(audioData));
|
|
27
|
+
downloadedPaths.set(segment.id, enhancedPath);
|
|
28
|
+
console.log(` โ
Downloaded ${segment.id} (${audioData.byteLength} bytes)`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(` โ Failed to download ${segment.id}:`, error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return downloadedPaths;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generate FFmpeg filter complex for precise audio assembly
|
|
39
|
+
*/
|
|
40
|
+
function generateFFmpegFilter(timeline, downloadedPaths, audioDir, options) {
|
|
41
|
+
const inputFiles = [];
|
|
42
|
+
const filters = [];
|
|
43
|
+
const qualityThreshold = options.qualityThreshold ?? 0.7;
|
|
44
|
+
let inputIndex = 0;
|
|
45
|
+
const segmentInputs = new Map();
|
|
46
|
+
// Add input files and create segment filters
|
|
47
|
+
for (const segment of timeline.segments) {
|
|
48
|
+
let audioPath;
|
|
49
|
+
let useEnhanced = false;
|
|
50
|
+
// Decide whether to use enhanced or original audio
|
|
51
|
+
if (downloadedPaths.has(segment.id) &&
|
|
52
|
+
segment.status === "success" &&
|
|
53
|
+
segment.qualityScore >= qualityThreshold) {
|
|
54
|
+
audioPath = downloadedPaths.get(segment.id);
|
|
55
|
+
useEnhanced = true;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Fallback to original audio
|
|
59
|
+
audioPath = resolve(audioDir, `${segment.id}.wav`);
|
|
60
|
+
if (!existsSync(audioPath)) {
|
|
61
|
+
// Try alternate naming patterns
|
|
62
|
+
const altPath = resolve(audioDir, `${segment.id.split('_')[0]}.wav`);
|
|
63
|
+
if (existsSync(altPath)) {
|
|
64
|
+
audioPath = altPath;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.warn(`โ ๏ธ Original audio not found for ${segment.id}, skipping`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
inputFiles.push(audioPath);
|
|
73
|
+
segmentInputs.set(segment.id, inputIndex);
|
|
74
|
+
// Apply time stretching if enhanced duration differs significantly from target
|
|
75
|
+
const targetDuration = segment.endTime - segment.startTime;
|
|
76
|
+
const actualDuration = useEnhanced ? segment.enhancedDuration : segment.originalDuration;
|
|
77
|
+
const driftMs = Math.abs((actualDuration - targetDuration) * 1000);
|
|
78
|
+
if (driftMs > timeline.assemblyInstructions.toleranceMs) {
|
|
79
|
+
// Time-stretch to match original timing
|
|
80
|
+
const stretchRatio = targetDuration / actualDuration;
|
|
81
|
+
filters.push(`[${inputIndex}:0]atempo=${stretchRatio.toFixed(6)}[seg${inputIndex}]`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
filters.push(`[${inputIndex}:0]anull[seg${inputIndex}]`);
|
|
85
|
+
}
|
|
86
|
+
inputIndex++;
|
|
87
|
+
}
|
|
88
|
+
// Generate silence for gaps
|
|
89
|
+
const silenceFilters = [];
|
|
90
|
+
for (let i = 0; i < timeline.silenceRegions.length; i++) {
|
|
91
|
+
const silence = timeline.silenceRegions[i];
|
|
92
|
+
const durationS = silence.endTime - silence.startTime;
|
|
93
|
+
silenceFilters.push(`aevalsrc=0:d=${durationS.toFixed(3)}[silence${i}]`);
|
|
94
|
+
}
|
|
95
|
+
// Combine all segments with precise timing
|
|
96
|
+
const mixInputs = [];
|
|
97
|
+
const delayFilters = [];
|
|
98
|
+
let segIndex = 0;
|
|
99
|
+
for (const segment of timeline.segments) {
|
|
100
|
+
if (segmentInputs.has(segment.id)) {
|
|
101
|
+
const delayMs = Math.round(segment.startTime * 1000);
|
|
102
|
+
delayFilters.push(`[seg${segmentInputs.get(segment.id)}]adelay=${delayMs}[delayed${segIndex}]`);
|
|
103
|
+
mixInputs.push(`[delayed${segIndex}]`);
|
|
104
|
+
segIndex++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Add silence regions with delays
|
|
108
|
+
timeline.silenceRegions.forEach((silence, i) => {
|
|
109
|
+
const delayMs = Math.round(silence.startTime * 1000);
|
|
110
|
+
delayFilters.push(`[silence${i}]adelay=${delayMs}[delayedsilence${i}]`);
|
|
111
|
+
mixInputs.push(`[delayedsilence${i}]`);
|
|
112
|
+
});
|
|
113
|
+
// Final mix
|
|
114
|
+
const mixFilter = `${mixInputs.join('')}amix=inputs=${mixInputs.length}:duration=longest[out]`;
|
|
115
|
+
const allFilters = [
|
|
116
|
+
...silenceFilters,
|
|
117
|
+
...filters,
|
|
118
|
+
...delayFilters,
|
|
119
|
+
mixFilter
|
|
120
|
+
];
|
|
121
|
+
return {
|
|
122
|
+
filterComplex: allFilters.join(';'),
|
|
123
|
+
inputFiles
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Execute FFmpeg command to stitch audio timeline
|
|
128
|
+
*/
|
|
129
|
+
async function executeFFmpeg(filterComplex, inputFiles, outputPath, options) {
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
const args = [
|
|
132
|
+
...inputFiles.flatMap(file => ['-i', file]),
|
|
133
|
+
'-filter_complex', filterComplex,
|
|
134
|
+
'-map', '[out]',
|
|
135
|
+
'-ar', String(options.sampleRate || 44100),
|
|
136
|
+
'-ac', String(options.channels || 2),
|
|
137
|
+
'-y', // Overwrite output
|
|
138
|
+
outputPath
|
|
139
|
+
];
|
|
140
|
+
console.log(`๐ง Running: ffmpeg ${args.join(' ')}`);
|
|
141
|
+
const ffmpeg = spawn('ffmpeg', args);
|
|
142
|
+
let stderr = '';
|
|
143
|
+
let duration = 0;
|
|
144
|
+
ffmpeg.stderr.on('data', (data) => {
|
|
145
|
+
const chunk = data.toString();
|
|
146
|
+
stderr += chunk;
|
|
147
|
+
// Extract duration from FFmpeg output
|
|
148
|
+
const durationMatch = chunk.match(/Duration: (\d+):(\d+):(\d+\.\d+)/);
|
|
149
|
+
if (durationMatch) {
|
|
150
|
+
const [, hours, minutes, seconds] = durationMatch;
|
|
151
|
+
duration = parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseFloat(seconds);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
ffmpeg.on('close', (code) => {
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
console.log(`โ
Audio stitching completed: ${outputPath}`);
|
|
157
|
+
resolve({ success: true, duration });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
console.error(`โ FFmpeg failed with code ${code}`);
|
|
161
|
+
resolve({ success: false, duration: 0, error: stderr });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
ffmpeg.on('error', (error) => {
|
|
165
|
+
console.error(`โ Failed to spawn FFmpeg:`, error);
|
|
166
|
+
resolve({ success: false, duration: 0, error: error.message });
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Calculate sync accuracy and quality metrics
|
|
172
|
+
*/
|
|
173
|
+
function calculateMetrics(timeline, downloadedPaths) {
|
|
174
|
+
const segmentsUsed = {
|
|
175
|
+
enhanced: 0,
|
|
176
|
+
original: 0,
|
|
177
|
+
failed: 0
|
|
178
|
+
};
|
|
179
|
+
let totalQuality = 0;
|
|
180
|
+
let maxDriftMs = 0;
|
|
181
|
+
let totalArtifacts = 0;
|
|
182
|
+
for (const segment of timeline.segments) {
|
|
183
|
+
if (downloadedPaths.has(segment.id) && segment.status === "success") {
|
|
184
|
+
segmentsUsed.enhanced++;
|
|
185
|
+
totalQuality += segment.qualityScore;
|
|
186
|
+
const driftMs = Math.abs((segment.enhancedDuration - segment.originalDuration) * 1000);
|
|
187
|
+
maxDriftMs = Math.max(maxDriftMs, driftMs);
|
|
188
|
+
}
|
|
189
|
+
else if (segment.status === "fallback") {
|
|
190
|
+
segmentsUsed.original++;
|
|
191
|
+
totalQuality += 0.7; // Assume original has decent quality
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
segmentsUsed.failed++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const totalSegments = segmentsUsed.enhanced + segmentsUsed.original + segmentsUsed.failed;
|
|
198
|
+
const avgQualityScore = totalSegments > 0 ? totalQuality / totalSegments : 0;
|
|
199
|
+
return {
|
|
200
|
+
segmentsUsed,
|
|
201
|
+
qualityMetrics: {
|
|
202
|
+
avgQualityScore,
|
|
203
|
+
maxDriftMs,
|
|
204
|
+
totalArtifacts
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Main audio stitching function
|
|
210
|
+
*/
|
|
211
|
+
export async function stitchEnhancedAudio(timeline, audioDir, options) {
|
|
212
|
+
console.log(`๐งต Starting audio stitching for job ${timeline.jobId}`);
|
|
213
|
+
console.log(`๐ ${timeline.segments.length} segments, ${timeline.totalDuration.toFixed(1)}s total`);
|
|
214
|
+
const errors = [];
|
|
215
|
+
try {
|
|
216
|
+
// Download enhanced audio files
|
|
217
|
+
const downloadedPaths = await downloadEnhancedAudio(timeline.segments, audioDir);
|
|
218
|
+
console.log(`๐ฆ Downloaded ${downloadedPaths.size}/${timeline.segments.length} enhanced segments`);
|
|
219
|
+
// Generate FFmpeg filter for precise assembly
|
|
220
|
+
const { filterComplex, inputFiles } = generateFFmpegFilter(timeline, downloadedPaths, audioDir, options);
|
|
221
|
+
if (inputFiles.length === 0) {
|
|
222
|
+
throw new Error("No audio files available for stitching");
|
|
223
|
+
}
|
|
224
|
+
console.log(`๐๏ธ Using ${inputFiles.length} audio inputs`);
|
|
225
|
+
// Execute audio stitching
|
|
226
|
+
const stitchResult = await executeFFmpeg(filterComplex, inputFiles, options.outputPath, options);
|
|
227
|
+
if (!stitchResult.success) {
|
|
228
|
+
throw new Error(stitchResult.error || "FFmpeg execution failed");
|
|
229
|
+
}
|
|
230
|
+
// Calculate metrics
|
|
231
|
+
const metrics = calculateMetrics(timeline, downloadedPaths);
|
|
232
|
+
const syncAccuracy = 1.0 - (metrics.qualityMetrics.maxDriftMs / 1000); // Convert to 0-1 scale
|
|
233
|
+
console.log(`๐ Quality: ${metrics.qualityMetrics.avgQualityScore.toFixed(2)}, Sync: ${(syncAccuracy * 100).toFixed(1)}%`);
|
|
234
|
+
console.log(`๐ต Enhanced: ${metrics.segmentsUsed.enhanced}, Original: ${metrics.segmentsUsed.original}, Failed: ${metrics.segmentsUsed.failed}`);
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
outputPath: options.outputPath,
|
|
238
|
+
finalDuration: stitchResult.duration,
|
|
239
|
+
segmentsUsed: metrics.segmentsUsed,
|
|
240
|
+
syncAccuracy: Math.max(0, syncAccuracy),
|
|
241
|
+
qualityMetrics: metrics.qualityMetrics,
|
|
242
|
+
errors
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
247
|
+
console.error(`โ Audio stitching failed:`, errorMsg);
|
|
248
|
+
errors.push(errorMsg);
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
outputPath: options.outputPath,
|
|
252
|
+
finalDuration: 0,
|
|
253
|
+
segmentsUsed: { enhanced: 0, original: 0, failed: timeline.segments.length },
|
|
254
|
+
syncAccuracy: 0,
|
|
255
|
+
qualityMetrics: { avgQualityScore: 0, maxDriftMs: 0, totalArtifacts: 0 },
|
|
256
|
+
errors
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
//# sourceMappingURL=audio-stitching.js.map
|