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.
Files changed (72) hide show
  1. package/dist/commands/auth/login.js +1 -1
  2. package/dist/commands/auth/login.js.map +1 -1
  3. package/dist/commands/demos/delete.d.ts +3 -0
  4. package/dist/commands/demos/delete.d.ts.map +1 -0
  5. package/dist/commands/demos/delete.js +41 -0
  6. package/dist/commands/demos/delete.js.map +1 -0
  7. package/dist/commands/demos/get.d.ts +3 -0
  8. package/dist/commands/demos/get.d.ts.map +1 -0
  9. package/dist/commands/demos/get.js +30 -0
  10. package/dist/commands/demos/get.js.map +1 -0
  11. package/dist/commands/demos/index.d.ts +3 -0
  12. package/dist/commands/demos/index.d.ts.map +1 -0
  13. package/dist/commands/demos/index.js +17 -0
  14. package/dist/commands/demos/index.js.map +1 -0
  15. package/dist/commands/demos/list.d.ts +3 -0
  16. package/dist/commands/demos/list.d.ts.map +1 -0
  17. package/dist/commands/demos/list.js +117 -0
  18. package/dist/commands/demos/list.js.map +1 -0
  19. package/dist/commands/demos/open.d.ts +3 -0
  20. package/dist/commands/demos/open.d.ts.map +1 -0
  21. package/dist/commands/demos/open.js +45 -0
  22. package/dist/commands/demos/open.js.map +1 -0
  23. package/dist/commands/demos/update.d.ts +3 -0
  24. package/dist/commands/demos/update.d.ts.map +1 -0
  25. package/dist/commands/demos/update.js +31 -0
  26. package/dist/commands/demos/update.js.map +1 -0
  27. package/dist/commands/generate.d.ts +9 -0
  28. package/dist/commands/generate.d.ts.map +1 -1
  29. package/dist/commands/generate.js +133 -18
  30. package/dist/commands/generate.js.map +1 -1
  31. package/dist/commands/push.d.ts +4 -0
  32. package/dist/commands/push.d.ts.map +1 -1
  33. package/dist/commands/push.js +114 -23
  34. package/dist/commands/push.js.map +1 -1
  35. package/dist/commands/render.d.ts +11 -0
  36. package/dist/commands/render.d.ts.map +1 -0
  37. package/dist/commands/render.js +161 -0
  38. package/dist/commands/render.js.map +1 -0
  39. package/dist/index.js +10 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/lib/api-client.d.ts +2 -0
  42. package/dist/lib/api-client.d.ts.map +1 -1
  43. package/dist/lib/api-client.js +33 -1
  44. package/dist/lib/api-client.js.map +1 -1
  45. package/dist/lib/audio-stitching.d.ts +64 -0
  46. package/dist/lib/audio-stitching.d.ts.map +1 -0
  47. package/dist/lib/audio-stitching.js +260 -0
  48. package/dist/lib/audio-stitching.js.map +1 -0
  49. package/dist/lib/credentials.d.ts.map +1 -1
  50. package/dist/lib/credentials.js +2 -0
  51. package/dist/lib/credentials.js.map +1 -1
  52. package/dist/lib/demo-scanner.d.ts +17 -0
  53. package/dist/lib/demo-scanner.d.ts.map +1 -0
  54. package/dist/lib/demo-scanner.js +70 -0
  55. package/dist/lib/demo-scanner.js.map +1 -0
  56. package/dist/lib/enhancement-client.d.ts +17 -0
  57. package/dist/lib/enhancement-client.d.ts.map +1 -0
  58. package/dist/lib/enhancement-client.js +129 -0
  59. package/dist/lib/enhancement-client.js.map +1 -0
  60. package/dist/lib/hybrid-rendering.d.ts +52 -0
  61. package/dist/lib/hybrid-rendering.d.ts.map +1 -0
  62. package/dist/lib/hybrid-rendering.js +161 -0
  63. package/dist/lib/hybrid-rendering.js.map +1 -0
  64. package/dist/lib/logger.d.ts +7 -0
  65. package/dist/lib/logger.d.ts.map +1 -0
  66. package/dist/lib/logger.js +19 -0
  67. package/dist/lib/logger.js.map +1 -0
  68. package/dist/lib/tts.d.ts +12 -0
  69. package/dist/lib/tts.d.ts.map +1 -1
  70. package/dist/lib/tts.js +64 -21
  71. package/dist/lib/tts.js.map +1 -1
  72. 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 { registerProjectsCommands } from "./commands/projects/index.js";
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
- registerProjectsCommands(program);
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,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAExE,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,CAAC;AAEpE,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,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAElC,OAAO,CAAC,KAAK,EAAE,CAAC"}
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"}
@@ -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":"AAIA,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,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;YAI3B,OAAO;CAkCtB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAExD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAOnD"}
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"}
@@ -1,5 +1,7 @@
1
1
  import { getToken } from "./credentials.js";
2
- const DEFAULT_BASE_URL = "https://api.demofly.dev";
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;AAE5C,MAAM,gBAAgB,GAAG,yBAAyB,CAAC;AAEnD,MAAM,OAAO,SAAS;IACZ,OAAO,CAAS;IAChB,KAAK,CAAS;IAEtB,YAAY,OAAe,EAAE,KAAa;QACxC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,IAAY;QACvB,OAAO,IAAI,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,IAAY,EAAE,IAAa;QACvC,OAAO,IAAI,CAAC,OAAO,CAAI,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,IAAY,EAAE,IAAa;QACtC,OAAO,IAAI,CAAC,OAAO,CAAI,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,IAAI,CAAC,OAAO,CAAO,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,MAAc,EACd,IAAY,EACZ,IAAc;QAEd,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,CAAC;QACrC,MAAM,OAAO,GAA2B;YACtC,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;YACrC,cAAc,EAAE,kBAAkB;SACnC,CAAC;QAEF,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAExC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,SAAc,CAAC;QACxB,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;QACtC,CAAC;QAED,OAAO,SAAc,CAAC;IACxB,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,IAAI,SAAS,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,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