@eldrforge/audio-tools 0.1.7 → 0.1.8
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 +709 -32
- package/dist/index.js +3 -24
- package/dist/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +0 -6
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/src/logger.d.ts +0 -10
- package/dist/src/logger.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,73 +1,750 @@
|
|
|
1
1
|
# @eldrforge/audio-tools
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Professional Audio Recording & Transcription Toolkit for Node.js**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- Integration with transcription services
|
|
11
|
-
- Voice-driven workflow support
|
|
7
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
8
|
+
[](https://www.npmjs.com/package/@eldrforge/audio-tools)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
*Voice-driven development workflows made simple*
|
|
12
|
+
|
|
13
|
+
[Features](#-features) • [Installation](#-installation) • [Quick Start](#-quick-start) • [Examples](#-examples) • [API Reference](#-api-reference) • [Documentation](#-documentation)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🎯 Overview
|
|
20
|
+
|
|
21
|
+
`@eldrforge/audio-tools` is a comprehensive TypeScript library for recording, transcribing, and managing audio in Node.js applications. Built for developers who want to integrate voice-driven workflows, voice notes, audio documentation, or AI-powered transcription into their projects.
|
|
22
|
+
|
|
23
|
+
### Key Highlights
|
|
24
|
+
|
|
25
|
+
- 🎙️ **High-Quality Recording** - Capture audio from any input device with configurable settings
|
|
26
|
+
- 🎬 **Visual Countdown Timers** - Professional recording countdowns with ANSI colors and beeps
|
|
27
|
+
- 🔊 **Device Management** - List, select, and configure audio input devices
|
|
28
|
+
- 🤖 **AI Transcription** - Powered by OpenAI's Whisper API for accurate transcription
|
|
29
|
+
- 📦 **Archive Management** - Timestamped archiving of audio files and transcripts
|
|
30
|
+
- 🔧 **Fully Typed** - Complete TypeScript definitions for excellent IDE support
|
|
31
|
+
- 🪵 **Flexible Logging** - Optional Winston logger integration
|
|
32
|
+
- 🚀 **Zero Config** - Sensible defaults, works out of the box
|
|
33
|
+
|
|
34
|
+
## ✨ Features
|
|
35
|
+
|
|
36
|
+
### Audio Recording
|
|
37
|
+
- ✅ Cross-platform support (macOS, Linux, Windows)
|
|
38
|
+
- ✅ Device selection and configuration
|
|
39
|
+
- ✅ Configurable sample rates and formats (WAV, MP3, FLAC)
|
|
40
|
+
- ✅ Duration limits and manual stop controls
|
|
41
|
+
- ✅ Custom output paths
|
|
42
|
+
- ✅ Real-time recording status
|
|
43
|
+
|
|
44
|
+
### Visual Countdown Timers
|
|
45
|
+
- ✅ ANSI color-coded terminal display
|
|
46
|
+
- ✅ In-place updating (no screen clutter)
|
|
47
|
+
- ✅ Audio beeps at configurable intervals
|
|
48
|
+
- ✅ Warning colors when time is low
|
|
49
|
+
- ✅ Callback support for custom behaviors
|
|
50
|
+
- ✅ Graceful cleanup and process handling
|
|
51
|
+
|
|
52
|
+
### Transcription & Archiving
|
|
53
|
+
- ✅ OpenAI Whisper API integration
|
|
54
|
+
- ✅ Automatic file archiving with timestamps
|
|
55
|
+
- ✅ Transcript preservation in Markdown format
|
|
56
|
+
- ✅ Batch processing support
|
|
57
|
+
- ✅ Error recovery and retry logic
|
|
58
|
+
|
|
59
|
+
## 📦 Installation
|
|
14
60
|
|
|
15
61
|
```bash
|
|
16
62
|
npm install @eldrforge/audio-tools
|
|
17
63
|
```
|
|
18
64
|
|
|
19
|
-
|
|
65
|
+
### Peer Dependencies
|
|
20
66
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
67
|
+
The library has optional peer dependencies for enhanced functionality:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# For logging (recommended)
|
|
71
|
+
npm install winston
|
|
72
|
+
|
|
73
|
+
# For shared utilities (optional)
|
|
74
|
+
npm install @eldrforge/shared
|
|
75
|
+
```
|
|
25
76
|
|
|
26
|
-
|
|
77
|
+
### System Requirements
|
|
78
|
+
|
|
79
|
+
- **Node.js**: 18.x or later
|
|
80
|
+
- **Operating System**: macOS, Linux, or Windows
|
|
81
|
+
- **Audio Input**: Microphone or audio input device
|
|
82
|
+
|
|
83
|
+
## 🚀 Quick Start
|
|
84
|
+
|
|
85
|
+
### Basic Recording
|
|
27
86
|
|
|
28
87
|
```typescript
|
|
29
|
-
import { recordAudio
|
|
88
|
+
import { recordAudio } from '@eldrforge/audio-tools';
|
|
30
89
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
90
|
+
// Record up to 60 seconds of audio
|
|
91
|
+
const result = await recordAudio({
|
|
92
|
+
duration: 60,
|
|
93
|
+
countdownDelay: 3,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log('Audio recorded:', result.filePath);
|
|
97
|
+
console.log('Duration:', result.duration, 'seconds');
|
|
98
|
+
console.log('File size:', result.fileSize, 'bytes');
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Record and Transcribe
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { recordAudio, transcribeAudio, archiveAudio } from '@eldrforge/audio-tools';
|
|
34
105
|
|
|
35
106
|
// Record audio
|
|
107
|
+
const recording = await recordAudio({ duration: 120 });
|
|
108
|
+
|
|
109
|
+
// Transcribe with OpenAI Whisper
|
|
110
|
+
const transcript = await transcribeAudio(recording.filePath);
|
|
111
|
+
|
|
112
|
+
// Archive both audio and transcript with timestamps
|
|
113
|
+
const archive = await archiveAudio(
|
|
114
|
+
recording.filePath,
|
|
115
|
+
transcript,
|
|
116
|
+
'output/recordings'
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
console.log('Transcript:', transcript);
|
|
120
|
+
console.log('Archived to:', archive.audioPath);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Interactive Device Selection
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { selectDeviceInteractive, recordAudio } from '@eldrforge/audio-tools';
|
|
127
|
+
|
|
128
|
+
// Let user select audio device interactively
|
|
129
|
+
const device = await selectDeviceInteractive();
|
|
130
|
+
|
|
131
|
+
// Record with selected device
|
|
132
|
+
const result = await recordAudio({ duration: 60 });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Countdown Timer
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { CountdownTimer } from '@eldrforge/audio-tools';
|
|
139
|
+
|
|
140
|
+
const timer = new CountdownTimer({
|
|
141
|
+
durationSeconds: 30,
|
|
142
|
+
beepAt30Seconds: true,
|
|
143
|
+
redAt30Seconds: true,
|
|
144
|
+
onTick: (remaining) => {
|
|
145
|
+
console.log(`${remaining} seconds remaining`);
|
|
146
|
+
},
|
|
147
|
+
onComplete: () => {
|
|
148
|
+
console.log('Time\'s up!');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await timer.start();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 📖 Documentation
|
|
156
|
+
|
|
157
|
+
Comprehensive documentation is available:
|
|
158
|
+
|
|
159
|
+
- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Step-by-step tutorial for beginners
|
|
160
|
+
- **[Quick Reference](docs/QUICK_REFERENCE.md)** - One-page cheat sheet
|
|
161
|
+
- **[CLI Examples](docs/CLI_EXAMPLES.md)** - Build command-line tools
|
|
162
|
+
- **[FAQ](docs/FAQ.md)** - Frequently asked questions
|
|
163
|
+
- **[Architecture](docs/ARCHITECTURE.md)** - Design and internals
|
|
164
|
+
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute
|
|
165
|
+
- **[Documentation Index](docs/INDEX.md)** - Complete documentation map
|
|
166
|
+
|
|
167
|
+
## 📚 Examples
|
|
168
|
+
|
|
169
|
+
The `examples/` directory contains comprehensive, runnable examples:
|
|
170
|
+
|
|
171
|
+
| Example | Description | File |
|
|
172
|
+
|---------|-------------|------|
|
|
173
|
+
| **Basic Recording** | Simple audio recording with default settings | `basic-recording.ts` |
|
|
174
|
+
| **Record & Transcribe** | Complete workflow with transcription and archiving | `record-and-transcribe.ts` |
|
|
175
|
+
| **Countdown Demo** | Visual countdown timer demonstrations | `countdown-demo.ts` |
|
|
176
|
+
| **Device Selection** | List and select audio input devices | `device-selection.ts` |
|
|
177
|
+
| **Custom Output** | Specify custom paths and filenames | `custom-output-path.ts` |
|
|
178
|
+
|
|
179
|
+
### Running Examples
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
cd examples
|
|
183
|
+
npm install
|
|
184
|
+
npm run basic # Basic recording
|
|
185
|
+
npm run transcribe # Record and transcribe (requires OPENAI_API_KEY)
|
|
186
|
+
npm run countdown # Countdown timer demos
|
|
187
|
+
npm run devices # Device selection
|
|
188
|
+
npm run custom-path # Custom output paths
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## 📖 API Reference
|
|
192
|
+
|
|
193
|
+
### Recording Functions
|
|
194
|
+
|
|
195
|
+
#### `recordAudio(options?: RecordingOptions): Promise<RecordingResult>`
|
|
196
|
+
|
|
197
|
+
Record audio from an input device.
|
|
198
|
+
|
|
199
|
+
**Options:**
|
|
200
|
+
```typescript
|
|
201
|
+
interface RecordingOptions {
|
|
202
|
+
device?: AudioDevice | string; // Audio device (optional, uses default)
|
|
203
|
+
duration?: number; // Max duration in seconds (optional)
|
|
204
|
+
outputPath?: string; // Output file path (optional, generates temp)
|
|
205
|
+
countdownDelay?: number; // Countdown delay (default: 3)
|
|
206
|
+
sampleRate?: number; // Sample rate in Hz (default: 44100)
|
|
207
|
+
format?: 'wav' | 'mp3' | 'flac'; // Audio format (default: 'wav')
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Returns:**
|
|
212
|
+
```typescript
|
|
213
|
+
interface RecordingResult {
|
|
214
|
+
filePath: string; // Path to recorded file
|
|
215
|
+
duration: number; // Recording duration in seconds
|
|
216
|
+
fileSize: number; // File size in bytes
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Example:**
|
|
221
|
+
```typescript
|
|
36
222
|
const result = await recordAudio({
|
|
37
|
-
duration:
|
|
38
|
-
|
|
223
|
+
duration: 60,
|
|
224
|
+
sampleRate: 48000,
|
|
225
|
+
format: 'wav',
|
|
226
|
+
countdownDelay: 3
|
|
39
227
|
});
|
|
228
|
+
```
|
|
40
229
|
|
|
41
|
-
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
#### `archiveAudio(audioPath: string, transcript: string, outputDir?: string)`
|
|
233
|
+
|
|
234
|
+
Archive audio file with its transcription.
|
|
235
|
+
|
|
236
|
+
**Parameters:**
|
|
237
|
+
- `audioPath`: Path to the original audio file
|
|
238
|
+
- `transcript`: Transcribed text content
|
|
239
|
+
- `outputDir`: Directory to save archived files (default: 'output')
|
|
240
|
+
|
|
241
|
+
**Returns:**
|
|
242
|
+
```typescript
|
|
243
|
+
{
|
|
244
|
+
audioPath: string; // Path to archived audio file
|
|
245
|
+
transcriptPath: string; // Path to archived transcript
|
|
246
|
+
}
|
|
42
247
|
```
|
|
43
248
|
|
|
44
|
-
|
|
249
|
+
**Example:**
|
|
250
|
+
```typescript
|
|
251
|
+
const archive = await archiveAudio(
|
|
252
|
+
'recording.wav',
|
|
253
|
+
'This is the transcribed text...',
|
|
254
|
+
'output/archives'
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Creates files like:
|
|
258
|
+
// - output/archives/250701-1430-review-audio.wav
|
|
259
|
+
// - output/archives/250701-1430-review-transcript.md
|
|
260
|
+
```
|
|
45
261
|
|
|
46
|
-
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
#### `deleteAudio(audioPath: string): Promise<void>`
|
|
265
|
+
|
|
266
|
+
Delete an audio file safely.
|
|
267
|
+
|
|
268
|
+
**Example:**
|
|
269
|
+
```typescript
|
|
270
|
+
await deleteAudio('temp-recording.wav');
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
#### `getAudioDuration(audioPath: string): Promise<number | null>`
|
|
276
|
+
|
|
277
|
+
Get the duration of an audio file (currently returns null, planned feature).
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
### Device Functions
|
|
282
|
+
|
|
283
|
+
#### `listAudioDevices(): Promise<AudioDevice[]>`
|
|
284
|
+
|
|
285
|
+
List all available audio input devices.
|
|
286
|
+
|
|
287
|
+
**Returns:**
|
|
288
|
+
```typescript
|
|
289
|
+
interface AudioDevice {
|
|
290
|
+
id: string;
|
|
291
|
+
name: string;
|
|
292
|
+
isDefault: boolean;
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
```typescript
|
|
298
|
+
const devices = await listAudioDevices();
|
|
299
|
+
devices.forEach(device => {
|
|
300
|
+
console.log(`${device.name} (${device.id})`);
|
|
301
|
+
if (device.isDefault) {
|
|
302
|
+
console.log(' ✓ Default device');
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
#### `getDefaultDevice(): Promise<AudioDevice | null>`
|
|
310
|
+
|
|
311
|
+
Get the system's default audio input device.
|
|
312
|
+
|
|
313
|
+
**Example:**
|
|
314
|
+
```typescript
|
|
315
|
+
const device = await getDefaultDevice();
|
|
316
|
+
if (device) {
|
|
317
|
+
console.log('Default device:', device.name);
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
#### `findDevice(idOrName: string): Promise<AudioDevice | null>`
|
|
324
|
+
|
|
325
|
+
Find a device by its ID or name.
|
|
326
|
+
|
|
327
|
+
**Example:**
|
|
328
|
+
```typescript
|
|
329
|
+
const device = await findDevice('MacBook Pro Microphone');
|
|
330
|
+
if (device) {
|
|
331
|
+
console.log('Found device:', device.id);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
#### `selectDeviceInteractive(): Promise<string>`
|
|
338
|
+
|
|
339
|
+
Present an interactive menu to select an audio device.
|
|
340
|
+
|
|
341
|
+
**Example:**
|
|
342
|
+
```typescript
|
|
343
|
+
const deviceId = await selectDeviceInteractive();
|
|
344
|
+
console.log('Selected device:', deviceId);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### Transcription Functions
|
|
350
|
+
|
|
351
|
+
#### `transcribeAudio(audioPath: string): Promise<string>`
|
|
352
|
+
|
|
353
|
+
Transcribe an audio file using OpenAI's Whisper API.
|
|
354
|
+
|
|
355
|
+
**Requirements:**
|
|
356
|
+
- `OPENAI_API_KEY` environment variable must be set
|
|
357
|
+
- Audio file must be in a supported format (WAV, MP3, FLAC, etc.)
|
|
358
|
+
|
|
359
|
+
**Example:**
|
|
360
|
+
```typescript
|
|
361
|
+
const transcript = await transcribeAudio('recording.wav');
|
|
362
|
+
console.log('Transcription:', transcript);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### Countdown Timer Classes
|
|
368
|
+
|
|
369
|
+
#### `CountdownTimer`
|
|
370
|
+
|
|
371
|
+
A visual countdown timer with customizable behavior.
|
|
372
|
+
|
|
373
|
+
**Constructor Options:**
|
|
374
|
+
```typescript
|
|
375
|
+
interface CountdownOptions {
|
|
376
|
+
durationSeconds: number; // Duration in seconds
|
|
377
|
+
beepAt30Seconds?: boolean; // Beep at 30s (default: true)
|
|
378
|
+
redAt30Seconds?: boolean; // Red color at 30s (default: true)
|
|
379
|
+
onTick?: (remaining: number) => void; // Called every second
|
|
380
|
+
onComplete?: () => void; // Called when complete
|
|
381
|
+
clearOnComplete?: boolean; // Clear display when done (default: false)
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Methods:**
|
|
386
|
+
- `start(): Promise<void>` - Start the countdown
|
|
387
|
+
- `stop(): void` - Stop the countdown
|
|
388
|
+
- `getRemainingSeconds(): number` - Get remaining time
|
|
389
|
+
- `destroy(): void` - Clean up resources
|
|
390
|
+
- `isTimerDestroyed(): boolean` - Check if destroyed
|
|
391
|
+
|
|
392
|
+
**Example:**
|
|
393
|
+
```typescript
|
|
394
|
+
const timer = new CountdownTimer({
|
|
395
|
+
durationSeconds: 60,
|
|
396
|
+
beepAt30Seconds: true,
|
|
397
|
+
redAt30Seconds: true,
|
|
398
|
+
onTick: (remaining) => {
|
|
399
|
+
if (remaining % 10 === 0) {
|
|
400
|
+
console.log(`${remaining} seconds left`);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
onComplete: () => {
|
|
404
|
+
console.log('Recording complete!');
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await timer.start();
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
#### `startCountdown(options: CountdownOptions): Promise<void>`
|
|
414
|
+
|
|
415
|
+
Convenience function to create and start a countdown timer.
|
|
416
|
+
|
|
417
|
+
**Example:**
|
|
418
|
+
```typescript
|
|
419
|
+
await startCountdown({
|
|
420
|
+
durationSeconds: 30,
|
|
421
|
+
onComplete: () => console.log('Done!')
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
#### `createAudioRecordingCountdown(seconds: number): CountdownTimer`
|
|
428
|
+
|
|
429
|
+
Create a countdown timer with sensible defaults for audio recording.
|
|
430
|
+
|
|
431
|
+
**Example:**
|
|
432
|
+
```typescript
|
|
433
|
+
const timer = createAudioRecordingCountdown(120);
|
|
434
|
+
await timer.start();
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
### Utility Functions
|
|
440
|
+
|
|
441
|
+
#### `getTimestampedArchivedAudioFilename(extension?: string): string`
|
|
442
|
+
|
|
443
|
+
Generate a timestamped filename for archived audio.
|
|
444
|
+
|
|
445
|
+
**Example:**
|
|
446
|
+
```typescript
|
|
447
|
+
const filename = getTimestampedArchivedAudioFilename('.mp3');
|
|
448
|
+
// Returns: "250701-1430-review-audio.mp3"
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
#### `getTimestampedArchivedTranscriptFilename(): string`
|
|
454
|
+
|
|
455
|
+
Generate a timestamped filename for archived transcripts.
|
|
456
|
+
|
|
457
|
+
**Example:**
|
|
458
|
+
```typescript
|
|
459
|
+
const filename = getTimestampedArchivedTranscriptFilename();
|
|
460
|
+
// Returns: "250701-1430-review-transcript.md"
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### Logging
|
|
466
|
+
|
|
467
|
+
#### `setLogger(logger: Logger): void`
|
|
468
|
+
|
|
469
|
+
Set a custom Winston logger instance.
|
|
470
|
+
|
|
471
|
+
**Example:**
|
|
472
|
+
```typescript
|
|
473
|
+
import { setLogger } from '@eldrforge/audio-tools';
|
|
474
|
+
import { createLogger, format, transports } from 'winston';
|
|
475
|
+
|
|
476
|
+
const logger = createLogger({
|
|
477
|
+
level: 'debug',
|
|
478
|
+
format: format.combine(
|
|
479
|
+
format.timestamp(),
|
|
480
|
+
format.colorize(),
|
|
481
|
+
format.simple()
|
|
482
|
+
),
|
|
483
|
+
transports: [
|
|
484
|
+
new transports.Console(),
|
|
485
|
+
new transports.File({ filename: 'audio-tools.log' })
|
|
486
|
+
]
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
setLogger(logger);
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
#### `getLogger(): Logger`
|
|
495
|
+
|
|
496
|
+
Get the current logger instance.
|
|
497
|
+
|
|
498
|
+
**Example:**
|
|
499
|
+
```typescript
|
|
500
|
+
import { getLogger } from '@eldrforge/audio-tools';
|
|
501
|
+
|
|
502
|
+
const logger = getLogger();
|
|
503
|
+
logger.info('Starting recording...');
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 🎬 Complete Usage Example
|
|
509
|
+
|
|
510
|
+
Here's a complete example showing a typical workflow:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import {
|
|
514
|
+
recordAudio,
|
|
515
|
+
transcribeAudio,
|
|
516
|
+
archiveAudio,
|
|
517
|
+
deleteAudio,
|
|
518
|
+
CountdownTimer,
|
|
519
|
+
setLogger
|
|
520
|
+
} from '@eldrforge/audio-tools';
|
|
521
|
+
import { createLogger, format, transports } from 'winston';
|
|
522
|
+
import { config } from 'dotenv';
|
|
523
|
+
|
|
524
|
+
// Load environment variables
|
|
525
|
+
config();
|
|
526
|
+
|
|
527
|
+
// Configure logging
|
|
528
|
+
const logger = createLogger({
|
|
529
|
+
level: 'info',
|
|
530
|
+
format: format.combine(
|
|
531
|
+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
532
|
+
format.colorize(),
|
|
533
|
+
format.printf(({ timestamp, level, message }) =>
|
|
534
|
+
`${timestamp} [${level}]: ${message}`
|
|
535
|
+
)
|
|
536
|
+
),
|
|
537
|
+
transports: [
|
|
538
|
+
new transports.Console(),
|
|
539
|
+
new transports.File({ filename: 'audio-tools.log' })
|
|
540
|
+
]
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
setLogger(logger);
|
|
544
|
+
|
|
545
|
+
async function recordAndTranscribeVoiceNote() {
|
|
546
|
+
let audioPath: string | null = null;
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
console.log('🎙️ Voice Note Recorder');
|
|
550
|
+
console.log('======================\n');
|
|
551
|
+
|
|
552
|
+
// Step 1: Countdown
|
|
553
|
+
console.log('Get ready to speak...\n');
|
|
554
|
+
const countdown = new CountdownTimer({
|
|
555
|
+
durationSeconds: 3,
|
|
556
|
+
beepAt30Seconds: false,
|
|
557
|
+
clearOnComplete: true
|
|
558
|
+
});
|
|
559
|
+
await countdown.start();
|
|
560
|
+
|
|
561
|
+
// Step 2: Record
|
|
562
|
+
console.log('🔴 Recording... (Press ENTER to stop)\n');
|
|
563
|
+
const recording = await recordAudio({
|
|
564
|
+
duration: 300, // 5 minutes max
|
|
565
|
+
sampleRate: 48000,
|
|
566
|
+
format: 'wav'
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
audioPath = recording.filePath;
|
|
570
|
+
logger.info(`Recorded ${recording.duration.toFixed(2)}s of audio`);
|
|
571
|
+
|
|
572
|
+
// Step 3: Transcribe
|
|
573
|
+
console.log('\n📝 Transcribing...');
|
|
574
|
+
const transcript = await transcribeAudio(audioPath);
|
|
575
|
+
|
|
576
|
+
console.log('\n✅ Transcript:\n');
|
|
577
|
+
console.log('─'.repeat(60));
|
|
578
|
+
console.log(transcript);
|
|
579
|
+
console.log('─'.repeat(60));
|
|
580
|
+
|
|
581
|
+
// Step 4: Archive
|
|
582
|
+
console.log('\n💾 Archiving...');
|
|
583
|
+
const archive = await archiveAudio(
|
|
584
|
+
audioPath,
|
|
585
|
+
transcript,
|
|
586
|
+
'output/voice-notes'
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
logger.info(`Archived to: ${archive.audioPath}`);
|
|
590
|
+
logger.info(`Transcript saved: ${archive.transcriptPath}`);
|
|
591
|
+
|
|
592
|
+
// Step 5: Cleanup
|
|
593
|
+
await deleteAudio(audioPath);
|
|
594
|
+
logger.info('Temporary file deleted');
|
|
595
|
+
|
|
596
|
+
console.log('\n✅ Voice note saved successfully!');
|
|
597
|
+
|
|
598
|
+
} catch (error) {
|
|
599
|
+
logger.error('Failed to process voice note:', error);
|
|
600
|
+
|
|
601
|
+
// Cleanup on error
|
|
602
|
+
if (audioPath) {
|
|
603
|
+
try {
|
|
604
|
+
await deleteAudio(audioPath);
|
|
605
|
+
} catch {
|
|
606
|
+
// Ignore cleanup errors
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Run the example
|
|
615
|
+
recordAndTranscribeVoiceNote().catch(error => {
|
|
616
|
+
console.error('\n❌ Error:', error.message);
|
|
617
|
+
process.exit(1);
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## 🔧 Configuration
|
|
622
|
+
|
|
623
|
+
### Environment Variables
|
|
624
|
+
|
|
625
|
+
- `OPENAI_API_KEY` - Required for transcription functionality
|
|
626
|
+
- `NO_COLOR` - Disable ANSI colors in terminal output
|
|
627
|
+
- `TERM` - Terminal type detection for ANSI support
|
|
628
|
+
|
|
629
|
+
### Audio Preferences
|
|
630
|
+
|
|
631
|
+
The library uses `@theunwalked/unplayable` which stores audio device preferences in:
|
|
632
|
+
```
|
|
633
|
+
~/.unplayable/audio-preferences.json
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
You can manually edit this file to set default devices.
|
|
637
|
+
|
|
638
|
+
## 🏗️ Architecture
|
|
639
|
+
|
|
640
|
+
### Dependencies
|
|
641
|
+
|
|
642
|
+
- **[@theunwalked/unplayable](https://github.com/theunwalked/unplayable)** - Cross-platform audio recording
|
|
643
|
+
- **[@eldrforge/ai-service](https://github.com/calenvarek/ai-service)** - OpenAI Whisper integration
|
|
644
|
+
- **[@eldrforge/shared](https://github.com/calenvarek/shared)** - Optional shared utilities
|
|
645
|
+
- **winston** - Optional structured logging
|
|
646
|
+
|
|
647
|
+
### Platform Support
|
|
648
|
+
|
|
649
|
+
| Platform | Status | Backend |
|
|
650
|
+
|----------|--------|---------|
|
|
651
|
+
| macOS | ✅ Supported | CoreAudio |
|
|
652
|
+
| Linux | ✅ Supported | ALSA/PulseAudio |
|
|
653
|
+
| Windows | ✅ Supported | WASAPI |
|
|
654
|
+
|
|
655
|
+
## 🧪 Testing
|
|
656
|
+
|
|
657
|
+
The library includes comprehensive test coverage:
|
|
47
658
|
|
|
48
659
|
```bash
|
|
660
|
+
# Run tests
|
|
661
|
+
npm test
|
|
662
|
+
|
|
663
|
+
# Run tests with coverage
|
|
664
|
+
npm run test
|
|
665
|
+
|
|
666
|
+
# Watch mode
|
|
667
|
+
npm run test -- --watch
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## 🛠️ Development
|
|
671
|
+
|
|
672
|
+
### Build from Source
|
|
673
|
+
|
|
674
|
+
```bash
|
|
675
|
+
# Clone the repository
|
|
676
|
+
git clone https://github.com/calenvarek/audio-tools.git
|
|
677
|
+
cd audio-tools
|
|
678
|
+
|
|
49
679
|
# Install dependencies
|
|
50
680
|
npm install
|
|
51
681
|
|
|
52
682
|
# Build
|
|
53
683
|
npm run build
|
|
54
684
|
|
|
55
|
-
#
|
|
56
|
-
npm
|
|
685
|
+
# Run tests
|
|
686
|
+
npm test
|
|
57
687
|
|
|
58
688
|
# Lint
|
|
59
689
|
npm run lint
|
|
60
690
|
```
|
|
61
691
|
|
|
62
|
-
|
|
692
|
+
### Project Structure
|
|
693
|
+
|
|
694
|
+
```
|
|
695
|
+
audio-tools/
|
|
696
|
+
├── src/
|
|
697
|
+
│ ├── countdown.ts # Countdown timer utilities
|
|
698
|
+
│ ├── devices.ts # Audio device management
|
|
699
|
+
│ ├── recording.ts # Recording and archiving
|
|
700
|
+
│ ├── transcription.ts # Transcription wrapper
|
|
701
|
+
│ ├── types.ts # TypeScript definitions
|
|
702
|
+
│ └── index.ts # Main exports
|
|
703
|
+
├── tests/ # Test files
|
|
704
|
+
├── examples/ # Usage examples
|
|
705
|
+
├── dist/ # Compiled output
|
|
706
|
+
└── package.json
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## 🤝 Contributing
|
|
710
|
+
|
|
711
|
+
Contributions are welcome! Please:
|
|
712
|
+
|
|
713
|
+
1. Fork the repository
|
|
714
|
+
2. Create a feature branch: `git checkout -b feature/my-feature`
|
|
715
|
+
3. Make your changes with tests
|
|
716
|
+
4. Run tests: `npm test`
|
|
717
|
+
5. Commit with clear messages
|
|
718
|
+
6. Push and open a Pull Request
|
|
719
|
+
|
|
720
|
+
## 📝 License
|
|
721
|
+
|
|
722
|
+
Apache-2.0 License - see [LICENSE](LICENSE) file for details.
|
|
723
|
+
|
|
724
|
+
## 🔗 Related Projects
|
|
725
|
+
|
|
726
|
+
- **[@eldrforge/ai-service](https://github.com/calenvarek/ai-service)** - AI services including transcription
|
|
727
|
+
- **[@eldrforge/shared](https://github.com/calenvarek/shared)** - Shared utilities
|
|
728
|
+
- **[@theunwalked/unplayable](https://github.com/theunwalked/unplayable)** - Cross-platform audio library
|
|
729
|
+
|
|
730
|
+
## 💬 Support
|
|
731
|
+
|
|
732
|
+
- 🚀 **Getting Started**: [Tutorial Guide](docs/GETTING_STARTED.md)
|
|
733
|
+
- 📖 **Documentation**: [Complete Docs](docs/INDEX.md)
|
|
734
|
+
- 🐛 **Issues**: [GitHub Issues](https://github.com/calenvarek/audio-tools/issues)
|
|
735
|
+
- 💡 **Discussions**: [GitHub Discussions](https://github.com/calenvarek/audio-tools/discussions)
|
|
736
|
+
- ❓ **FAQ**: [Frequently Asked Questions](docs/FAQ.md)
|
|
737
|
+
|
|
738
|
+
## 📊 Changelog
|
|
739
|
+
|
|
740
|
+
See [RELEASE_NOTES.md](RELEASE_NOTES.md) for version history and changes.
|
|
63
741
|
|
|
64
|
-
|
|
65
|
-
- ✅ Linux (ALSA/PulseAudio)
|
|
66
|
-
- ✅ Windows (WASAPI)
|
|
742
|
+
---
|
|
67
743
|
|
|
68
|
-
|
|
744
|
+
<div align="center">
|
|
69
745
|
|
|
70
|
-
|
|
746
|
+
Made with ❤️ by [Calen Varek](https://github.com/calenvarek)
|
|
71
747
|
|
|
72
|
-
|
|
748
|
+
⭐ Star this repo if you find it useful!
|
|
73
749
|
|
|
750
|
+
</div>
|
package/dist/index.js
CHANGED
|
@@ -1,32 +1,11 @@
|
|
|
1
|
+
import { getLogger } from '@eldrforge/shared';
|
|
2
|
+
export { getLogger, setLogger } from '@eldrforge/shared';
|
|
1
3
|
import { selectAndConfigureAudioDevice, processAudio } from '@theunwalked/unplayable';
|
|
2
4
|
import { homedir } from 'os';
|
|
3
5
|
import { join } from 'path';
|
|
4
6
|
import { promises } from 'fs';
|
|
5
7
|
import { transcribeAudio as transcribeAudio$1 } from '@eldrforge/ai-service';
|
|
6
8
|
|
|
7
|
-
/**
|
|
8
|
-
* Logger utilities for audio-tools
|
|
9
|
-
*/ let logger;
|
|
10
|
-
/**
|
|
11
|
-
* Set the logger instance
|
|
12
|
-
*/ function setLogger(newLogger) {
|
|
13
|
-
logger = newLogger;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Get the current logger or create a console fallback
|
|
17
|
-
*/ function getLogger() {
|
|
18
|
-
if (logger) {
|
|
19
|
-
return logger;
|
|
20
|
-
}
|
|
21
|
-
// Console fallback
|
|
22
|
-
/* eslint-disable no-console */ return {
|
|
23
|
-
error: (message, ...args)=>console.error(message, ...args),
|
|
24
|
-
warn: (message, ...args)=>console.warn(message, ...args),
|
|
25
|
-
info: (message, ...args)=>console.log(message, ...args),
|
|
26
|
-
debug: (message, ...args)=>console.debug(message, ...args)
|
|
27
|
-
};
|
|
28
|
-
/* eslint-enable no-console */ }
|
|
29
|
-
|
|
30
9
|
function _define_property(obj, key, value) {
|
|
31
10
|
if (key in obj) {
|
|
32
11
|
Object.defineProperty(obj, key, {
|
|
@@ -448,5 +427,5 @@ function _define_property(obj, key, value) {
|
|
|
448
427
|
}
|
|
449
428
|
}
|
|
450
429
|
|
|
451
|
-
export { CountdownTimer, archiveAudio, countdown, createAudioRecordingCountdown, deleteAudio, findDevice, getAudioDuration, getDefaultDevice,
|
|
430
|
+
export { CountdownTimer, archiveAudio, countdown, createAudioRecordingCountdown, deleteAudio, findDevice, getAudioDuration, getDefaultDevice, getTimestampedArchivedAudioFilename, getTimestampedArchivedTranscriptFilename, listAudioDevices, recordAudio, selectDeviceInteractive, startCountdown, transcribeAudio };
|
|
452
431
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/logger.ts","../src/countdown.ts","../src/devices.ts","../src/recording.ts","../src/transcription.ts"],"sourcesContent":["/**\n * Logger utilities for audio-tools\n */\n\nimport type { Logger } from './types';\n\nlet logger: Logger | undefined;\n\n/**\n * Set the logger instance\n */\nexport function setLogger(newLogger: Logger): void {\n logger = newLogger;\n}\n\n/**\n * Get the current logger or create a console fallback\n */\nexport function getLogger(): Logger {\n if (logger) {\n return logger;\n }\n\n // Console fallback\n /* eslint-disable no-console */\n return {\n error: (message: string, ...args: any[]) => console.error(message, ...args),\n warn: (message: string, ...args: any[]) => console.warn(message, ...args),\n info: (message: string, ...args: any[]) => console.log(message, ...args),\n debug: (message: string, ...args: any[]) => console.debug(message, ...args),\n };\n /* eslint-enable no-console */\n}\n\n","#!/usr/bin/env node\n\n/**\n * Countdown timer utility for audio recording sessions\n * Provides a visual countdown with beep warnings and color changes\n */\n\nexport interface CountdownOptions {\n /** Duration in seconds */\n durationSeconds: number;\n /** Show beep warning at 30 seconds remaining */\n beepAt30Seconds?: boolean;\n /** Change color to red at 30 seconds remaining */\n redAt30Seconds?: boolean;\n /** Callback function called every second with remaining time */\n onTick?: (remainingSeconds: number) => void;\n /** Callback function called when countdown reaches zero */\n onComplete?: () => void;\n /** Whether to clear the countdown line when finished */\n clearOnComplete?: boolean;\n}\n\n/**\n * ANSI escape codes for terminal control\n */\nconst ANSI = {\n // Cursor movement\n CURSOR_UP: '\\x1b[1A',\n CURSOR_TO_START: '\\x1b[0G',\n CLEAR_LINE: '\\x1b[2K',\n\n // Colors\n RED: '\\x1b[31m',\n GREEN: '\\x1b[32m',\n YELLOW: '\\x1b[33m',\n BLUE: '\\x1b[34m',\n MAGENTA: '\\x1b[35m',\n CYAN: '\\x1b[36m',\n WHITE: '\\x1b[37m',\n RESET: '\\x1b[0m',\n\n // Text styles\n BOLD: '\\x1b[1m',\n DIM: '\\x1b[2m'\n} as const;\n\n/**\n * Format seconds into MM:SS format\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n}\n\n/**\n * Generate a beep sound using process.stdout.write with ASCII bell character\n */\nfunction beep(): void {\n process.stdout.write('\\x07'); // ASCII bell character\n}\n\n/**\n * Check if terminal supports colors and cursor movement\n */\nfunction supportsAnsi(): boolean {\n return process.stdout.isTTY &&\n process.env.TERM !== 'dumb' &&\n !process.env.NO_COLOR;\n}\n\n/**\n * Display a live countdown timer that updates in place\n */\nexport class CountdownTimer {\n private options: Required<CountdownOptions>;\n private intervalId: NodeJS.Timeout | null = null;\n private currentSeconds: number;\n private hasBeepedAt30: boolean = false;\n private isFirstDisplay: boolean = true;\n private supportsAnsi: boolean;\n private cleanupHandlers: Array<() => void> = [];\n private isDestroyed = false;\n\n constructor(options: CountdownOptions) {\n this.options = {\n beepAt30Seconds: true,\n redAt30Seconds: true,\n onTick: () => {},\n onComplete: () => {},\n clearOnComplete: false,\n ...options\n };\n this.currentSeconds = this.options.durationSeconds;\n this.supportsAnsi = supportsAnsi();\n\n // Set up cleanup handlers for process termination\n this.setupCleanupHandlers();\n }\n\n /**\n * Start the countdown timer\n */\n start(): Promise<void> {\n return new Promise((resolve) => {\n // Handle zero seconds case\n if (this.currentSeconds <= 0) {\n this.options.onComplete();\n resolve();\n return;\n }\n\n // Display initial countdown\n this.displayCountdown();\n\n this.intervalId = setInterval(() => {\n // Check if destroyed before processing\n if (this.isDestroyed) {\n clearInterval(this.intervalId!);\n resolve();\n return;\n }\n\n this.currentSeconds--;\n\n // Check for beep warning\n if (this.options.beepAt30Seconds &&\n this.currentSeconds === 30 &&\n !this.hasBeepedAt30) {\n beep();\n this.hasBeepedAt30 = true;\n }\n\n // Call tick callback\n this.options.onTick(this.currentSeconds);\n\n if (this.currentSeconds <= 0) {\n this.stop();\n this.options.onComplete();\n resolve();\n } else {\n this.displayCountdown();\n }\n }, 1000);\n });\n }\n\n /**\n * Stop the countdown timer\n */\n stop(): void {\n if (this.isDestroyed) {\n return;\n }\n\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n\n if (this.options.clearOnComplete && this.supportsAnsi) {\n // Clear the countdown line\n process.stdout.write(ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);\n } else if (!this.isFirstDisplay) {\n // Add a newline if we've been updating in place\n process.stdout.write('\\n');\n }\n }\n\n /**\n * Get current remaining time\n */\n getRemainingSeconds(): number {\n return this.currentSeconds;\n }\n\n /**\n * Display the countdown timer\n */\n private displayCountdown(): void {\n const timeString = formatTime(this.currentSeconds);\n const isWarningTime = this.currentSeconds <= 30;\n\n let output: string;\n\n if (this.supportsAnsi) {\n // Use colors and in-place updating if supported\n if (!this.isFirstDisplay) {\n // Move cursor up and clear the line to overwrite previous countdown\n process.stdout.write(ANSI.CURSOR_UP + ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);\n }\n\n const color = isWarningTime && this.options.redAt30Seconds ? ANSI.RED : ANSI.CYAN;\n const style = isWarningTime ? ANSI.BOLD : '';\n\n output = `${color}${style}⏱️ Recording time remaining: ${timeString}${ANSI.RESET}`;\n } else {\n // Fallback for terminals that don't support ANSI\n const warning = isWarningTime ? ' ⚠️ ' : '';\n output = `⏱️ Recording time remaining: ${timeString}${warning}`;\n }\n\n process.stdout.write(output + '\\n');\n this.isFirstDisplay = false;\n }\n\n /**\n * Set up cleanup handlers for process termination and uncaught exceptions\n */\n private setupCleanupHandlers(): void {\n // Skip setting up process listeners in test environments to avoid listener leaks\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n return;\n }\n\n const cleanup = () => {\n this.destroy();\n };\n\n // Handle various exit scenarios\n const exitHandler = () => cleanup();\n const uncaughtExceptionHandler = (error: Error) => {\n cleanup();\n // Re-throw to maintain normal error handling\n throw error;\n };\n\n process.on('exit', exitHandler);\n process.on('SIGINT', exitHandler);\n process.on('SIGTERM', exitHandler);\n process.on('uncaughtException', uncaughtExceptionHandler);\n process.on('unhandledRejection', cleanup);\n\n // Store handlers for removal during destroy\n this.cleanupHandlers = [\n () => process.removeListener('exit', exitHandler),\n () => process.removeListener('SIGINT', exitHandler),\n () => process.removeListener('SIGTERM', exitHandler),\n () => process.removeListener('uncaughtException', uncaughtExceptionHandler),\n () => process.removeListener('unhandledRejection', cleanup)\n ];\n }\n\n /**\n * Destroy the timer and clean up all resources\n */\n destroy(): void {\n if (this.isDestroyed) {\n return;\n }\n\n this.isDestroyed = true;\n this.stop();\n\n // Remove all process event listeners\n this.cleanupHandlers.forEach(handler => {\n try {\n handler();\n } catch {\n // Ignore errors during cleanup\n }\n });\n this.cleanupHandlers = [];\n }\n\n /**\n * Check if the timer has been destroyed\n */\n isTimerDestroyed(): boolean {\n return this.isDestroyed;\n }\n}\n\n/**\n * Create and start a countdown timer (convenience function)\n */\nexport async function startCountdown(options: CountdownOptions): Promise<void> {\n const timer = new CountdownTimer(options);\n return timer.start();\n}\n\n/**\n * Create a countdown timer for audio recording with sensible defaults\n */\nexport function createAudioRecordingCountdown(durationSeconds: number): CountdownTimer {\n return new CountdownTimer({\n durationSeconds,\n beepAt30Seconds: true,\n redAt30Seconds: true,\n clearOnComplete: true\n });\n}\n\n/**\n * Simple countdown function for backwards compatibility\n * @param seconds Number of seconds to count down\n * @param onTick Optional callback called on each tick\n * @deprecated Use CountdownTimer or startCountdown for more features\n */\nexport async function countdown(\n seconds: number,\n onTick?: (remaining: number) => void\n): Promise<void> {\n const options: CountdownOptions = {\n durationSeconds: seconds,\n beepAt30Seconds: false,\n redAt30Seconds: false,\n clearOnComplete: false\n };\n\n if (onTick) {\n options.onTick = onTick;\n }\n\n await startCountdown(options);\n}\n","/**\n * Audio device detection and selection\n */\n\nimport { selectAndConfigureAudioDevice } from '@theunwalked/unplayable';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { getLogger } from './logger';\nimport type { AudioDevice } from './types';\n\n/**\n * List all available audio input devices\n * Note: This is a placeholder - @theunwalked/unplayable doesn't provide a direct API for this\n * In practice, you would use selectAndConfigureAudioDevice for device selection\n */\nexport async function listAudioDevices(): Promise<AudioDevice[]> {\n const logger = getLogger();\n\n // This would require extending @theunwalked/unplayable or using a different library\n logger.warn('listAudioDevices is not fully implemented - use selectDeviceInteractive instead');\n return [];\n}\n\n/**\n * Get the default audio input device\n */\nexport async function getDefaultDevice(): Promise<AudioDevice | null> {\n const devices = await listAudioDevices();\n return devices.find((d) => d.isDefault) || devices[0] || null;\n}\n\n/**\n * Find device by ID or name\n */\nexport async function findDevice(idOrName: string): Promise<AudioDevice | null> {\n const devices = await listAudioDevices();\n\n return (\n devices.find((d) => d.id === idOrName) ||\n devices.find((d) => d.name === idOrName) ||\n null\n );\n}\n\n/**\n * Interactive device selection using @theunwalked/unplayable\n * This function uses the built-in interactive device selector\n */\nexport async function selectDeviceInteractive(): Promise<string> {\n const logger = getLogger();\n\n try {\n const preferencesDir = join(homedir(), '.unplayable');\n const result = await selectAndConfigureAudioDevice(preferencesDir, logger, false);\n return result;\n } catch (error) {\n logger.error('Device selection failed:', error);\n throw new Error(`Device selection failed: ${error}`);\n }\n}\n\n","/**\n * Audio recording functionality\n */\n\nimport { processAudio } from '@theunwalked/unplayable';\nimport { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { getLogger } from './logger';\nimport type { RecordingOptions, RecordingResult } from './types';\n\n/**\n * Record audio using @theunwalked/unplayable\n */\nexport async function recordAudio(\n options: RecordingOptions = {}\n): Promise<RecordingResult> {\n const logger = getLogger();\n\n const {\n duration,\n outputPath,\n } = options;\n\n const startTime = Date.now();\n\n try {\n logger.info('Starting audio recording...');\n logger.info('Press ENTER to stop recording');\n\n const audioResult = await processAudio({\n file: undefined,\n maxRecordingTime: duration,\n outputDirectory: outputPath ? outputPath.substring(0, outputPath.lastIndexOf('/')) : 'output',\n debug: false\n });\n\n if (audioResult.cancelled) {\n throw new Error('Recording cancelled by user');\n }\n\n const endTime = Date.now();\n const actualDuration = (endTime - startTime) / 1000;\n\n // Get file stats\n const filePath = audioResult.audioFilePath || join('output', `recording-${Date.now()}.wav`);\n const stats = await fs.stat(filePath);\n\n logger.info(`Recording complete: ${filePath}`);\n logger.info(`Duration: ${actualDuration.toFixed(2)} seconds`);\n logger.info(`File size: ${(stats.size / 1024).toFixed(2)} KB`);\n\n return {\n filePath,\n duration: actualDuration,\n fileSize: stats.size,\n };\n } catch (error) {\n logger.error('Recording failed:', error);\n throw new Error(`Recording failed: ${error}`);\n }\n}\n\n/**\n * Get timestamped filename for archiving\n */\nfunction getTimestampedFilename(baseName: string, extension: string = '.json'): string {\n const now = new Date();\n\n // Format as YYMMdd-HHmm (e.g., 250701-1030)\n const yy = now.getFullYear().toString().slice(-2);\n const mm = (now.getMonth() + 1).toString().padStart(2, '0');\n const dd = now.getDate().toString().padStart(2, '0');\n const hh = now.getHours().toString().padStart(2, '0');\n const min = now.getMinutes().toString().padStart(2, '0');\n\n const timestamp = `${yy}${mm}${dd}-${hh}${min}`;\n\n return `${timestamp}-${baseName}${extension}`;\n}\n\n/**\n * Get timestamped filename for archived audio\n */\nexport function getTimestampedArchivedAudioFilename(originalExtension: string = '.wav'): string {\n return getTimestampedFilename('review-audio', originalExtension);\n}\n\n/**\n * Get timestamped filename for archived transcript\n */\nexport function getTimestampedArchivedTranscriptFilename(): string {\n return getTimestampedFilename('review-transcript', '.md');\n}\n\n/**\n * Archive audio file with transcription to specified directory\n * This saves BOTH the audio file AND transcription text together\n * @param originalAudioPath Path to the original audio file\n * @param transcriptionText The transcribed text content\n * @param outputDirectory Directory to save archived files (default: 'output')\n * @returns Paths to both archived audio and transcript files\n */\nexport async function archiveAudio(\n originalAudioPath: string,\n transcriptionText: string,\n outputDirectory: string = 'output'\n): Promise<{ audioPath: string; transcriptPath: string }> {\n const logger = getLogger();\n const path = await import('path');\n\n try {\n // Ensure the output directory exists\n await fs.mkdir(outputDirectory, { recursive: true });\n\n // Get file extension from original audio file\n const originalExtension = path.extname(originalAudioPath);\n\n // Generate timestamped filenames\n const archivedAudioFilename = getTimestampedArchivedAudioFilename(originalExtension);\n const archivedTranscriptFilename = getTimestampedArchivedTranscriptFilename();\n\n // Full paths for archived files\n const archivedAudioPath = path.join(outputDirectory, archivedAudioFilename);\n const archivedTranscriptPath = path.join(outputDirectory, archivedTranscriptFilename);\n\n // Copy audio file if it exists\n try {\n await fs.access(originalAudioPath);\n const audioBuffer = await fs.readFile(originalAudioPath);\n await fs.writeFile(archivedAudioPath, audioBuffer);\n logger.debug('Archived audio file to: %s', archivedAudioPath);\n } catch {\n logger.warn('AUDIO_FILE_NOT_FOUND: Original audio file not accessible | Path: %s | Impact: Cannot archive original', originalAudioPath);\n }\n\n // Save transcription text\n const transcriptContent = `# Audio Transcription Archive\\n\\n**Original Audio File:** ${originalAudioPath}\\n**Archived:** ${new Date().toISOString()}\\n\\n## Transcription\\n\\n${transcriptionText}`;\n await fs.writeFile(archivedTranscriptPath, transcriptContent, 'utf8');\n logger.debug('Archived transcription to: %s', archivedTranscriptPath);\n\n logger.info('AUDIO_ARCHIVED: Audio and transcript archived successfully | Audio: %s | Transcript: %s | Status: archived', archivedAudioFilename, archivedTranscriptFilename);\n\n return {\n audioPath: archivedAudioPath,\n transcriptPath: archivedTranscriptPath\n };\n\n } catch (error: any) {\n logger.error('AUDIO_ARCHIVE_FAILED: Failed to archive audio files | Error: %s | Impact: Audio not preserved', error.message);\n throw new Error(`Audio archiving failed: ${error.message}`);\n }\n}\n\n/**\n * Delete audio file\n */\nexport async function deleteAudio(audioPath: string): Promise<void> {\n const logger = getLogger();\n\n try {\n await fs.unlink(audioPath);\n logger.debug(`Deleted audio file: ${audioPath}`);\n } catch (error: any) {\n if (error.code !== 'ENOENT') {\n logger.warn(`Failed to delete audio file: ${error}`);\n }\n }\n}\n\n/**\n * Get audio file duration (requires external library or audio analysis)\n * For now, returns null - can be enhanced later\n */\nexport async function getAudioDuration(_audioPath: string): Promise<number | null> {\n // TODO: Implement audio duration detection\n // May require additional library like 'music-metadata'\n return null;\n}\n\n","/**\n * Audio transcription utilities\n * Wraps @eldrforge/ai-service for convenience\n */\n\nimport { transcribeAudio as aiTranscribe } from '@eldrforge/ai-service';\nimport { getLogger } from './logger';\n\n/**\n * Transcribe audio file using AI service\n * This is a convenience wrapper around @eldrforge/ai-service\n */\nexport async function transcribeAudio(audioPath: string): Promise<string> {\n const logger = getLogger();\n\n try {\n logger.info('Transcribing audio...');\n const result = await aiTranscribe(audioPath);\n const transcript = result.text;\n logger.info('Transcription complete');\n return transcript;\n } catch (error) {\n logger.error('Transcription failed:', error);\n throw new Error(`Transcription failed: ${error}`);\n }\n}\n\n"],"names":["logger","setLogger","newLogger","getLogger","error","message","args","console","warn","info","log","debug","ANSI","CURSOR_UP","CURSOR_TO_START","CLEAR_LINE","RED","CYAN","RESET","BOLD","formatTime","seconds","minutes","Math","floor","remainingSeconds","toString","padStart","beep","process","stdout","write","supportsAnsi","isTTY","env","TERM","NO_COLOR","CountdownTimer","start","Promise","resolve","currentSeconds","options","onComplete","displayCountdown","intervalId","setInterval","isDestroyed","clearInterval","beepAt30Seconds","hasBeepedAt30","onTick","stop","clearOnComplete","isFirstDisplay","getRemainingSeconds","timeString","isWarningTime","output","color","redAt30Seconds","style","warning","setupCleanupHandlers","NODE_ENV","VITEST","cleanup","destroy","exitHandler","uncaughtExceptionHandler","on","cleanupHandlers","removeListener","forEach","handler","isTimerDestroyed","durationSeconds","startCountdown","timer","createAudioRecordingCountdown","countdown","listAudioDevices","getDefaultDevice","devices","find","d","isDefault","findDevice","idOrName","id","name","selectDeviceInteractive","preferencesDir","join","homedir","result","selectAndConfigureAudioDevice","Error","recordAudio","duration","outputPath","startTime","Date","now","audioResult","processAudio","file","undefined","maxRecordingTime","outputDirectory","substring","lastIndexOf","cancelled","endTime","actualDuration","filePath","audioFilePath","stats","fs","stat","toFixed","size","fileSize","getTimestampedFilename","baseName","extension","yy","getFullYear","slice","mm","getMonth","dd","getDate","hh","getHours","min","getMinutes","timestamp","getTimestampedArchivedAudioFilename","originalExtension","getTimestampedArchivedTranscriptFilename","archiveAudio","originalAudioPath","transcriptionText","path","mkdir","recursive","extname","archivedAudioFilename","archivedTranscriptFilename","archivedAudioPath","archivedTranscriptPath","access","audioBuffer","readFile","writeFile","transcriptContent","toISOString","audioPath","transcriptPath","deleteAudio","unlink","code","getAudioDuration","_audioPath","transcribeAudio","aiTranscribe","transcript","text"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/countdown.ts","../src/devices.ts","../src/recording.ts","../src/transcription.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Countdown timer utility for audio recording sessions\n * Provides a visual countdown with beep warnings and color changes\n */\n\nexport interface CountdownOptions {\n /** Duration in seconds */\n durationSeconds: number;\n /** Show beep warning at 30 seconds remaining */\n beepAt30Seconds?: boolean;\n /** Change color to red at 30 seconds remaining */\n redAt30Seconds?: boolean;\n /** Callback function called every second with remaining time */\n onTick?: (remainingSeconds: number) => void;\n /** Callback function called when countdown reaches zero */\n onComplete?: () => void;\n /** Whether to clear the countdown line when finished */\n clearOnComplete?: boolean;\n}\n\n/**\n * ANSI escape codes for terminal control\n */\nconst ANSI = {\n // Cursor movement\n CURSOR_UP: '\\x1b[1A',\n CURSOR_TO_START: '\\x1b[0G',\n CLEAR_LINE: '\\x1b[2K',\n\n // Colors\n RED: '\\x1b[31m',\n GREEN: '\\x1b[32m',\n YELLOW: '\\x1b[33m',\n BLUE: '\\x1b[34m',\n MAGENTA: '\\x1b[35m',\n CYAN: '\\x1b[36m',\n WHITE: '\\x1b[37m',\n RESET: '\\x1b[0m',\n\n // Text styles\n BOLD: '\\x1b[1m',\n DIM: '\\x1b[2m'\n} as const;\n\n/**\n * Format seconds into MM:SS format\n */\nfunction formatTime(seconds: number): string {\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;\n}\n\n/**\n * Generate a beep sound using process.stdout.write with ASCII bell character\n */\nfunction beep(): void {\n process.stdout.write('\\x07'); // ASCII bell character\n}\n\n/**\n * Check if terminal supports colors and cursor movement\n */\nfunction supportsAnsi(): boolean {\n return process.stdout.isTTY &&\n process.env.TERM !== 'dumb' &&\n !process.env.NO_COLOR;\n}\n\n/**\n * Display a live countdown timer that updates in place\n */\nexport class CountdownTimer {\n private options: Required<CountdownOptions>;\n private intervalId: NodeJS.Timeout | null = null;\n private currentSeconds: number;\n private hasBeepedAt30: boolean = false;\n private isFirstDisplay: boolean = true;\n private supportsAnsi: boolean;\n private cleanupHandlers: Array<() => void> = [];\n private isDestroyed = false;\n\n constructor(options: CountdownOptions) {\n this.options = {\n beepAt30Seconds: true,\n redAt30Seconds: true,\n onTick: () => {},\n onComplete: () => {},\n clearOnComplete: false,\n ...options\n };\n this.currentSeconds = this.options.durationSeconds;\n this.supportsAnsi = supportsAnsi();\n\n // Set up cleanup handlers for process termination\n this.setupCleanupHandlers();\n }\n\n /**\n * Start the countdown timer\n */\n start(): Promise<void> {\n return new Promise((resolve) => {\n // Handle zero seconds case\n if (this.currentSeconds <= 0) {\n this.options.onComplete();\n resolve();\n return;\n }\n\n // Display initial countdown\n this.displayCountdown();\n\n this.intervalId = setInterval(() => {\n // Check if destroyed before processing\n if (this.isDestroyed) {\n clearInterval(this.intervalId!);\n resolve();\n return;\n }\n\n this.currentSeconds--;\n\n // Check for beep warning\n if (this.options.beepAt30Seconds &&\n this.currentSeconds === 30 &&\n !this.hasBeepedAt30) {\n beep();\n this.hasBeepedAt30 = true;\n }\n\n // Call tick callback\n this.options.onTick(this.currentSeconds);\n\n if (this.currentSeconds <= 0) {\n this.stop();\n this.options.onComplete();\n resolve();\n } else {\n this.displayCountdown();\n }\n }, 1000);\n });\n }\n\n /**\n * Stop the countdown timer\n */\n stop(): void {\n if (this.isDestroyed) {\n return;\n }\n\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n\n if (this.options.clearOnComplete && this.supportsAnsi) {\n // Clear the countdown line\n process.stdout.write(ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);\n } else if (!this.isFirstDisplay) {\n // Add a newline if we've been updating in place\n process.stdout.write('\\n');\n }\n }\n\n /**\n * Get current remaining time\n */\n getRemainingSeconds(): number {\n return this.currentSeconds;\n }\n\n /**\n * Display the countdown timer\n */\n private displayCountdown(): void {\n const timeString = formatTime(this.currentSeconds);\n const isWarningTime = this.currentSeconds <= 30;\n\n let output: string;\n\n if (this.supportsAnsi) {\n // Use colors and in-place updating if supported\n if (!this.isFirstDisplay) {\n // Move cursor up and clear the line to overwrite previous countdown\n process.stdout.write(ANSI.CURSOR_UP + ANSI.CURSOR_TO_START + ANSI.CLEAR_LINE);\n }\n\n const color = isWarningTime && this.options.redAt30Seconds ? ANSI.RED : ANSI.CYAN;\n const style = isWarningTime ? ANSI.BOLD : '';\n\n output = `${color}${style}⏱️ Recording time remaining: ${timeString}${ANSI.RESET}`;\n } else {\n // Fallback for terminals that don't support ANSI\n const warning = isWarningTime ? ' ⚠️ ' : '';\n output = `⏱️ Recording time remaining: ${timeString}${warning}`;\n }\n\n process.stdout.write(output + '\\n');\n this.isFirstDisplay = false;\n }\n\n /**\n * Set up cleanup handlers for process termination and uncaught exceptions\n */\n private setupCleanupHandlers(): void {\n // Skip setting up process listeners in test environments to avoid listener leaks\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n return;\n }\n\n const cleanup = () => {\n this.destroy();\n };\n\n // Handle various exit scenarios\n const exitHandler = () => cleanup();\n const uncaughtExceptionHandler = (error: Error) => {\n cleanup();\n // Re-throw to maintain normal error handling\n throw error;\n };\n\n process.on('exit', exitHandler);\n process.on('SIGINT', exitHandler);\n process.on('SIGTERM', exitHandler);\n process.on('uncaughtException', uncaughtExceptionHandler);\n process.on('unhandledRejection', cleanup);\n\n // Store handlers for removal during destroy\n this.cleanupHandlers = [\n () => process.removeListener('exit', exitHandler),\n () => process.removeListener('SIGINT', exitHandler),\n () => process.removeListener('SIGTERM', exitHandler),\n () => process.removeListener('uncaughtException', uncaughtExceptionHandler),\n () => process.removeListener('unhandledRejection', cleanup)\n ];\n }\n\n /**\n * Destroy the timer and clean up all resources\n */\n destroy(): void {\n if (this.isDestroyed) {\n return;\n }\n\n this.isDestroyed = true;\n this.stop();\n\n // Remove all process event listeners\n this.cleanupHandlers.forEach(handler => {\n try {\n handler();\n } catch {\n // Ignore errors during cleanup\n }\n });\n this.cleanupHandlers = [];\n }\n\n /**\n * Check if the timer has been destroyed\n */\n isTimerDestroyed(): boolean {\n return this.isDestroyed;\n }\n}\n\n/**\n * Create and start a countdown timer (convenience function)\n */\nexport async function startCountdown(options: CountdownOptions): Promise<void> {\n const timer = new CountdownTimer(options);\n return timer.start();\n}\n\n/**\n * Create a countdown timer for audio recording with sensible defaults\n */\nexport function createAudioRecordingCountdown(durationSeconds: number): CountdownTimer {\n return new CountdownTimer({\n durationSeconds,\n beepAt30Seconds: true,\n redAt30Seconds: true,\n clearOnComplete: true\n });\n}\n\n/**\n * Simple countdown function for backwards compatibility\n * @param seconds Number of seconds to count down\n * @param onTick Optional callback called on each tick\n * @deprecated Use CountdownTimer or startCountdown for more features\n */\nexport async function countdown(\n seconds: number,\n onTick?: (remaining: number) => void\n): Promise<void> {\n const options: CountdownOptions = {\n durationSeconds: seconds,\n beepAt30Seconds: false,\n redAt30Seconds: false,\n clearOnComplete: false\n };\n\n if (onTick) {\n options.onTick = onTick;\n }\n\n await startCountdown(options);\n}\n","/**\n * Audio device detection and selection\n */\n\nimport { selectAndConfigureAudioDevice } from '@theunwalked/unplayable';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { getLogger } from '@eldrforge/shared';\nimport type { AudioDevice } from './types';\n\n/**\n * List all available audio input devices\n * Note: This is a placeholder - @theunwalked/unplayable doesn't provide a direct API for this\n * In practice, you would use selectAndConfigureAudioDevice for device selection\n */\nexport async function listAudioDevices(): Promise<AudioDevice[]> {\n const logger = getLogger();\n\n // This would require extending @theunwalked/unplayable or using a different library\n logger.warn('listAudioDevices is not fully implemented - use selectDeviceInteractive instead');\n return [];\n}\n\n/**\n * Get the default audio input device\n */\nexport async function getDefaultDevice(): Promise<AudioDevice | null> {\n const devices = await listAudioDevices();\n return devices.find((d) => d.isDefault) || devices[0] || null;\n}\n\n/**\n * Find device by ID or name\n */\nexport async function findDevice(idOrName: string): Promise<AudioDevice | null> {\n const devices = await listAudioDevices();\n\n return (\n devices.find((d) => d.id === idOrName) ||\n devices.find((d) => d.name === idOrName) ||\n null\n );\n}\n\n/**\n * Interactive device selection using @theunwalked/unplayable\n * This function uses the built-in interactive device selector\n */\nexport async function selectDeviceInteractive(): Promise<string> {\n const logger = getLogger();\n\n try {\n const preferencesDir = join(homedir(), '.unplayable');\n const result = await selectAndConfigureAudioDevice(preferencesDir, logger, false);\n return result;\n } catch (error) {\n logger.error('Device selection failed:', error);\n throw new Error(`Device selection failed: ${error}`);\n }\n}\n\n","/**\n * Audio recording functionality\n */\n\nimport { processAudio } from '@theunwalked/unplayable';\nimport { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { getLogger } from '@eldrforge/shared';\nimport type { RecordingOptions, RecordingResult } from './types';\n\n/**\n * Record audio using @theunwalked/unplayable\n */\nexport async function recordAudio(\n options: RecordingOptions = {}\n): Promise<RecordingResult> {\n const logger = getLogger();\n\n const {\n duration,\n outputPath,\n } = options;\n\n const startTime = Date.now();\n\n try {\n logger.info('Starting audio recording...');\n logger.info('Press ENTER to stop recording');\n\n const audioResult = await processAudio({\n file: undefined,\n maxRecordingTime: duration,\n outputDirectory: outputPath ? outputPath.substring(0, outputPath.lastIndexOf('/')) : 'output',\n debug: false\n });\n\n if (audioResult.cancelled) {\n throw new Error('Recording cancelled by user');\n }\n\n const endTime = Date.now();\n const actualDuration = (endTime - startTime) / 1000;\n\n // Get file stats\n const filePath = audioResult.audioFilePath || join('output', `recording-${Date.now()}.wav`);\n const stats = await fs.stat(filePath);\n\n logger.info(`Recording complete: ${filePath}`);\n logger.info(`Duration: ${actualDuration.toFixed(2)} seconds`);\n logger.info(`File size: ${(stats.size / 1024).toFixed(2)} KB`);\n\n return {\n filePath,\n duration: actualDuration,\n fileSize: stats.size,\n };\n } catch (error) {\n logger.error('Recording failed:', error);\n throw new Error(`Recording failed: ${error}`);\n }\n}\n\n/**\n * Get timestamped filename for archiving\n */\nfunction getTimestampedFilename(baseName: string, extension: string = '.json'): string {\n const now = new Date();\n\n // Format as YYMMdd-HHmm (e.g., 250701-1030)\n const yy = now.getFullYear().toString().slice(-2);\n const mm = (now.getMonth() + 1).toString().padStart(2, '0');\n const dd = now.getDate().toString().padStart(2, '0');\n const hh = now.getHours().toString().padStart(2, '0');\n const min = now.getMinutes().toString().padStart(2, '0');\n\n const timestamp = `${yy}${mm}${dd}-${hh}${min}`;\n\n return `${timestamp}-${baseName}${extension}`;\n}\n\n/**\n * Get timestamped filename for archived audio\n */\nexport function getTimestampedArchivedAudioFilename(originalExtension: string = '.wav'): string {\n return getTimestampedFilename('review-audio', originalExtension);\n}\n\n/**\n * Get timestamped filename for archived transcript\n */\nexport function getTimestampedArchivedTranscriptFilename(): string {\n return getTimestampedFilename('review-transcript', '.md');\n}\n\n/**\n * Archive audio file with transcription to specified directory\n * This saves BOTH the audio file AND transcription text together\n * @param originalAudioPath Path to the original audio file\n * @param transcriptionText The transcribed text content\n * @param outputDirectory Directory to save archived files (default: 'output')\n * @returns Paths to both archived audio and transcript files\n */\nexport async function archiveAudio(\n originalAudioPath: string,\n transcriptionText: string,\n outputDirectory: string = 'output'\n): Promise<{ audioPath: string; transcriptPath: string }> {\n const logger = getLogger();\n const path = await import('path');\n\n try {\n // Ensure the output directory exists\n await fs.mkdir(outputDirectory, { recursive: true });\n\n // Get file extension from original audio file\n const originalExtension = path.extname(originalAudioPath);\n\n // Generate timestamped filenames\n const archivedAudioFilename = getTimestampedArchivedAudioFilename(originalExtension);\n const archivedTranscriptFilename = getTimestampedArchivedTranscriptFilename();\n\n // Full paths for archived files\n const archivedAudioPath = path.join(outputDirectory, archivedAudioFilename);\n const archivedTranscriptPath = path.join(outputDirectory, archivedTranscriptFilename);\n\n // Copy audio file if it exists\n try {\n await fs.access(originalAudioPath);\n const audioBuffer = await fs.readFile(originalAudioPath);\n await fs.writeFile(archivedAudioPath, audioBuffer);\n logger.debug('Archived audio file to: %s', archivedAudioPath);\n } catch {\n logger.warn('AUDIO_FILE_NOT_FOUND: Original audio file not accessible | Path: %s | Impact: Cannot archive original', originalAudioPath);\n }\n\n // Save transcription text\n const transcriptContent = `# Audio Transcription Archive\\n\\n**Original Audio File:** ${originalAudioPath}\\n**Archived:** ${new Date().toISOString()}\\n\\n## Transcription\\n\\n${transcriptionText}`;\n await fs.writeFile(archivedTranscriptPath, transcriptContent, 'utf8');\n logger.debug('Archived transcription to: %s', archivedTranscriptPath);\n\n logger.info('AUDIO_ARCHIVED: Audio and transcript archived successfully | Audio: %s | Transcript: %s | Status: archived', archivedAudioFilename, archivedTranscriptFilename);\n\n return {\n audioPath: archivedAudioPath,\n transcriptPath: archivedTranscriptPath\n };\n\n } catch (error: any) {\n logger.error('AUDIO_ARCHIVE_FAILED: Failed to archive audio files | Error: %s | Impact: Audio not preserved', error.message);\n throw new Error(`Audio archiving failed: ${error.message}`);\n }\n}\n\n/**\n * Delete audio file\n */\nexport async function deleteAudio(audioPath: string): Promise<void> {\n const logger = getLogger();\n\n try {\n await fs.unlink(audioPath);\n logger.debug(`Deleted audio file: ${audioPath}`);\n } catch (error: any) {\n if (error.code !== 'ENOENT') {\n logger.warn(`Failed to delete audio file: ${error}`);\n }\n }\n}\n\n/**\n * Get audio file duration (requires external library or audio analysis)\n * For now, returns null - can be enhanced later\n */\nexport async function getAudioDuration(_audioPath: string): Promise<number | null> {\n // TODO: Implement audio duration detection\n // May require additional library like 'music-metadata'\n return null;\n}\n\n","/**\n * Audio transcription utilities\n * Wraps @eldrforge/ai-service for convenience\n */\n\nimport { transcribeAudio as aiTranscribe } from '@eldrforge/ai-service';\nimport { getLogger } from '@eldrforge/shared';\n\n/**\n * Transcribe audio file using AI service\n * This is a convenience wrapper around @eldrforge/ai-service\n */\nexport async function transcribeAudio(audioPath: string): Promise<string> {\n const logger = getLogger();\n\n try {\n logger.info('Transcribing audio...');\n const result = await aiTranscribe(audioPath);\n const transcript = result.text;\n logger.info('Transcription complete');\n return transcript;\n } catch (error) {\n logger.error('Transcription failed:', error);\n throw new Error(`Transcription failed: ${error}`);\n }\n}\n\n"],"names":["ANSI","CURSOR_UP","CURSOR_TO_START","CLEAR_LINE","RED","CYAN","RESET","BOLD","formatTime","seconds","minutes","Math","floor","remainingSeconds","toString","padStart","beep","process","stdout","write","supportsAnsi","isTTY","env","TERM","NO_COLOR","CountdownTimer","start","Promise","resolve","currentSeconds","options","onComplete","displayCountdown","intervalId","setInterval","isDestroyed","clearInterval","beepAt30Seconds","hasBeepedAt30","onTick","stop","clearOnComplete","isFirstDisplay","getRemainingSeconds","timeString","isWarningTime","output","color","redAt30Seconds","style","warning","setupCleanupHandlers","NODE_ENV","VITEST","cleanup","destroy","exitHandler","uncaughtExceptionHandler","error","on","cleanupHandlers","removeListener","forEach","handler","isTimerDestroyed","durationSeconds","startCountdown","timer","createAudioRecordingCountdown","countdown","listAudioDevices","logger","getLogger","warn","getDefaultDevice","devices","find","d","isDefault","findDevice","idOrName","id","name","selectDeviceInteractive","preferencesDir","join","homedir","result","selectAndConfigureAudioDevice","Error","recordAudio","duration","outputPath","startTime","Date","now","info","audioResult","processAudio","file","undefined","maxRecordingTime","outputDirectory","substring","lastIndexOf","debug","cancelled","endTime","actualDuration","filePath","audioFilePath","stats","fs","stat","toFixed","size","fileSize","getTimestampedFilename","baseName","extension","yy","getFullYear","slice","mm","getMonth","dd","getDate","hh","getHours","min","getMinutes","timestamp","getTimestampedArchivedAudioFilename","originalExtension","getTimestampedArchivedTranscriptFilename","archiveAudio","originalAudioPath","transcriptionText","path","mkdir","recursive","extname","archivedAudioFilename","archivedTranscriptFilename","archivedAudioPath","archivedTranscriptPath","access","audioBuffer","readFile","writeFile","transcriptContent","toISOString","audioPath","transcriptPath","message","deleteAudio","unlink","code","getAudioDuration","_audioPath","transcribeAudio","aiTranscribe","transcript","text"],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Audio recording tools for voice-driven development workflows
|
|
4
4
|
*/
|
|
5
5
|
export * from './types';
|
|
6
|
-
export { setLogger, getLogger } from '
|
|
6
|
+
export { setLogger, getLogger, type Logger } from '@eldrforge/shared';
|
|
7
7
|
export { countdown, CountdownTimer, startCountdown, createAudioRecordingCountdown } from './countdown';
|
|
8
8
|
export type { CountdownOptions } from './countdown';
|
|
9
9
|
export { listAudioDevices, getDefaultDevice, findDevice, selectDeviceInteractive, } from './devices';
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EACH,SAAS,EACT,cAAc,EACd,cAAc,EACd,6BAA6B,EAChC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,EACH,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,EACV,uBAAuB,GAC1B,MAAM,WAAW,CAAC;AAGnB,OAAO,EACH,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,mCAAmC,EACnC,wCAAwC,GAC3C,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -28,10 +28,4 @@ export interface RecordingResult {
|
|
|
28
28
|
/** File size in bytes */
|
|
29
29
|
fileSize: number;
|
|
30
30
|
}
|
|
31
|
-
export interface Logger {
|
|
32
|
-
error: (message: string, ...args: any[]) => void;
|
|
33
|
-
warn: (message: string, ...args: any[]) => void;
|
|
34
|
-
info: (message: string, ...args: any[]) => void;
|
|
35
|
-
debug: (message: string, ...args: any[]) => void;
|
|
36
|
-
}
|
|
37
31
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/src/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAE9B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IAEjB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAE9B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IAEjB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IAEjB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eldrforge/audio-tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Audio recording tools for voice-driven development workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"author": "Calen Varek <calenvarek@gmail.com>",
|
|
40
40
|
"license": "Apache-2.0",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@eldrforge/ai-service": "^0.1.
|
|
42
|
+
"@eldrforge/ai-service": "^0.1.14",
|
|
43
43
|
"@theunwalked/unplayable": "^0.0.21"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@eldrforge/shared": "
|
|
58
|
+
"@eldrforge/shared": "^0.1.3",
|
|
59
59
|
"@eslint/eslintrc": "^3.3.1",
|
|
60
60
|
"@eslint/js": "^9.33.0",
|
|
61
61
|
"@swc/core": "^1.13.3",
|
package/dist/src/logger.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Logger } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* Set the logger instance
|
|
4
|
-
*/
|
|
5
|
-
export declare function setLogger(newLogger: Logger): void;
|
|
6
|
-
/**
|
|
7
|
-
* Get the current logger or create a console fallback
|
|
8
|
-
*/
|
|
9
|
-
export declare function getLogger(): Logger;
|
|
10
|
-
//# sourceMappingURL=logger.d.ts.map
|
package/dist/src/logger.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC;;GAEG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAclC"}
|