@thinkable-labs/audio-enhancer 1.0.0 → 2.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.
@@ -6,14 +6,23 @@ export interface EnhanceOptions {
6
6
  export interface EnhanceItem {
7
7
  videoPath: string;
8
8
  voiceId: string;
9
+ /** Produce the isolated→voice-change variant. Default: true. */
10
+ withIsolation?: boolean;
11
+ /** Produce the direct (skip isolation) voice-change variant. Default: true. */
12
+ withoutIsolation?: boolean;
9
13
  }
14
+ export type Variant = "isolated" | "direct";
10
15
  export type ItemStatus = "pending" | "extracting" | "isolating" | "voice-changing" | "success" | "error";
16
+ export interface VariantResult {
17
+ variant: Variant;
18
+ outputPath: string;
19
+ status: "success" | "error";
20
+ error?: string;
21
+ }
11
22
  export interface ItemResult {
12
23
  item: EnhanceItem;
13
24
  videoName: string;
14
- outputPath: string;
15
- status: ItemStatus;
16
- error?: string;
25
+ outputs: VariantResult[];
17
26
  }
18
27
  export interface BatchEnhanceResult {
19
28
  results: ItemResult[];
@@ -25,10 +34,16 @@ export type ProgressCallback = (update: {
25
34
  current: number;
26
35
  total: number;
27
36
  videoName: string;
37
+ variant?: Variant;
28
38
  status: ItemStatus;
29
39
  outputPath?: string;
30
40
  error?: string;
31
41
  }) => void;
32
- export declare function enhanceVideo(item: EnhanceItem, options: EnhanceOptions): Promise<ItemResult>;
42
+ export declare function enhanceVideo(item: EnhanceItem, options: EnhanceOptions, onProgress?: (update: {
43
+ variant?: Variant;
44
+ status: ItemStatus;
45
+ outputPath?: string;
46
+ error?: string;
47
+ }) => void): Promise<ItemResult>;
33
48
  export declare function enhanceBatch(items: EnhanceItem[], options: EnhanceOptions, onProgress?: ProgressCallback): Promise<BatchEnhanceResult>;
34
49
  //# sourceMappingURL=enhancer.d.ts.map
package/dist/enhancer.js CHANGED
@@ -39,41 +39,65 @@ const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
40
  const extractor_1 = require("./extractor");
41
41
  const elevenlabs_1 = require("./elevenlabs");
42
+ // ─── Helpers ────────────────────────────────────────────────────────────────
43
+ function resolveVariants(item) {
44
+ const withIsolation = item.withIsolation ?? true;
45
+ const withoutIsolation = item.withoutIsolation ?? true;
46
+ const variants = [];
47
+ if (withIsolation)
48
+ variants.push("isolated");
49
+ if (withoutIsolation)
50
+ variants.push("direct");
51
+ return variants;
52
+ }
53
+ function variantOutputPath(outputDir, videoName, variant) {
54
+ const suffix = variant === "isolated" ? "isolated" : "direct";
55
+ return path.join(outputDir, `${videoName}-${suffix}.mp3`);
56
+ }
42
57
  // ─── Enhance a single video ────────────────────────────────────────────────
43
- async function enhanceVideo(item, options) {
58
+ async function enhanceVideo(item, options, onProgress) {
44
59
  const videoName = path.basename(item.videoPath, path.extname(item.videoPath));
45
- const outputPath = path.join(options.outputDir, `${videoName}.mp3`);
60
+ const variants = resolveVariants(item);
61
+ if (variants.length === 0) {
62
+ throw new Error(`No variants enabled for ${videoName}: at least one of withIsolation or withoutIsolation must be true`);
63
+ }
64
+ if (!fs.existsSync(options.outputDir)) {
65
+ fs.mkdirSync(options.outputDir, { recursive: true });
66
+ }
67
+ const outputs = [];
46
68
  let tempAudioPath = null;
47
69
  try {
48
- // Step 1: Extract audio from video
70
+ // Step 1: Extract audio from video (shared by all variants)
71
+ onProgress?.({ status: "extracting" });
49
72
  tempAudioPath = await (0, extractor_1.extractAudio)(item.videoPath, options.ffmpegPath);
50
- // Step 2: Isolate voice (remove background noise)
51
- const isolatedAudio = await (0, elevenlabs_1.isolateAudio)(tempAudioPath, options.apiKey);
52
- // Step 3: Voice change
53
- const finalAudio = await (0, elevenlabs_1.changeVoice)(isolatedAudio, item.voiceId, options.apiKey);
54
- // Save final audio
55
- if (!fs.existsSync(options.outputDir)) {
56
- fs.mkdirSync(options.outputDir, { recursive: true });
73
+ const extractedBuffer = fs.readFileSync(tempAudioPath);
74
+ // Step 2: Run each variant
75
+ for (const variant of variants) {
76
+ const outputPath = variantOutputPath(options.outputDir, videoName, variant);
77
+ try {
78
+ let voiceInput;
79
+ if (variant === "isolated") {
80
+ onProgress?.({ variant, status: "isolating" });
81
+ voiceInput = await (0, elevenlabs_1.isolateAudio)(tempAudioPath, options.apiKey);
82
+ }
83
+ else {
84
+ voiceInput = extractedBuffer;
85
+ }
86
+ onProgress?.({ variant, status: "voice-changing" });
87
+ const finalAudio = await (0, elevenlabs_1.changeVoice)(voiceInput, item.voiceId, options.apiKey);
88
+ fs.writeFileSync(outputPath, finalAudio);
89
+ outputs.push({ variant, outputPath, status: "success" });
90
+ onProgress?.({ variant, status: "success", outputPath });
91
+ }
92
+ catch (err) {
93
+ const errorMsg = err?.message || String(err);
94
+ outputs.push({ variant, outputPath, status: "error", error: errorMsg });
95
+ onProgress?.({ variant, status: "error", error: errorMsg });
96
+ }
57
97
  }
58
- fs.writeFileSync(outputPath, finalAudio);
59
- return {
60
- item,
61
- videoName,
62
- outputPath,
63
- status: "success",
64
- };
65
- }
66
- catch (err) {
67
- return {
68
- item,
69
- videoName,
70
- outputPath,
71
- status: "error",
72
- error: err?.message || String(err),
73
- };
98
+ return { item, videoName, outputs };
74
99
  }
75
100
  finally {
76
- // Clean up temp file
77
101
  if (tempAudioPath && fs.existsSync(tempAudioPath)) {
78
102
  try {
79
103
  fs.unlinkSync(tempAudioPath);
@@ -93,53 +117,27 @@ async function enhanceBatch(items, options, onProgress) {
93
117
  for (let i = 0; i < items.length; i++) {
94
118
  const item = items[i];
95
119
  const videoName = path.basename(item.videoPath, path.extname(item.videoPath));
96
- // Notify: extracting
97
- onProgress?.({
98
- current: i + 1,
99
- total: items.length,
100
- videoName,
101
- status: "extracting",
102
- });
103
- let tempAudioPath = null;
104
120
  try {
105
- // Step 1: Extract audio
106
- tempAudioPath = await (0, extractor_1.extractAudio)(item.videoPath, options.ffmpegPath);
107
- // Notify: isolating
108
- onProgress?.({
109
- current: i + 1,
110
- total: items.length,
111
- videoName,
112
- status: "isolating",
113
- });
114
- // Step 2: Isolate voice
115
- const isolatedAudio = await (0, elevenlabs_1.isolateAudio)(tempAudioPath, options.apiKey);
116
- // Notify: voice changing
117
- onProgress?.({
118
- current: i + 1,
119
- total: items.length,
120
- videoName,
121
- status: "voice-changing",
122
- });
123
- // Step 3: Voice change
124
- const finalAudio = await (0, elevenlabs_1.changeVoice)(isolatedAudio, item.voiceId, options.apiKey);
125
- // Save
126
- const outputPath = path.join(options.outputDir, `${videoName}.mp3`);
127
- fs.writeFileSync(outputPath, finalAudio);
128
- successCount++;
129
- results.push({ item, videoName, outputPath, status: "success" });
130
- onProgress?.({
131
- current: i + 1,
132
- total: items.length,
133
- videoName,
134
- status: "success",
135
- outputPath,
121
+ const result = await enhanceVideo(item, options, (update) => {
122
+ onProgress?.({
123
+ current: i + 1,
124
+ total: items.length,
125
+ videoName,
126
+ ...update,
127
+ });
136
128
  });
129
+ results.push(result);
130
+ const allOk = result.outputs.every((o) => o.status === "success");
131
+ if (allOk)
132
+ successCount++;
133
+ else
134
+ errorCount++;
137
135
  }
138
136
  catch (err) {
139
- errorCount++;
140
- const outputPath = path.join(options.outputDir, `${videoName}.mp3`);
137
+ // Pre-variant failure (e.g., extraction failed, no variants enabled)
141
138
  const errorMsg = err?.message || String(err);
142
- results.push({ item, videoName, outputPath, status: "error", error: errorMsg });
139
+ errorCount++;
140
+ results.push({ item, videoName, outputs: [] });
143
141
  onProgress?.({
144
142
  current: i + 1,
145
143
  total: items.length,
@@ -148,14 +146,6 @@ async function enhanceBatch(items, options, onProgress) {
148
146
  error: errorMsg,
149
147
  });
150
148
  }
151
- finally {
152
- if (tempAudioPath && fs.existsSync(tempAudioPath)) {
153
- try {
154
- fs.unlinkSync(tempAudioPath);
155
- }
156
- catch { }
157
- }
158
- }
159
149
  }
160
150
  return { results, totalCount: items.length, successCount, errorCount };
161
151
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { extractAudio } from "./extractor";
2
2
  export { isolateAudio, changeVoice } from "./elevenlabs";
3
3
  export { enhanceVideo, enhanceBatch } from "./enhancer";
4
- export type { EnhanceOptions, EnhanceItem, ItemStatus, ItemResult, BatchEnhanceResult, ProgressCallback, } from "./enhancer";
4
+ export type { EnhanceOptions, EnhanceItem, ItemStatus, ItemResult, VariantResult, Variant, BatchEnhanceResult, ProgressCallback, } from "./enhancer";
5
5
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinkable-labs/audio-enhancer",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Extract audio from video, isolate voice with ElevenLabs, apply voice change",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",